diff --git a/plugins/kotlin/src/main/java/org/vineflower/kotlin/expr/KFunctionExprent.java b/plugins/kotlin/src/main/java/org/vineflower/kotlin/expr/KFunctionExprent.java index 063b2f73e6..7bf371609d 100644 --- a/plugins/kotlin/src/main/java/org/vineflower/kotlin/expr/KFunctionExprent.java +++ b/plugins/kotlin/src/main/java/org/vineflower/kotlin/expr/KFunctionExprent.java @@ -30,13 +30,13 @@ public enum KFunctionType implements Typed { STR_TEMPLATE } - public KFunctionExprent(KFunctionType funcType, List operands, BitSet bytecodeOffsets) { + public KFunctionExprent(KFunctionType funcType, List operands, BytecodeRange bytecodeOffsets) { this(FunctionType.OTHER, operands, bytecodeOffsets); this.kType = funcType; } - public KFunctionExprent(FunctionType funcType, List operands, BitSet bytecodeOffsets) { + public KFunctionExprent(FunctionType funcType, List operands, BytecodeRange bytecodeOffsets) { super(funcType, new ArrayList<>(KUtils.replaceExprents(operands)), bytecodeOffsets); } diff --git a/plugins/kotlin/src/main/java/org/vineflower/kotlin/expr/KVarExprent.java b/plugins/kotlin/src/main/java/org/vineflower/kotlin/expr/KVarExprent.java index fd507b6815..34670529e6 100644 --- a/plugins/kotlin/src/main/java/org/vineflower/kotlin/expr/KVarExprent.java +++ b/plugins/kotlin/src/main/java/org/vineflower/kotlin/expr/KVarExprent.java @@ -21,7 +21,7 @@ public enum DeclarationType { private DeclarationType declarationType; - public KVarExprent(int index, VarType varType, VarProcessor processor, BitSet bytecode) { + public KVarExprent(int index, VarType varType, VarProcessor processor, BytecodeRange bytecode) { super(index, varType, processor, bytecode); } diff --git a/src/org/jetbrains/java/decompiler/main/decompiler/ConsoleDecompiler.java b/src/org/jetbrains/java/decompiler/main/decompiler/ConsoleDecompiler.java index 3af2a704d3..920100fb14 100644 --- a/src/org/jetbrains/java/decompiler/main/decompiler/ConsoleDecompiler.java +++ b/src/org/jetbrains/java/decompiler/main/decompiler/ConsoleDecompiler.java @@ -57,8 +57,7 @@ else if (args.length > x+1) { System.out.println("error: Failed to read config file '" + path + "'"); throw new RuntimeException(e); } - } - else { + } else { params.add(args[x]); } } diff --git a/src/org/jetbrains/java/decompiler/main/rels/MethodProcessor.java b/src/org/jetbrains/java/decompiler/main/rels/MethodProcessor.java index 4e4cb44134..b43e44ec5b 100644 --- a/src/org/jetbrains/java/decompiler/main/rels/MethodProcessor.java +++ b/src/org/jetbrains/java/decompiler/main/rels/MethodProcessor.java @@ -20,6 +20,7 @@ import org.jetbrains.java.decompiler.modules.decompiler.deobfuscator.ExceptionDeobfuscator; import org.jetbrains.java.decompiler.modules.decompiler.flow.DirectGraph; import org.jetbrains.java.decompiler.modules.decompiler.flow.FlattenStatementsHelper; +import org.jetbrains.java.decompiler.modules.decompiler.stats.BasicBlockStatement; import org.jetbrains.java.decompiler.modules.decompiler.stats.RootStatement; import org.jetbrains.java.decompiler.modules.decompiler.vars.VarProcessor; import org.jetbrains.java.decompiler.struct.StructClass; @@ -88,9 +89,9 @@ public void run() { public static RootStatement codeToJava(StructClass cl, StructMethod mt, MethodDescriptor md, VarProcessor varProc, LanguageSpec spec) throws IOException { CancelationManager.checkCanceled(); - debugCurrentlyDecompiling.set(null); - debugCurrentCFG.set(null); - debugCurrentDecompileRecord.set(null); + debugCurrentlyDecompiling.remove(); + debugCurrentCFG.remove(); + debugCurrentDecompileRecord.remove(); boolean isInitializer = CodeConstants.CLINIT_NAME.equals(mt.getName()); // for now static initializer only PluginContext pluginContext = PluginContext.getCurrentContext(); @@ -253,6 +254,12 @@ public static RootStatement codeToJava(StructClass cl, StructMethod mt, MethodDe // Main loop while (true) { + if (root.isSimple() && root.getFirst().getExprents().size() <= 1) { + LabelHelper.identifyLabels(root); + decompileRecord.add("IdentifyLabels", root); + break; + } + decompileRecord.incrementMainLoop(); decompileRecord.add("Start", root); @@ -311,11 +318,6 @@ public static RootStatement codeToJava(StructClass cl, StructMethod mt, MethodDe continue; } - if (IntersectionCastProcessor.makeIntersectionCasts(root)) { - decompileRecord.add("intersectionCasts", root); - continue; - } - if (DecompilerContext.getOption(IFernflowerPreferences.PATTERN_MATCHING)) { if (cl.getVersion().hasIfPatternMatching()) { if (IfPatternMatchProcessor.matchInstanceof(root)) { @@ -444,6 +446,10 @@ public static RootStatement codeToJava(StructClass cl, StructMethod mt, MethodDe decompileRecord.add("HideEmptyDefault", root); } + if (IntersectionCastProcessor.makeIntersectionCasts(root)) { + decompileRecord.add("intersectionCasts", root); + } + if (GenericsProcessor.qualifyChains(root)) { decompileRecord.add("QualifyGenericChains", root); } @@ -476,6 +482,8 @@ public static RootStatement codeToJava(StructClass cl, StructMethod mt, MethodDe // Debug print the decompile record DotExporter.toDotFile(decompileRecord, mt, "decompileRecord", false); + // Delete unneeded data now that processing is complete + ExprProcessor.releaseResources(root, varProc); mt.releaseResources(); return root; diff --git a/src/org/jetbrains/java/decompiler/modules/decompiler/ConcatenationHelper.java b/src/org/jetbrains/java/decompiler/modules/decompiler/ConcatenationHelper.java index 15021b3d93..036c99008f 100644 --- a/src/org/jetbrains/java/decompiler/modules/decompiler/ConcatenationHelper.java +++ b/src/org/jetbrains/java/decompiler/modules/decompiler/ConcatenationHelper.java @@ -180,7 +180,7 @@ else if (found == 2) { return createConcatExprent(lstOperands, expr.bytecode); } - private static Exprent createConcatExprent(List lstOperands, BitSet bytecode) { + private static Exprent createConcatExprent(List lstOperands, Exprent.BytecodeRange bytecode) { // build exprent to return Exprent func = lstOperands.get(0); diff --git a/src/org/jetbrains/java/decompiler/modules/decompiler/ExprProcessor.java b/src/org/jetbrains/java/decompiler/modules/decompiler/ExprProcessor.java index 17092f7653..dbd1b4ebcc 100644 --- a/src/org/jetbrains/java/decompiler/modules/decompiler/ExprProcessor.java +++ b/src/org/jetbrains/java/decompiler/modules/decompiler/ExprProcessor.java @@ -212,10 +212,10 @@ public void processBlock(BasicBlockStatement stat, PrimitiveExprsList data, Stru Instruction instr = seq.getInstr(i); Integer bytecode_offset = block.getOldOffset(i); - BitSet bytecode_offsets = null; + Exprent.BytecodeRange bytecode_offsets = null; if (bytecode_offset >= 0) { - bytecode_offsets = new BitSet(); - bytecode_offsets.set(bytecode_offset, bytecode_offset + instr.length); + bytecode_offsets = new Exprent.BytecodeRange(new Exprent.BytecodeSet.Range(bytecode_offset, bytecode_offset + instr.length - 1)); +// bytecode_offsets.set(); } switch (instr.opcode) { @@ -308,7 +308,7 @@ else if (cn instanceof LinkConstant) { Exprent expr = stack.pop(); int varindex = instr.operand(0); if (bytecode_offsets != null) { //TODO: Figure out why this nulls in some cases - bytecode_offsets.set(bytecode_offset, bytecode_offset + instr.length); + bytecode_offsets.or(new Exprent.BytecodeRange(new Exprent.BytecodeSet.Range(bytecode_offset, bytecode_offset + instr.length - 1))); } varExprent = new VarExprent(varindex, varTypes[instr.opcode - opc_istore], varProcessor, bytecode_offsets); varExprent.setBackingInstr(instr); @@ -747,6 +747,23 @@ private static void markExprOddity(RootStatement root, Exprent ex) { } } + public static void releaseResources(RootStatement stat, VarProcessor varProc) { + releaseResources(stat); + + varProc.getVarVersions().getTypeProcessor().getUpperBounds().clear(); + } + + private static void releaseResources(Statement stat) { + for (Statement st : stat.getStats()) { + releaseResources(st); + } + + if (stat instanceof BasicBlockStatement block) { + ((ArrayList)block.getBlock().getInstrOldOffsets()).trimToSize(); + ((ArrayList) block.getExprents()).trimToSize(); + } + } + public static String getTypeName(VarType type) { return getTypeName(type, true); } diff --git a/src/org/jetbrains/java/decompiler/modules/decompiler/FinallyProcessor.java b/src/org/jetbrains/java/decompiler/modules/decompiler/FinallyProcessor.java index b5a69b8b18..41794c55d6 100644 --- a/src/org/jetbrains/java/decompiler/modules/decompiler/FinallyProcessor.java +++ b/src/org/jetbrains/java/decompiler/modules/decompiler/FinallyProcessor.java @@ -46,8 +46,11 @@ public class FinallyProcessor { private final StructMethod mt; private final MethodDescriptor methodDescriptor; private final VarProcessor varProcessor; + + // Ephemeral variables private VarVersionsGraph ssuversions; private Map instrRewrites; + private RootStatement root; public FinallyProcessor(StructMethod mt, MethodDescriptor md, VarProcessor varProc) { this.mt = mt; @@ -57,6 +60,8 @@ public FinallyProcessor(StructMethod mt, MethodDescriptor md, VarProcessor varPr public boolean iterateGraph(StructClass cl, StructMethod mt, RootStatement root, ControlFlowGraph graph) { this.ssuversions = null; + this.instrRewrites = null; + this.root = root; BytecodeVersion bytecodeVersion = mt.getBytecodeVersion(); ListStack stack = new ListStack<>(); @@ -130,19 +135,21 @@ private Record(int firstCode, Map mapLast) { } } - private Record getFinallyInformation(StructClass cl, StructMethod mt, RootStatement root, CatchAllStatement fstat) { - ExprProcessor proc = new ExprProcessor(this.methodDescriptor, this.varProcessor); - proc.processStatement(root, cl); - - if (this.ssuversions == null) { - // FIXME: don't split SSAU unless needed! + private Map getInstrRewrites() { + if (this.instrRewrites == null) { SSAConstructorSparseEx ssa = new SSAConstructorSparseEx(); ssa.splitVariables(root, mt); this.instrRewrites = SimpleSSAReassign.reassignSSAForm(ssa, root); StackVarsProcessor.setVersionsToNull(root); + } + + return this.instrRewrites; + } + private VarVersionsGraph getVarVersionsGraph() { + if (this.ssuversions == null) { SSAUConstructorSparseEx ssau = new SSAUConstructorSparseEx(); ssau.splitVariables(root, mt); @@ -150,6 +157,13 @@ private Record getFinallyInformation(StructClass cl, StructMethod mt, RootStatem StackVarsProcessor.setVersionsToNull(root); } + return this.ssuversions; + } + + private Record getFinallyInformation(StructClass cl, StructMethod mt, RootStatement root, CatchAllStatement fstat) { + ExprProcessor proc = new ExprProcessor(this.methodDescriptor, this.varProcessor); + proc.processStatement(root, cl); + Map mapLast = new LinkedHashMap<>(); BasicBlockStatement firstBlockStatement = fstat.getHandler().getBasichead(); @@ -921,14 +935,15 @@ public boolean equalInstructions(Instruction first, Instruction second, List rewrites = this.getInstrRewrites(); + if (rewrites.containsKey(first)) { + firstOp = rewrites.get(first); } - if (this.instrRewrites.containsKey(second)) { - secondOp = this.instrRewrites.get(second); + if (rewrites.containsKey(second)) { + secondOp = rewrites.get(second); } - if (this.ssuversions.areVarsAnalogous(firstOp, secondOp)) { + if (this.getVarVersionsGraph().areVarsAnalogous(firstOp, secondOp)) { ok = true; } diff --git a/src/org/jetbrains/java/decompiler/modules/decompiler/SimplifyExprentsHelper.java b/src/org/jetbrains/java/decompiler/modules/decompiler/SimplifyExprentsHelper.java index a9f5324d6e..97027ff49b 100644 --- a/src/org/jetbrains/java/decompiler/modules/decompiler/SimplifyExprentsHelper.java +++ b/src/org/jetbrains/java/decompiler/modules/decompiler/SimplifyExprentsHelper.java @@ -1044,7 +1044,7 @@ private static boolean buildIff(Statement stat, SSAConstructorSparseEx ssa) { if (stat instanceof IfStatement && stat.getExprents() == null) { IfStatement statement = (IfStatement) stat; Exprent ifHeadExpr = statement.getHeadexprent(); - BitSet ifHeadExprBytecode = (ifHeadExpr == null ? null : ifHeadExpr.bytecode); + Exprent.BytecodeRange ifHeadExprBytecode = (ifHeadExpr == null ? null : ifHeadExpr.bytecode); if (statement.iftype == IfStatement.IFTYPE_IFELSE) { Statement ifStatement = statement.getIfstat(); diff --git a/src/org/jetbrains/java/decompiler/modules/decompiler/exps/AnnotationExprent.java b/src/org/jetbrains/java/decompiler/modules/decompiler/exps/AnnotationExprent.java index 15797f5475..d3b34ac4b1 100644 --- a/src/org/jetbrains/java/decompiler/modules/decompiler/exps/AnnotationExprent.java +++ b/src/org/jetbrains/java/decompiler/modules/decompiler/exps/AnnotationExprent.java @@ -152,7 +152,7 @@ public void setDidWriteAlready(boolean didWriteAlready) { } @Override - public void getBytecodeRange(BitSet values) { + public void getBytecodeRange(BytecodeRange values) { measureBytecode(values, parValues); measureBytecode(values); } diff --git a/src/org/jetbrains/java/decompiler/modules/decompiler/exps/ArrayExprent.java b/src/org/jetbrains/java/decompiler/modules/decompiler/exps/ArrayExprent.java index 5126342b25..e10d1be63c 100644 --- a/src/org/jetbrains/java/decompiler/modules/decompiler/exps/ArrayExprent.java +++ b/src/org/jetbrains/java/decompiler/modules/decompiler/exps/ArrayExprent.java @@ -15,7 +15,7 @@ public class ArrayExprent extends Exprent { private Exprent index; private final VarType hardType; - public ArrayExprent(Exprent array, Exprent index, VarType hardType, BitSet bytecodeOffsets) { + public ArrayExprent(Exprent array, Exprent index, VarType hardType, BytecodeRange bytecodeOffsets) { super(Type.ARRAY); this.array = array; this.index = index; @@ -130,7 +130,7 @@ public Exprent getIndex() { } @Override - public void getBytecodeRange(BitSet values) { + public void getBytecodeRange(BytecodeRange values) { measureBytecode(values, array); measureBytecode(values, index); measureBytecode(values); diff --git a/src/org/jetbrains/java/decompiler/modules/decompiler/exps/AssertExprent.java b/src/org/jetbrains/java/decompiler/modules/decompiler/exps/AssertExprent.java index 3637d3faa2..212fe93dcf 100644 --- a/src/org/jetbrains/java/decompiler/modules/decompiler/exps/AssertExprent.java +++ b/src/org/jetbrains/java/decompiler/modules/decompiler/exps/AssertExprent.java @@ -56,7 +56,7 @@ public TextBuffer toJava(int indent) { } @Override - public void getBytecodeRange(BitSet values) { + public void getBytecodeRange(BytecodeRange values) { measureBytecode(values, parameters); measureBytecode(values); } diff --git a/src/org/jetbrains/java/decompiler/modules/decompiler/exps/AssignmentExprent.java b/src/org/jetbrains/java/decompiler/modules/decompiler/exps/AssignmentExprent.java index c454010c85..808efe8b54 100644 --- a/src/org/jetbrains/java/decompiler/modules/decompiler/exps/AssignmentExprent.java +++ b/src/org/jetbrains/java/decompiler/modules/decompiler/exps/AssignmentExprent.java @@ -33,7 +33,7 @@ public class AssignmentExprent extends Exprent { private Exprent right; private FunctionExprent.FunctionType condType = null; - public AssignmentExprent(Exprent left, Exprent right, BitSet bytecodeOffsets) { + public AssignmentExprent(Exprent left, Exprent right, BytecodeRange bytecodeOffsets) { super(Type.ASSIGNMENT); this.left = left; this.right = right; @@ -41,7 +41,7 @@ public AssignmentExprent(Exprent left, Exprent right, BitSet bytecodeOffsets) { addBytecodeOffsets(bytecodeOffsets); } - public AssignmentExprent(Exprent left, Exprent right, FunctionExprent.FunctionType condType, BitSet bytecodeOffsets) { + public AssignmentExprent(Exprent left, Exprent right, FunctionExprent.FunctionType condType, BytecodeRange bytecodeOffsets) { this(left, right, bytecodeOffsets); this.condType = condType; } @@ -286,7 +286,7 @@ public boolean equals(Object o) { } @Override - public void getBytecodeRange(BitSet values) { + public void getBytecodeRange(BytecodeRange values) { measureBytecode(values, left); measureBytecode(values, right); measureBytecode(values); diff --git a/src/org/jetbrains/java/decompiler/modules/decompiler/exps/ConstExprent.java b/src/org/jetbrains/java/decompiler/modules/decompiler/exps/ConstExprent.java index 816ff529f8..6415ab63b6 100644 --- a/src/org/jetbrains/java/decompiler/modules/decompiler/exps/ConstExprent.java +++ b/src/org/jetbrains/java/decompiler/modules/decompiler/exps/ConstExprent.java @@ -20,8 +20,8 @@ public class ConstExprent extends Exprent { private static final Map CHAR_ESCAPES = new HashMap<>(); - private static final Map> UNINLINED_DOUBLES = new HashMap<>(); - private static final Map> UNINLINED_FLOATS = new HashMap<>(); + private static final Map> UNINLINED_DOUBLES = new HashMap<>(); + private static final Map> UNINLINED_FLOATS = new HashMap<>(); private static final Set NO_PAREN_VALUES = new HashSet<>(); static { @@ -134,20 +134,20 @@ public class ConstExprent extends Exprent { private final boolean boolPermitted; private boolean wasCondy = false; - public ConstExprent(int val, boolean boolPermitted, BitSet bytecodeOffsets) { + public ConstExprent(int val, boolean boolPermitted, BytecodeRange bytecodeOffsets) { this(guessType(val, boolPermitted), val, boolPermitted, bytecodeOffsets); } - public ConstExprent(VarType constType, Object value, BitSet bytecodeOffsets) { + public ConstExprent(VarType constType, Object value, BytecodeRange bytecodeOffsets) { this(constType, value, false, bytecodeOffsets); } - public ConstExprent(VarType constType, Object value, BitSet bytecodeOffsets, boolean wasCondy) { + public ConstExprent(VarType constType, Object value, BytecodeRange bytecodeOffsets, boolean wasCondy) { this(constType, value, false, bytecodeOffsets); this.wasCondy = wasCondy; } - protected ConstExprent(VarType constType, Object value, boolean boolPermitted, BitSet bytecodeOffsets) { + protected ConstExprent(VarType constType, Object value, boolean boolPermitted, BytecodeRange bytecodeOffsets) { super(Type.CONST); this.constType = constType; this.value = value; @@ -386,15 +386,15 @@ public int getPrecedence() { return super.getPrecedence(); } - private static TextBuffer getPiDouble(BitSet bytecode) { + private static TextBuffer getPiDouble(BytecodeRange bytecode) { return getDouble(bytecode, "PI", "java/lang/Math"); } - private static TextBuffer getDouble(BitSet bytecode, String name, String className) { + private static TextBuffer getDouble(BytecodeRange bytecode, String name, String className) { return new FieldExprent(name, className, true, null, FieldDescriptor.DOUBLE_DESCRIPTOR, bytecode).toJava(0); } - private static TextBuffer getFloat(BitSet bytecode, String name, String className) { + private static TextBuffer getFloat(BytecodeRange bytecode, String name, String className) { return new FieldExprent(name, className, true, null, FieldDescriptor.FLOAT_DESCRIPTOR, bytecode).toJava(0); } @@ -640,7 +640,7 @@ public boolean isBoolPermitted() { } @Override - public void getBytecodeRange(BitSet values) { + public void getBytecodeRange(BytecodeRange values) { measureBytecode(values); } diff --git a/src/org/jetbrains/java/decompiler/modules/decompiler/exps/ExitExprent.java b/src/org/jetbrains/java/decompiler/modules/decompiler/exps/ExitExprent.java index 23701a4f71..548c5615a7 100644 --- a/src/org/jetbrains/java/decompiler/modules/decompiler/exps/ExitExprent.java +++ b/src/org/jetbrains/java/decompiler/modules/decompiler/exps/ExitExprent.java @@ -30,7 +30,7 @@ public enum Type { private final VarType retType; private final MethodDescriptor methodDescriptor; - public ExitExprent(Type exitType, Exprent value, VarType retType, BitSet bytecodeOffsets, MethodDescriptor methodDescriptor) { + public ExitExprent(Type exitType, Exprent value, VarType retType, BytecodeRange bytecodeOffsets, MethodDescriptor methodDescriptor) { super(Exprent.Type.EXIT); this.exitType = exitType; this.value = value; @@ -155,7 +155,7 @@ public MethodDescriptor getMethodDescriptor() { } @Override - public void getBytecodeRange(BitSet values) { + public void getBytecodeRange(BytecodeRange values) { measureBytecode(values, value); measureBytecode(values); } diff --git a/src/org/jetbrains/java/decompiler/modules/decompiler/exps/Exprent.java b/src/org/jetbrains/java/decompiler/modules/decompiler/exps/Exprent.java index 7ff23a3b47..5c0a8ac862 100644 --- a/src/org/jetbrains/java/decompiler/modules/decompiler/exps/Exprent.java +++ b/src/org/jetbrains/java/decompiler/modules/decompiler/exps/Exprent.java @@ -57,7 +57,7 @@ public enum Type { public final Type type; public final int id; - public BitSet bytecode = null; // offsets of bytecode instructions decompiled to this exprent + public BytecodeRange bytecode = null; // offsets of bytecode instructions decompiled to this exprent protected Exprent(Type type) { this.type = type; @@ -175,29 +175,29 @@ public TextBuffer toJava() { public void replaceExprent(Exprent oldExpr, Exprent newExpr) { } - public void addBytecodeOffsets(BitSet bytecodeOffsets) { + public void addBytecodeOffsets(BytecodeRange bytecodeOffsets) { if (bytecodeOffsets != null) { if (bytecode == null) { - bytecode = new BitSet(); + bytecode = new BytecodeRange(); } bytecode.or(bytecodeOffsets); } } - public abstract void getBytecodeRange(BitSet values); + public abstract void getBytecodeRange(BytecodeRange values); - protected void measureBytecode(BitSet values) { + protected void measureBytecode(BytecodeRange values) { if (bytecode != null && values != null) { values.or(bytecode); } } - protected static void measureBytecode(BitSet values, Exprent exprent) { + protected static void measureBytecode(BytecodeRange values, Exprent exprent) { if (exprent != null) exprent.getBytecodeRange(values); } - protected static void measureBytecode(BitSet values, List list) { + protected static void measureBytecode(BytecodeRange values, List list) { if (list != null && !list.isEmpty()) { for (Exprent e : list) e.getBytecodeRange(values); @@ -367,4 +367,133 @@ public boolean match(MatchNode matchNode, MatchEngine engine) { public String toString() { return toJava(0).convertToStringAndAllowDataDiscard(); } + + public static class BytecodeRange { + private BytecodeSet set = new BytecodeSet.Empty(); + + public BytecodeRange() { + + } + + public BytecodeRange(BytecodeSet set) { + this.set = set; + } + + public void or(BytecodeRange range) { +// System.out.print(set + " | " + range.set + " -> "); + this.set = set.or(range.set); + +// System.out.println(set); + } + + public BitSet asBitSet() { + return set.set(); + } + + public int length() { + return asBitSet().length(); + } + } + + public interface BytecodeSet { + + BytecodeSet or(BytecodeSet other); + + BitSet set(); + + class Empty implements BytecodeSet { + + @Override + public BytecodeSet or(BytecodeSet other) { + if (other instanceof Empty) { + return this; + } else if (other instanceof Range r) { + return new Range(r.start, r.end); + } else if (other instanceof Bits b) { + Bits set = new Bits(new BitSet()); + set.or(b); + return set; + } + + throw new IllegalStateException(); + } + + @Override + public BitSet set() { + return new BitSet(); + } + + @Override + public String toString() { + return "Nil"; + } + } + + // Inclusive range + class Range implements BytecodeSet { + public final int start; + public final int end; + + public Range(int start, int end) { + this.start = start; + this.end = end; + } + + @Override + public BytecodeSet or(BytecodeSet other) { + if (other instanceof Empty) { + return this; + } else if (other instanceof Range r) { + if (r.start == start && r.end == end) { + return this; + } if (r.end > end && r.start >= start && r.start <= end + 1) { + return new Range(start, r.end); + } else if (r.start < start && r.end <= end && r.end >= start - 1) { + return new Range(start, r.end); + } + } + + BitSet set = set(); + set.or(other.set()); + + return new Bits(set); + } + + @Override + public BitSet set() { + BitSet set = new BitSet(); + set.set(start, end + 1); + return set; + } + + @Override + public String toString() { + return "[" + start + "-" + end + "]"; + } + } + + class Bits implements BytecodeSet { + private final BitSet set; + + public Bits(BitSet set) { + this.set = set; + } + + @Override + public BytecodeSet or(BytecodeSet other) { + set.or(other.set()); + return this; + } + + @Override + public BitSet set() { + return set; + } + + @Override + public String toString() { + return set.toString(); + } + } + } } \ No newline at end of file diff --git a/src/org/jetbrains/java/decompiler/modules/decompiler/exps/FieldExprent.java b/src/org/jetbrains/java/decompiler/modules/decompiler/exps/FieldExprent.java index 85c7676a96..8bc1f7af10 100644 --- a/src/org/jetbrains/java/decompiler/modules/decompiler/exps/FieldExprent.java +++ b/src/org/jetbrains/java/decompiler/modules/decompiler/exps/FieldExprent.java @@ -40,15 +40,15 @@ public class FieldExprent extends Exprent { private boolean isQualifier = false; private boolean wasCondy = false; - public FieldExprent(LinkConstant cn, Exprent instance, BitSet bytecodeOffsets) { + public FieldExprent(LinkConstant cn, Exprent instance, BytecodeRange bytecodeOffsets) { this(cn.elementname, cn.classname, instance == null, instance, FieldDescriptor.parseDescriptor(cn.descriptor), bytecodeOffsets); } - public FieldExprent(String name, String classname, boolean isStatic, Exprent instance, FieldDescriptor descriptor, BitSet bytecodeOffsets) { + public FieldExprent(String name, String classname, boolean isStatic, Exprent instance, FieldDescriptor descriptor, BytecodeRange bytecodeOffsets) { this(name, classname, isStatic, instance, descriptor, bytecodeOffsets, false, false); } - public FieldExprent(String name, String classname, boolean isStatic, Exprent instance, FieldDescriptor descriptor, BitSet bytecodeOffsets, boolean forceQualified, boolean wasCondy) { + public FieldExprent(String name, String classname, boolean isStatic, Exprent instance, FieldDescriptor descriptor, BytecodeRange bytecodeOffsets, boolean forceQualified, boolean wasCondy) { super(Type.FIELD); this.name = name; this.classname = classname; @@ -284,7 +284,7 @@ public void forceQualified(boolean value) { } @Override - public void getBytecodeRange(BitSet values) { + public void getBytecodeRange(BytecodeRange values) { measureBytecode(values, instance); measureBytecode(values); } diff --git a/src/org/jetbrains/java/decompiler/modules/decompiler/exps/FunctionExprent.java b/src/org/jetbrains/java/decompiler/modules/decompiler/exps/FunctionExprent.java index 49af9bd357..9e9f0c2f66 100644 --- a/src/org/jetbrains/java/decompiler/modules/decompiler/exps/FunctionExprent.java +++ b/src/org/jetbrains/java/decompiler/modules/decompiler/exps/FunctionExprent.java @@ -149,7 +149,7 @@ public boolean isPostfixPPMM() { private boolean needsCast = true; private boolean disableNewlineGroupCreation = false; - public FunctionExprent(FunctionType funcType, ListStack stack, BitSet bytecodeOffsets) { + public FunctionExprent(FunctionType funcType, ListStack stack, BytecodeRange bytecodeOffsets) { this(funcType, new ArrayList<>(), bytecodeOffsets); if (funcType.arity == 1) { @@ -165,7 +165,7 @@ else if (funcType.arity == 2) { } } - public FunctionExprent(FunctionType funcType, List operands, BitSet bytecodeOffsets) { + public FunctionExprent(FunctionType funcType, List operands, BytecodeRange bytecodeOffsets) { super(Type.FUNCTION); this.funcType = funcType; this.lstOperands = operands; @@ -173,7 +173,7 @@ public FunctionExprent(FunctionType funcType, List operands, BitSet byt addBytecodeOffsets(bytecodeOffsets); } - public FunctionExprent(FunctionType funcType, Exprent operand, BitSet bytecodeOffsets) { + public FunctionExprent(FunctionType funcType, Exprent operand, BytecodeRange bytecodeOffsets) { this(funcType, new ArrayList<>(1), bytecodeOffsets); lstOperands.add(operand); } @@ -845,7 +845,7 @@ public boolean allowNewlineAfterQualifier() { } @Override - public void getBytecodeRange(BitSet values) { + public void getBytecodeRange(BytecodeRange values) { measureBytecode(values, lstOperands); measureBytecode(values); } diff --git a/src/org/jetbrains/java/decompiler/modules/decompiler/exps/IfExprent.java b/src/org/jetbrains/java/decompiler/modules/decompiler/exps/IfExprent.java index dd8d555798..9f792d5d69 100644 --- a/src/org/jetbrains/java/decompiler/modules/decompiler/exps/IfExprent.java +++ b/src/org/jetbrains/java/decompiler/modules/decompiler/exps/IfExprent.java @@ -56,7 +56,7 @@ public Type getNegative() { private Exprent condition; - public IfExprent(Type ifType, ListStack stack, BitSet bytecodeOffsets) { + public IfExprent(Type ifType, ListStack stack, BytecodeRange bytecodeOffsets) { this(null, bytecodeOffsets); if (ifType.ordinal() <= Type.LE.ordinal()) { @@ -69,7 +69,7 @@ else if (ifType.ordinal() <= Type.NONNULL.ordinal()) { condition = ifType.functionType == null ? stack.pop() : new FunctionExprent(ifType.functionType, stack, bytecodeOffsets); } - private IfExprent(Exprent condition, BitSet bytecodeOffsets) { + private IfExprent(Exprent condition, BytecodeRange bytecodeOffsets) { super(Exprent.Type.IF); this.condition = condition; @@ -132,7 +132,7 @@ public void setCondition(Exprent condition) { } @Override - public void getBytecodeRange(BitSet values) { + public void getBytecodeRange(BytecodeRange values) { measureBytecode(values, condition); measureBytecode(values); } diff --git a/src/org/jetbrains/java/decompiler/modules/decompiler/exps/InvocationExprent.java b/src/org/jetbrains/java/decompiler/modules/decompiler/exps/InvocationExprent.java index a3ac3222a2..8ba80a8a3d 100644 --- a/src/org/jetbrains/java/decompiler/modules/decompiler/exps/InvocationExprent.java +++ b/src/org/jetbrains/java/decompiler/modules/decompiler/exps/InvocationExprent.java @@ -85,7 +85,7 @@ public InvocationExprent(int opcode, LinkConstant bootstrapMethod, List bootstrapArguments, ListStack stack, - BitSet bytecodeOffsets) { + BytecodeRange bytecodeOffsets) { this(); name = cn.elementname; @@ -1871,7 +1871,7 @@ public void setInvocationInstance() { } @Override - public void getBytecodeRange(BitSet values) { + public void getBytecodeRange(BytecodeRange values) { measureBytecode(values, lstParameters); measureBytecode(values, instance); measureBytecode(values); diff --git a/src/org/jetbrains/java/decompiler/modules/decompiler/exps/MonitorExprent.java b/src/org/jetbrains/java/decompiler/modules/decompiler/exps/MonitorExprent.java index 364f809894..f899f15837 100644 --- a/src/org/jetbrains/java/decompiler/modules/decompiler/exps/MonitorExprent.java +++ b/src/org/jetbrains/java/decompiler/modules/decompiler/exps/MonitorExprent.java @@ -21,7 +21,7 @@ public enum Type { private final Type monType; private Exprent value; - public MonitorExprent(Type monType, Exprent value, BitSet bytecodeOffsets) { + public MonitorExprent(Type monType, Exprent value, BytecodeRange bytecodeOffsets) { super(Exprent.Type.MONITOR); this.monType = monType; this.value = value; @@ -91,7 +91,7 @@ public void setRemove(boolean remove) { } @Override - public void getBytecodeRange(BitSet values) { + public void getBytecodeRange(BytecodeRange values) { measureBytecode(values, value); measureBytecode(values); } diff --git a/src/org/jetbrains/java/decompiler/modules/decompiler/exps/NewExprent.java b/src/org/jetbrains/java/decompiler/modules/decompiler/exps/NewExprent.java index 1af9d340bc..2611ae90ff 100644 --- a/src/org/jetbrains/java/decompiler/modules/decompiler/exps/NewExprent.java +++ b/src/org/jetbrains/java/decompiler/modules/decompiler/exps/NewExprent.java @@ -47,11 +47,11 @@ public class NewExprent extends Exprent { private List genericArgs = new ArrayList<>(); private VarType inferredLambdaType = null; - public NewExprent(VarType newType, ListStack stack, int arrayDim, BitSet bytecodeOffsets) { + public NewExprent(VarType newType, ListStack stack, int arrayDim, BytecodeRange bytecodeOffsets) { this(newType, getDimensions(arrayDim, stack), bytecodeOffsets); } - public NewExprent(VarType newType, List lstDims, BitSet bytecodeOffsets) { + public NewExprent(VarType newType, List lstDims, BytecodeRange bytecodeOffsets) { super(Type.NEW); this.newType = newType; this.lstDims = lstDims; @@ -784,7 +784,7 @@ public boolean equals(Object o) { } @Override - public void getBytecodeRange(BitSet values) { + public void getBytecodeRange(BytecodeRange values) { measureBytecode(values, lstArrayElements); measureBytecode(values, lstDims); measureBytecode(values, constructor); diff --git a/src/org/jetbrains/java/decompiler/modules/decompiler/exps/PatternExprent.java b/src/org/jetbrains/java/decompiler/modules/decompiler/exps/PatternExprent.java index 754305102b..2d28d962a1 100644 --- a/src/org/jetbrains/java/decompiler/modules/decompiler/exps/PatternExprent.java +++ b/src/org/jetbrains/java/decompiler/modules/decompiler/exps/PatternExprent.java @@ -95,7 +95,7 @@ public CheckTypesResult checkExprTypeBounds() { } @Override - public void getBytecodeRange(BitSet values) { + public void getBytecodeRange(BytecodeRange values) { measureBytecode(values, exprents); measureBytecode(values); } diff --git a/src/org/jetbrains/java/decompiler/modules/decompiler/exps/SwitchExprent.java b/src/org/jetbrains/java/decompiler/modules/decompiler/exps/SwitchExprent.java index f15a88293e..2358b92e51 100644 --- a/src/org/jetbrains/java/decompiler/modules/decompiler/exps/SwitchExprent.java +++ b/src/org/jetbrains/java/decompiler/modules/decompiler/exps/SwitchExprent.java @@ -251,7 +251,7 @@ protected List getAllExprents(List list) { } @Override - public void getBytecodeRange(BitSet values) { + public void getBytecodeRange(BytecodeRange values) { measureBytecode(values); } } diff --git a/src/org/jetbrains/java/decompiler/modules/decompiler/exps/SwitchHeadExprent.java b/src/org/jetbrains/java/decompiler/modules/decompiler/exps/SwitchHeadExprent.java index 0a7169f0be..bcd763cabc 100644 --- a/src/org/jetbrains/java/decompiler/modules/decompiler/exps/SwitchHeadExprent.java +++ b/src/org/jetbrains/java/decompiler/modules/decompiler/exps/SwitchHeadExprent.java @@ -17,7 +17,7 @@ public class SwitchHeadExprent extends Exprent { private Exprent value; private List> caseValues = new ArrayList<>(); - public SwitchHeadExprent(Exprent value, BitSet bytecodeOffsets) { + public SwitchHeadExprent(Exprent value, BytecodeRange bytecodeOffsets) { super(Type.SWITCH_HEAD); this.value = value; @@ -116,7 +116,7 @@ public boolean equals(Object o) { } @Override - public void getBytecodeRange(BitSet values) { + public void getBytecodeRange(BytecodeRange values) { if (caseValues != null && !caseValues.isEmpty()) { for (List l : caseValues) { if (l != null && !l.isEmpty()) { diff --git a/src/org/jetbrains/java/decompiler/modules/decompiler/exps/VarExprent.java b/src/org/jetbrains/java/decompiler/modules/decompiler/exps/VarExprent.java index cc83725634..0fe3cc8e3b 100644 --- a/src/org/jetbrains/java/decompiler/modules/decompiler/exps/VarExprent.java +++ b/src/org/jetbrains/java/decompiler/modules/decompiler/exps/VarExprent.java @@ -66,7 +66,7 @@ public VarExprent(int index, VarType varType, VarProcessor processor) { this(index, varType, processor, null); } - public VarExprent(int index, VarType varType, VarProcessor processor, BitSet bytecode) { + public VarExprent(int index, VarType varType, VarProcessor processor, BytecodeRange bytecode) { super(Type.VAR); this.index = index; this.varType = varType; @@ -347,7 +347,7 @@ public boolean equalsVersions(Object o) { } @Override - public void getBytecodeRange(BitSet values) { + public void getBytecodeRange(BytecodeRange values) { measureBytecode(values); } diff --git a/src/org/jetbrains/java/decompiler/modules/decompiler/exps/YieldExprent.java b/src/org/jetbrains/java/decompiler/modules/decompiler/exps/YieldExprent.java index e043ec9fdd..73b140e388 100644 --- a/src/org/jetbrains/java/decompiler/modules/decompiler/exps/YieldExprent.java +++ b/src/org/jetbrains/java/decompiler/modules/decompiler/exps/YieldExprent.java @@ -68,7 +68,7 @@ public VarType getExprType() { } @Override - public void getBytecodeRange(BitSet values) { + public void getBytecodeRange(BytecodeRange values) { measureBytecode(values, this.content); measureBytecode(values); } diff --git a/src/org/jetbrains/java/decompiler/modules/decompiler/flow/DirectNode.java b/src/org/jetbrains/java/decompiler/modules/decompiler/flow/DirectNode.java index c3ffd64159..9c0fd4675d 100644 --- a/src/org/jetbrains/java/decompiler/modules/decompiler/flow/DirectNode.java +++ b/src/org/jetbrains/java/decompiler/modules/decompiler/flow/DirectNode.java @@ -80,7 +80,12 @@ public boolean equals(Object o) { @Override public int hashCode() { - return Objects.hash(type, id); + int res = 1; + + res = 31 * res + type.hashCode(); + res = 31 * res + id.hashCode(); + + return res; } @Override diff --git a/src/org/jetbrains/java/decompiler/modules/decompiler/sforms/SSAUConstructorSparseEx.java b/src/org/jetbrains/java/decompiler/modules/decompiler/sforms/SSAUConstructorSparseEx.java index a7f38829e9..167240fb4b 100644 --- a/src/org/jetbrains/java/decompiler/modules/decompiler/sforms/SSAUConstructorSparseEx.java +++ b/src/org/jetbrains/java/decompiler/modules/decompiler/sforms/SSAUConstructorSparseEx.java @@ -44,7 +44,7 @@ public void splitVariables(RootStatement root, StructMethod mt) { this.ssaStatements(this.dgraph, new HashSet<>(), true, mt, 999_999); - this.ssuVersions.initDominators(); +// this.ssuVersions.initDominators(); // Validation testing ValidationHelper.validateVarVersionsGraph(this.ssuVersions, root, this.varAssignmentMap); diff --git a/src/org/jetbrains/java/decompiler/modules/decompiler/stats/CatchStatement.java b/src/org/jetbrains/java/decompiler/modules/decompiler/stats/CatchStatement.java index 4d6fef2772..78909eeeb7 100644 --- a/src/org/jetbrains/java/decompiler/modules/decompiler/stats/CatchStatement.java +++ b/src/org/jetbrains/java/decompiler/modules/decompiler/stats/CatchStatement.java @@ -219,7 +219,7 @@ public Statement getSimpleCopy() { return cs; } - public void getOffset(BitSet values) { + public void getOffset(Exprent.BytecodeRange values) { super.getOffset(values); for (Exprent exp : this.getResources()) { diff --git a/src/org/jetbrains/java/decompiler/modules/decompiler/stats/DummyExitStatement.java b/src/org/jetbrains/java/decompiler/modules/decompiler/stats/DummyExitStatement.java index 377559466c..694a9566f9 100644 --- a/src/org/jetbrains/java/decompiler/modules/decompiler/stats/DummyExitStatement.java +++ b/src/org/jetbrains/java/decompiler/modules/decompiler/stats/DummyExitStatement.java @@ -1,19 +1,21 @@ // Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package org.jetbrains.java.decompiler.modules.decompiler.stats; +import org.jetbrains.java.decompiler.modules.decompiler.exps.Exprent; + import java.util.BitSet; public final class DummyExitStatement extends Statement { - public BitSet bytecode = null; // offsets of bytecode instructions mapped to dummy exit + public Exprent.BytecodeRange bytecode = null; // offsets of bytecode instructions mapped to dummy exit public DummyExitStatement() { super(StatementType.DUMMY_EXIT); } - public void addBytecodeOffsets(BitSet bytecodeOffsets) { - if (bytecodeOffsets != null && !bytecodeOffsets.isEmpty()) { + public void addBytecodeOffsets(Exprent.BytecodeRange bytecodeOffsets) { + if (bytecodeOffsets != null && !bytecodeOffsets.asBitSet().isEmpty()) { if (bytecode == null) { - bytecode = new BitSet(); + bytecode = new Exprent.BytecodeRange(); } bytecode.or(bytecodeOffsets); } diff --git a/src/org/jetbrains/java/decompiler/modules/decompiler/stats/RootStatement.java b/src/org/jetbrains/java/decompiler/modules/decompiler/stats/RootStatement.java index fe7f33a62f..38288c0b12 100644 --- a/src/org/jetbrains/java/decompiler/modules/decompiler/stats/RootStatement.java +++ b/src/org/jetbrains/java/decompiler/modules/decompiler/stats/RootStatement.java @@ -75,6 +75,10 @@ public void addComments(ControlFlowGraph graph) { public void buildContentFlags() { buildContentFlagsStat(this); + + if (!flags.hasLoops && !flags.hasSwitch && !flags.hasTryCatch && first instanceof BasicBlockStatement) { + flags.simple = true; + } } private void buildContentFlagsStat(Statement stat) { @@ -103,6 +107,10 @@ public boolean hasSwitch() { return this.flags.hasSwitch; } + public boolean isSimple() { + return this.flags.simple; + } + @Override public StartEndPair getStartEndRange() { return StartEndPair.join(first.getStartEndRange(), dummyExit != null ? dummyExit.getStartEndRange() : null); @@ -112,5 +120,6 @@ private static class ContentFlags { private boolean hasTryCatch; private boolean hasLoops; private boolean hasSwitch; + private boolean simple; } } diff --git a/src/org/jetbrains/java/decompiler/modules/decompiler/stats/Statement.java b/src/org/jetbrains/java/decompiler/modules/decompiler/stats/Statement.java index 2d358f0070..7de2f3a1ed 100644 --- a/src/org/jetbrains/java/decompiler/modules/decompiler/stats/Statement.java +++ b/src/org/jetbrains/java/decompiler/modules/decompiler/stats/Statement.java @@ -978,7 +978,7 @@ public String toString() { } //TODO: Cleanup/cache? - public void getOffset(BitSet values) { + public void getOffset(Exprent.BytecodeRange values) { if (this instanceof DummyExitStatement && ((DummyExitStatement)this).bytecode != null) values.or(((DummyExitStatement)this).bytecode); if (this.getExprents() != null) { @@ -1001,9 +1001,9 @@ public void getOffset(BitSet values) { private StartEndPair endpoints; public StartEndPair getStartEndRange() { if (endpoints == null) { - BitSet set = new BitSet(); + Exprent.BytecodeRange set = new Exprent.BytecodeRange(); getOffset(set); - endpoints = new StartEndPair(set.nextSetBit(0), set.length() - 1); + endpoints = new StartEndPair(set.asBitSet().nextSetBit(0), set.length() - 1); } return endpoints; } diff --git a/src/org/jetbrains/java/decompiler/modules/decompiler/vars/VarTypeProcessor.java b/src/org/jetbrains/java/decompiler/modules/decompiler/vars/VarTypeProcessor.java index 3c073d04fa..4620ee0bf2 100644 --- a/src/org/jetbrains/java/decompiler/modules/decompiler/vars/VarTypeProcessor.java +++ b/src/org/jetbrains/java/decompiler/modules/decompiler/vars/VarTypeProcessor.java @@ -36,6 +36,9 @@ public VarTypeProcessor(StructMethod mt, MethodDescriptor md) { } public void calculateVarTypes(RootStatement root, DirectGraph graph) { + lowerBounds.clear(); + upperBounds.clear(); + setInitVars(root); resetExprentTypes(graph); diff --git a/src/org/jetbrains/java/decompiler/modules/decompiler/vars/VarVersionsGraph.java b/src/org/jetbrains/java/decompiler/modules/decompiler/vars/VarVersionsGraph.java index a66717fa41..695014dcb6 100644 --- a/src/org/jetbrains/java/decompiler/modules/decompiler/vars/VarVersionsGraph.java +++ b/src/org/jetbrains/java/decompiler/modules/decompiler/vars/VarVersionsGraph.java @@ -28,6 +28,10 @@ public VarVersionNode createNode(VarVersionPair ver, LocalVariable lvt) { public boolean isDominatorSet(VarVersionNode node, Set domnodes) { if (domnodes.size() == 1) { + if (this.engine == null) { + initDominators(); + } + return this.engine.isDominator(node, domnodes.iterator().next()); } else { if (domnodes.contains(node)) { diff --git a/src/org/jetbrains/java/decompiler/struct/attr/StructLocalVariableTableAttribute.java b/src/org/jetbrains/java/decompiler/struct/attr/StructLocalVariableTableAttribute.java index 05a7c803ea..8aa38ba0ce 100644 --- a/src/org/jetbrains/java/decompiler/struct/attr/StructLocalVariableTableAttribute.java +++ b/src/org/jetbrains/java/decompiler/struct/attr/StructLocalVariableTableAttribute.java @@ -2,6 +2,7 @@ package org.jetbrains.java.decompiler.struct.attr; import org.jetbrains.java.decompiler.code.BytecodeVersion; +import org.jetbrains.java.decompiler.modules.decompiler.exps.Exprent; import org.jetbrains.java.decompiler.modules.decompiler.stats.Statement; import org.jetbrains.java.decompiler.modules.decompiler.vars.VarVersionPair; import org.jetbrains.java.decompiler.struct.consts.ConstantPool; @@ -77,9 +78,9 @@ public Stream matchingVars(int index) { } public Stream matchingVars(Statement stat) { - BitSet values = new BitSet(); + Exprent.BytecodeRange values = new Exprent.BytecodeRange(); stat.getOffset(values); - return getRange(values.nextSetBit(0), values.length() - 1); + return getRange(values.asBitSet().nextSetBit(0), values.length() - 1); } public Stream getRange(int start, int end) { diff --git a/src/org/jetbrains/java/decompiler/struct/gen/VarType.java b/src/org/jetbrains/java/decompiler/struct/gen/VarType.java index edca5c705f..5a4cade2ee 100644 --- a/src/org/jetbrains/java/decompiler/struct/gen/VarType.java +++ b/src/org/jetbrains/java/decompiler/struct/gen/VarType.java @@ -78,7 +78,7 @@ protected VarType(CodeType type, int arrayDim, String value, TypeFamily typeFami ValidationHelper.assertTrue(type == CodeType.NULL || value != null, "Must not be null for non null type"); this.type = type; this.arrayDim = arrayDim; - this.value = value; + this.value = value;// == null ? null : value.intern(); this.typeFamily = typeFamily; this.stackSize = stackSize; } @@ -120,7 +120,7 @@ public VarType(String signature, boolean clType) { this.type = type; this.arrayDim = arrayDim; - this.value = value; + this.value = value;// == null ? null : value.intern(); this.typeFamily = getFamily(type, arrayDim); this.stackSize = getStackSize(type, arrayDim); } diff --git a/src/org/jetbrains/java/decompiler/util/TextBuffer.java b/src/org/jetbrains/java/decompiler/util/TextBuffer.java index 6f4d1100b2..21b3f058bc 100644 --- a/src/org/jetbrains/java/decompiler/util/TextBuffer.java +++ b/src/org/jetbrains/java/decompiler/util/TextBuffer.java @@ -11,6 +11,7 @@ import org.jetbrains.java.decompiler.main.extern.TextTokenVisitor; import org.jetbrains.java.decompiler.modules.decompiler.ExprProcessor; import org.jetbrains.java.decompiler.modules.decompiler.exps.AnnotationExprent; +import org.jetbrains.java.decompiler.modules.decompiler.exps.Exprent; import org.jetbrains.java.decompiler.modules.decompiler.exps.TypeAnnotation; import org.jetbrains.java.decompiler.struct.gen.CodeType; import org.jetbrains.java.decompiler.struct.gen.FieldDescriptor; @@ -350,20 +351,26 @@ public void addStartBytecodeMapping(int bytecodeOffset) { myBytecodeOffsetMapping.putIfAbsent(new BytecodeMappingKey(bytecodeOffset, null, null), 0); } - public void addBytecodeMapping(BitSet bytecodeOffsets) { + public void addBytecodeMapping(Exprent.BytecodeRange bytecodeOffsets) { if (bytecodeOffsets == null) { return; } - for (int i = bytecodeOffsets.nextSetBit(0); i >= 0; i = bytecodeOffsets.nextSetBit(i + 1)) { + + BitSet set = bytecodeOffsets.asBitSet(); + + for (int i = set.nextSetBit(0); i >= 0; i = set.nextSetBit(i + 1)) { addBytecodeMapping(i); } } - public void addStartBytecodeMapping(BitSet bytecodeOffsets) { + public void addStartBytecodeMapping(Exprent.BytecodeRange bytecodeOffsets) { if (bytecodeOffsets == null) { return; } - for (int i = bytecodeOffsets.nextSetBit(0); i >= 0; i = bytecodeOffsets.nextSetBit(i + 1)) { + + BitSet set = bytecodeOffsets.asBitSet(); + + for (int i = set.nextSetBit(0); i >= 0; i = set.nextSetBit(i + 1)) { addStartBytecodeMapping(i); } } @@ -841,7 +848,11 @@ public boolean equals(Object o) { @Override public int hashCode() { - return Objects.hash(myBytecodeOffset, myClass, myMethod); + int res = 1; + res = 31 * res + Integer.hashCode(myBytecodeOffset); + res = 31 * res + (myClass == null ? 0 : myClass.hashCode()); + res = 31 * res + (myMethod == null ? 0 : myMethod.hashCode()); + return res; } @Override diff --git a/src/org/jetbrains/java/decompiler/util/collections/FastSparseSetFactory.java b/src/org/jetbrains/java/decompiler/util/collections/FastSparseSetFactory.java index 77461de2b9..edfdfd0961 100644 --- a/src/org/jetbrains/java/decompiler/util/collections/FastSparseSetFactory.java +++ b/src/org/jetbrains/java/decompiler/util/collections/FastSparseSetFactory.java @@ -1,6 +1,9 @@ // Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package org.jetbrains.java.decompiler.util.collections; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.java.decompiler.modules.decompiler.ValidationHelper; + import java.util.*; public class FastSparseSetFactory { @@ -67,8 +70,10 @@ public static final class FastSparseSet implements Iterable { private final PackedMap colValuesInternal; - private int[] data; - private int[] next; + @NotNull + private final FastSparseSetFactory.IntArray data; + @NotNull + private final FastSparseSetFactory.IntArray next; private FastSparseSet(FastSparseSetFactory factory) { this.factory = factory; @@ -77,13 +82,12 @@ private FastSparseSet(FastSparseSetFactory factory) { // Originally, this returned factory.getLastBlock() + 1. However, in the most common case, only 1 element is added. // This means that the array is unnecessarily large. Instead, max(lastBlock, 1) is used to ensure empty factories // don't produce -1 lengths. - // TODO: the array init of size 1 can be elided, and the array can be lazy initialized when sized above 1 int length = Math.max(factory.getLastBlock(), 1); - this.data = new int[length]; - this.next = null; + this.data = new IntArray(length); + this.next = new IntArray(length); } - private FastSparseSet(FastSparseSetFactory factory, int[] data, int[] next) { + private FastSparseSet(FastSparseSetFactory factory, IntArray data, IntArray next) { this.factory = factory; this.colValuesInternal = factory.getInternalValuesCollection(); @@ -92,15 +96,15 @@ private FastSparseSet(FastSparseSetFactory factory, int[] data, int[] next) { } public FastSparseSet getCopy() { - int[] newData = this.data.clone(); - int[] newNext = this.next == null ? null : this.next.clone(); + IntArray newData = this.data.copy(); + IntArray newNext = this.next.copy(); return new FastSparseSet<>(factory, newData, newNext); } - private int[] ensureCapacity(int index) { + private void ensureCapacity(int index) { - int newlength = data.length; + int newlength = data.length(); if (newlength == 0) { newlength = 1; } @@ -109,12 +113,8 @@ private int[] ensureCapacity(int index) { newlength *= 2; } - data = Arrays.copyOf(data, newlength); - if (next != null) { - next = Arrays.copyOf(next, newlength); - } - - return data; + data.resize(newlength); + next.resize(newlength); } public void add(E element) { @@ -126,25 +126,17 @@ public void add(E element) { } int block = PackedMap.unpackLow(index); - if (block >= data.length) { + if (block >= data.length()) { ensureCapacity(block); } - data[block] |= PackedMap.unpackHigh(index); + data.set(block, data.get(block) | PackedMap.unpackHigh(index)); changeNext(block, getNextIdx(block), block); } private int getNextIdx(int block) { - return next == null ? 0 : next[block]; - } - - private int[] allocNext() { - if (next == null) { - next = new int[data.length]; - } - - return next; + return next.get(block); } public void remove(E element) { @@ -157,10 +149,10 @@ public void remove(E element) { } int block = PackedMap.unpackLow(index); - if (block < data.length) { - data[block] &= ~PackedMap.unpackHigh(index); + if (block < data.length()) { + data.set(block, data.get(block) & ~PackedMap.unpackHigh(index)); - if (data[block] == 0) { + if (data.get(block) == 0) { changeNext(block, block, getNextIdx(block)); } } @@ -176,29 +168,31 @@ public boolean contains(E element) { } int block = PackedMap.unpackLow(index); - return block < data.length && ((data[block] & PackedMap.unpackHigh(index)) != 0); + return block < data.length() && ((data.get(block) & PackedMap.unpackHigh(index)) != 0); } private void setNext() { int link = 0; - for (int i = data.length - 1; i >= 0; i--) { - if (link != 0 && next == null) { - allocNext(); - next[i] = link; + for (int i = data.length() - 1; i >= 0; i--) { + if (link != 0) { + next.set(i, link); } - if (data[i] != 0) { + if (data.get(i) != 0) { link = i; } } } private void changeNext(int key, int oldnext, int newnext) { + if (oldnext == newnext) { + return; + } + for (int i = key - 1; i >= 0; i--) { if (getNextIdx(i) == oldnext) { - allocNext(); - next[i] = newnext; + next.set(i, newnext); } else { break; } @@ -207,18 +201,18 @@ private void changeNext(int key, int oldnext, int newnext) { public void union(FastSparseSet set) { - int[] extdata = set.getData(); - int[] intdata = data; - int intlength = intdata.length; + IntArray extdata = set.getData(); + IntArray intdata = data; + int intlength = intdata.length(); int pointer = 0; do { if (pointer >= intlength) { - intdata = ensureCapacity(extdata.length - 1); + ensureCapacity(extdata.length() - 1); } - boolean nextrec = (intdata[pointer] == 0); - intdata[pointer] |= extdata[pointer]; + boolean nextrec = (intdata.get(pointer) == 0); + intdata.set(pointer, intdata.get(pointer) | extdata.get(pointer)); if (nextrec) { changeNext(pointer, getNextIdx(pointer), pointer); @@ -230,17 +224,17 @@ public void union(FastSparseSet set) { } public void intersection(FastSparseSet set) { - int[] extdata = set.getData(); - int[] intdata = data; + IntArray extdata = set.getData(); + IntArray intdata = data; - int minlength = Math.min(extdata.length, intdata.length); + int minlength = Math.min(extdata.length(), intdata.length()); for (int i = minlength - 1; i >= 0; i--) { - intdata[i] &= extdata[i]; + intdata.set(i, intdata.get(i) & extdata.get(i)); } - for (int i = intdata.length - 1; i >= minlength; i--) { - intdata[i] = 0; + for (int i = intdata.length() - 1; i >= minlength; i--) { + intdata.set(i, 0); } setNext(); @@ -248,9 +242,9 @@ public void intersection(FastSparseSet set) { public void complement(FastSparseSet set) { - int[] extdata = set.getData(); - int[] intdata = data; - int extlength = extdata.length; + IntArray extdata = set.getData(); + IntArray intdata = data; + int extlength = extdata.length(); int pointer = 0; do { @@ -258,8 +252,8 @@ public void complement(FastSparseSet set) { break; } - intdata[pointer] &= ~extdata[pointer]; - if (intdata[pointer] == 0) { + intdata.set(pointer, intdata.get(pointer) & ~extdata.get(pointer)); + if (intdata.get(pointer) == 0) { changeNext(pointer, pointer, getNextIdx(pointer)); } @@ -277,22 +271,22 @@ public boolean equals(Object o) { if (o == this) return true; if (!(o instanceof FastSparseSet)) return false; - int[] longdata = ((FastSparseSet)o).getData(); - int[] shortdata = data; + IntArray longdata = ((FastSparseSet)o).getData(); + IntArray shortdata = data; - if (data.length > longdata.length) { + if (data.length() > longdata.length()) { shortdata = longdata; longdata = data; } - for (int i = shortdata.length - 1; i >= 0; i--) { - if (shortdata[i] != longdata[i]) { + for (int i = shortdata.length() - 1; i >= 0; i--) { + if (shortdata.get(i) != longdata.get(i)) { return false; } } - for (int i = longdata.length - 1; i >= shortdata.length; i--) { - if (longdata[i] != 0) { + for (int i = longdata.length() - 1; i >= shortdata.length(); i--) { + if (longdata.get(i) != 0) { return false; } } @@ -303,19 +297,17 @@ public boolean equals(Object o) { public int getCardinality() { boolean found = false; - int[] intdata = data; + IntArray intdata = data; - for (int i = intdata.length - 1; i >= 0; i--) { - int block = intdata[i]; + for (int i = intdata.length() - 1; i >= 0; i--) { + int block = intdata.get(i); if (block != 0) { if (found) { return 2; - } - else { + } else { if ((block & (block - 1)) == 0) { found = true; - } - else { + } else { return 2; } } @@ -326,7 +318,7 @@ public int getCardinality() { } public boolean isEmpty() { - return data.length == 0 || (getNextIdx( 0) == 0 && data[0] == 0); + return data.length() == 0 || (getNextIdx( 0) == 0 && data.get(0) == 0); } @Override @@ -337,9 +329,9 @@ public Iterator iterator() { public Set toPlainSet() { HashSet set = new HashSet<>(); - int[] intdata = data; + IntArray intdata = data; - int size = data.length * 32; + int size = data.length() * 32; if (size > colValuesInternal.size()) { size = colValuesInternal.size(); } @@ -347,7 +339,8 @@ public Set toPlainSet() { for (int i = size - 1; i >= 0; i--) { long index = colValuesInternal.get(i); - if ((intdata[PackedMap.unpackLow(index)] & PackedMap.unpackHigh(index)) != 0) { + int lo = PackedMap.unpackLow(index); + if ((intdata.get(lo) & PackedMap.unpackHigh(index)) != 0) { set.add(colValuesInternal.getKey(i)); } } @@ -359,24 +352,23 @@ public String toString() { return toPlainSet().toString(); } - private int[] getData() { + private IntArray getData() { return data; } - private int[] getNext() { + private IntArray getNext() { return next; } private FastSparseSetFactory getFactory() { return factory; } - } public static final class FastSparseSetIterator implements Iterator { private final PackedMap colValuesInternal; - private final int[] data; - private final int[] next; + private final IntArray data; + private final IntArray next; private final int size; private int pointer = -1; @@ -395,8 +387,8 @@ private int getNextIndex(int index) { int bindex = index >>> 5; int dindex = index & 0x1F; - while (bindex < data.length) { - int block = data[bindex]; + while (bindex < data.length()) { + int block = data.get(bindex); if (block != 0) { block >>>= dindex; @@ -410,7 +402,7 @@ private int getNextIndex(int index) { } dindex = 0; - bindex = next == null ? 0 : next[bindex]; + bindex = next.get(bindex); if (bindex == 0) { break; @@ -445,7 +437,410 @@ public E next() { @Override public void remove() { long index = colValuesInternal.get(pointer); - data[PackedMap.unpackLow(index)] &= ~PackedMap.unpackHigh(index); + int lo = PackedMap.unpackLow(index); + data.set(lo, data.get(lo) & ~PackedMap.unpackHigh(index)); + } + } + + } + + public static final class IntArray { + private ArrayTower tower = ArrayTower.None.INSTANCE; + private int size; + + public IntArray(int size) { + this.size = size; + } + + public void setNone() { + tower = ArrayTower.None.INSTANCE; + } + + public String str() { + return tower.toString(); + } + + public int get(int index) { + return tower.get(index); + } + + public void set(int index, int value) { + if (!tower.canSet(index, value)) { + ArrayTower last = tower; + if (tower instanceof ArrayTower.None) { + if (index == 0 && value == 1) { + tower = new ArrayTower.Ladder(); + } else { + tower = new ArrayTower.Single(index, value); + } + } else if (tower instanceof ArrayTower.Single single) { + if (single.value == value && (single.index == index + 1 || single.index == index - 1)) { + // Same value with multiple indices, use bitset +// BitSet bits = new BitSet(); +// bits.set(single.index); +// tower = new ArrayTower.Bits(value, bits); + + // range + tower = new ArrayTower.Range(value, single.index, single.index); + } else { + // Different values, fall all the way down to array + if (index == single.index || single.value == 0) { + // Size is one, just replace the value in single + tower = new ArrayTower.Single(index, value); + } else { + int[] ints = new int[size]; + ints[single.index] = single.value; + tower = new ArrayTower.Array(ints, 1); + } + } + } +// else if (tower instanceof ArrayTower.Bits bits) { +// int[] ints = new int[size]; +// bits.promote(ints); +// tower = new ArrayTower.Array(ints, bits.set); +// } + else if (tower instanceof ArrayTower.Ladder ladder) { + int[] ints = new int[size]; + ladder.promote(ints); + tower = new ArrayTower.Array(ints, ladder.size + 1); + } else if (tower instanceof ArrayTower.Range range) { + int[] ints = new int[size]; + range.promote(ints); + tower = new ArrayTower.Array(ints, (range.end - range.start) + 1); + } + + ValidationHelper.validateTrue(last != tower, "must have changed"); + } + + tower.set(index, value); + +// if (this.tower instanceof ArrayTower.Bits bits && bits.set == 0) { +// this.tower = ArrayTower.None.INSTANCE; +// } else + if (this.tower instanceof ArrayTower.Array ary) { + if (ary.set == 0) { + this.tower = ArrayTower.None.INSTANCE; + } + } else if (this.tower instanceof ArrayTower.Range range) { + if (range.end < range.start) { + this.tower = ArrayTower.None.INSTANCE; + } else if (range.end == range.start) { + this.tower = new ArrayTower.Single(range.start, range.value); + } + } + } + + public void resize(int newSize) { + if (newSize > size) { + size = newSize; + + if (tower instanceof ArrayTower.Array ary) { + tower = new ArrayTower.Array(Arrays.copyOf(ary.ary, newSize), ary.set); + } + } + } + + public int cardApprox() { + if (this.tower instanceof ArrayTower.None) { + return 0; + } else if (this.tower instanceof ArrayTower.Single) { + return 1; + } else if (this.tower instanceof ArrayTower.Bits bits) { + return Math.min(bits.set, 2); + } else if (this.tower instanceof ArrayTower.Ladder ladder) { + return Math.min(ladder.size + 1, 2); + } else if (this.tower instanceof ArrayTower.Array array) { + return Math.min(array.set, 2); + } + + throw new IllegalStateException("Illegal state!"); + } + + public IntArray copy() { + IntArray next = new IntArray(size); + next.tower = tower.copy(); + + return next; + } + + public int length() { + return size; + } + } + + private sealed interface ArrayTower { + int get(int i); + + default void set(int i, int v) { + if (canSet(i, v)) { + return; + } + + throw new IllegalStateException("Can't set " + i + " " + v); + } + + boolean canSet(int i, int v); + + ArrayTower copy(); + + default void promote(int[] ary) { + for (int i = 0; i < ary.length; i++) { + ary[i] = get(i); + } + } + + record None() implements ArrayTower { + public static final None INSTANCE = new None(); + + @Override + public int get(int i) { + return 0; + } + + @Override + public boolean canSet(int i, int v) { + return v == 0; + } + + @Override + public ArrayTower copy() { + return INSTANCE; + } + + @Override + public String toString() { + return "Nil"; + } + } + + record Single(int index, int value) implements ArrayTower { + + @Override + public int get(int i) { + return i == index ? value : 0; + } + + @Override + public boolean canSet(int i, int v) { + return (i == index && v == value) || (i != index && v == 0); + } + + @Override + public ArrayTower copy() { + return new Single(index, value); + } + + @Override + public String toString() { + return value + "@" + index; + } + } + + final class Ladder implements ArrayTower { + private int size = -1; + + @Override + public int get(int i) { + return i <= size ? i + 1 : 0; + } + + @Override + public void set(int i, int v) { + // Increase ladder size + if (i == size + 1) { + if (v == size + 2) { + size++; + } else if (v == 0) { + size--; + } else { + ValidationHelper.assertTrue(false, "impossible case"); + } + } + // else already in ladder, no change + } + + @Override + public boolean canSet(int i, int v) { + return i == size + 1 && (v == size + 2 || v == 0) // new in ladder + || i <= size && i + 1 == v // already in ladder + ; + } + + @Override + public ArrayTower copy() { + Ladder ladder = new Ladder(); + ladder.size = size; + return ladder; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < size; i++) { + if (i > 0) { + sb.append(","); + } + sb.append(i + 1); + } + return "Ladder:" + sb.toString(); + } + } + + final class Bits implements ArrayTower { + private final int value; + private final BitSet index; + private int set = 0; + + private Bits(int value, BitSet index) { + this.value = value; + this.index = index; + } + + @Override + public int get(int i) { + return index.get(i) ? value : 0; + } + + @Override + public void set(int i, int v) { + ValidationHelper.assertTrue(v == value || v == 0, "must be"); + int old = index.get(i) ? value : 0; + + index.set(i, v == value); + + if (old == 0 && v != 0) { + set++; + } else if (old != 0 && v == 0) { + set--; + } + } + + @Override + public boolean canSet(int i, int v) { + return value == v || v == 0; + } + + @Override + public ArrayTower copy() { + Bits bits = new Bits(value, (BitSet) index.clone()); + bits.set = set; + return bits; + } + + @Override + public String toString() { + return value + "@" + index; + } + } + + final class Range implements ArrayTower { + private final int value; + private int start; + private int end; + + public Range(int value, int start, int end) { + this.value = value; + this.start = start; + this.end = end; + } + + @Override + public int get(int i) { + return i >= start && i <= end ? value : 0; + } + + @Override + public void set(int i, int v) { + if (v == 0) { + // contract range + if (i == start) { + start++; + } else { + end--; + } + } else { + // expand range + if (i == start - 1) { + start--; + } else { + end++; + } + } + } + + @Override + public boolean canSet(int i, int v) { + // TODO: extract common behavior? + if (v == value) { + // expand range + if (i == start - 1) { + return true; + } else if (i == end + 1) { + return true; + } + } else if (v == 0) { + // contract range + if (i == start) { + return true; + } else if (i == end) { + return true; + } + } + + return false; + } + + @Override + public ArrayTower copy() { + return new Range(value, start, end); + } + + @Override + public String toString() { + return value + "@[" + start + "-" + end + "]"; + } + } + + final class Array implements ArrayTower { + private final int[] ary; + private int set; + + private Array(int[] ary, int set) { + this.ary = ary; + this.set = set; + } + + @Override + public int get(int i) { + return ary[i]; + } + + @Override + public void set(int i, int v) { + int old = ary[i]; + ary[i] = v; + + if (old == 0 && v != 0) { + set++; + } else if (old != 0 && v == 0) { + set--; + } + } + + @Override + public boolean canSet(int i, int v) { + return true; + } + + @Override + public ArrayTower copy() { + return new ArrayTower.Array(Arrays.copyOf(ary, ary.length), set); + } + + @Override + public String toString() { + return Arrays.toString(ary); + } } } } diff --git a/src/org/jetbrains/java/decompiler/util/collections/SFormsFastMapDirect.java b/src/org/jetbrains/java/decompiler/util/collections/SFormsFastMapDirect.java index 1136873e49..6b28e5bd3a 100644 --- a/src/org/jetbrains/java/decompiler/util/collections/SFormsFastMapDirect.java +++ b/src/org/jetbrains/java/decompiler/util/collections/SFormsFastMapDirect.java @@ -1,11 +1,13 @@ // Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package org.jetbrains.java.decompiler.util.collections; +import org.jetbrains.java.decompiler.modules.decompiler.ValidationHelper; import org.jetbrains.java.decompiler.modules.decompiler.exps.VarExprent; import org.jetbrains.java.decompiler.modules.decompiler.vars.VarVersionNode; import org.jetbrains.java.decompiler.modules.decompiler.vars.VarVersionPair; import org.jetbrains.java.decompiler.util.InterpreterUtil; import org.jetbrains.java.decompiler.util.collections.FastSparseSetFactory.FastSparseSet; +import org.jetbrains.java.decompiler.util.collections.FastSparseSetFactory.IntArray; import java.util.ArrayList; import java.util.Arrays; @@ -20,9 +22,9 @@ public class SFormsFastMapDirect { private int size; private final FastSparseSetFactory factory; - @SuppressWarnings("unchecked") private final FastSparseSet[][] elements = new FastSparseSet[3][]; + private final FastSparseSetArray[] elements = new FastSparseSetArray[3]; - private final int[][] next = new int[3][]; + private final IntArray[] next = new IntArray[3]; public SFormsFastMapDirect(FastSparseSetFactory factory) { this(true, factory); @@ -32,9 +34,8 @@ private SFormsFastMapDirect(boolean initialize, FastSparseSetFactory fa this.factory = factory; if (initialize) { for (int i = 2; i >= 0; i--) { - @SuppressWarnings("unchecked") FastSparseSet[] empty = FastSparseSet.EMPTY_ARRAY; - elements[i] = empty; - next[i] = InterpreterUtil.EMPTY_INT_ARRAY; + elements[i] = new FastSparseSetArray(0); + next[i] = new FastSparseSetFactory.IntArray(0); } } } @@ -44,35 +45,22 @@ public SFormsFastMapDirect getCopy() { SFormsFastMapDirect map = new SFormsFastMapDirect(false, factory); map.size = size; - FastSparseSet[][] mapelements = map.elements; - int[][] mapnext = map.next; + FastSparseSetArray[] mapelements = map.elements; + IntArray[] mapnext = map.next; for (int i = 2; i >= 0; i--) { - FastSparseSet[] arr = elements[i]; - int length = arr.length; + FastSparseSetArray arr = elements[i]; + int length = arr.length(); if (length > 0) { - int[] arrnext = next[i]; + IntArray arrnext = next[i]; - @SuppressWarnings("unchecked") FastSparseSet[] arrnew = new FastSparseSet[length]; - int[] arrnextnew = Arrays.copyOf(arrnext, length); + mapelements[i] = arr.copy(); + mapnext[i] = arrnext.copy(); - mapelements[i] = arrnew; - mapnext[i] = arrnextnew; - - int pointer = 0; - do { - FastSparseSet set = arr[pointer]; - if (set != null) { - arrnew[pointer] = set.getCopy(); - } - - pointer = arrnext[pointer]; - } - while (pointer != 0); } else { - mapelements[i] = FastSparseSet.EMPTY_ARRAY; - mapnext[i] = InterpreterUtil.EMPTY_INT_ARRAY; + mapelements[i] = new FastSparseSetArray(0); + mapnext[i] = new IntArray(0); } } @@ -96,31 +84,23 @@ public void remove(int key) { } public void removeAllFields() { - FastSparseSet[] arr = elements[2]; - int[] arrnext = next[2]; + FastSparseSetArray arr = elements[2]; + IntArray arrnext = next[2]; - for (int i = arr.length - 1; i >= 0; i--) { - FastSparseSet val = arr[i]; - if (val != null) { - arr[i] = null; - size--; - } - arrnext[i] = 0; - } + size -= arr.cardinality(); + + arrnext.setNone(); + arr.setNone(); } public void removeAllStacks() { - FastSparseSet[] arr = elements[1]; - int[] arrnext = next[1]; + FastSparseSetArray arr = elements[1]; + IntArray arrnext = next[1]; - for (int i = arr.length - 1; i >= 0; i--) { - FastSparseSet val = arr[i]; - if (val != null) { - arr[i] = null; - size--; - } - arrnext[i] = 0; - } + size -= arr.cardinality(); + + arrnext.setNone(); + arr.setNone(); } private void putInternal(final int key, final FastSparseSet value, boolean remove) { @@ -134,33 +114,36 @@ private void putInternal(final int key, final FastSparseSet value, bool ikey -= VarExprent.STACK_BASE; } - FastSparseSet[] arr = elements[index]; - if (ikey >= arr.length) { + FastSparseSetArray arr = elements[index]; + if (ikey >= arr.length()) { if (remove) { return; } else { - arr = ensureCapacity(index, ikey + 1, false); + ensureCapacity(index, ikey + 1, false); } } - FastSparseSet oldval = arr[ikey]; - arr[ikey] = value; + FastSparseSet oldval = arr.get(ikey); + arr.set(ikey, value); - int[] arrnext = next[index]; + IntArray arrnext = next[index]; if (oldval == null && value != null) { size++; - changeNext(arrnext, ikey, arrnext[ikey], ikey); + changeNext(arrnext, ikey, arrnext.get(ikey), ikey); } else if (oldval != null && value == null) { size--; - changeNext(arrnext, ikey, ikey, arrnext[ikey]); + changeNext(arrnext, ikey, ikey, arrnext.get(ikey)); } } - private static void changeNext(int[] arrnext, int key, int oldnext, int newnext) { + private static void changeNext(IntArray arrnext, int key, int oldnext, int newnext) { + if (oldnext == newnext) { + return; + } for (int i = key - 1; i >= 0; i--) { - if (arrnext[i] == oldnext) { - arrnext[i] = newnext; + if (arrnext.get(i) == oldnext) { + arrnext.set(i, newnext); } else { break; } @@ -182,10 +165,10 @@ public FastSparseSet get(int key) { key -= VarExprent.STACK_BASE; } - FastSparseSet[] arr = elements[index]; + FastSparseSetArray arr = elements[index]; - if (key < arr.length) { - return arr[key]; + if (key < arr.length()) { + return arr.get(key); } return null; @@ -194,36 +177,36 @@ public FastSparseSet get(int key) { public void complement(SFormsFastMapDirect map) { for (int i = 2; i >= 0; i--) { - FastSparseSet[] lstOwn = elements[i]; + FastSparseSetArray lstOwn = elements[i]; - if (lstOwn.length == 0) { + if (lstOwn.length() == 0) { continue; } - FastSparseSet[] lstExtern = map.elements[i]; - int[] arrnext = next[i]; + FastSparseSetArray lstExtern = map.elements[i]; + IntArray arrnext = next[i]; int pointer = 0; do { - FastSparseSet first = lstOwn[pointer]; + FastSparseSet first = lstOwn.get(pointer); if (first != null) { - if (pointer >= lstExtern.length) { + if (pointer >= lstExtern.length()) { break; } - FastSparseSet second = lstExtern[pointer]; + FastSparseSet second = lstExtern.get(pointer); if (second != null) { first.complement(second); if (first.isEmpty()) { - lstOwn[pointer] = null; + lstOwn.set(pointer, null); size--; - changeNext(arrnext, pointer, pointer, arrnext[pointer]); + changeNext(arrnext, pointer, pointer, arrnext.get(pointer)); } } } - pointer = arrnext[pointer]; + pointer = arrnext.get(pointer); } while (pointer != 0); } @@ -232,23 +215,23 @@ public void complement(SFormsFastMapDirect map) { public void intersection(SFormsFastMapDirect map) { for (int i = 2; i >= 0; i--) { - FastSparseSet[] lstOwn = elements[i]; + FastSparseSetArray lstOwn = elements[i]; - if (lstOwn.length == 0) { + if (lstOwn.length() == 0) { continue; } - FastSparseSet[] lstExtern = map.elements[i]; - int[] arrnext = next[i]; + FastSparseSetArray lstExtern = map.elements[i]; + IntArray arrnext = next[i]; int pointer = 0; do { - FastSparseSet first = lstOwn[pointer]; + FastSparseSet first = lstOwn.get(pointer); if (first != null) { FastSparseSet second = null; - if (pointer < lstExtern.length) { - second = lstExtern[pointer]; + if (pointer < lstExtern.length()) { + second = lstExtern.get(pointer); } if (second != null) { @@ -256,13 +239,13 @@ public void intersection(SFormsFastMapDirect map) { } if (second == null || first.isEmpty()) { - lstOwn[pointer] = null; + lstOwn.set(pointer, null); size--; - changeNext(arrnext, pointer, pointer, arrnext[pointer]); + changeNext(arrnext, pointer, pointer, arrnext.get(pointer)); } } - pointer = arrnext[pointer]; + pointer = arrnext.get(pointer); } while (pointer != 0); } @@ -271,39 +254,40 @@ public void intersection(SFormsFastMapDirect map) { public void union(SFormsFastMapDirect map) { for (int i = 2; i >= 0; i--) { - FastSparseSet[] lstExtern = map.elements[i]; + FastSparseSetArray lstExtern = map.elements[i]; - if (lstExtern.length == 0) { + if (lstExtern.length() == 0) { continue; } - FastSparseSet[] lstOwn = elements[i]; - int[] arrnext = next[i]; - int[] arrnextExtern = map.next[i]; + FastSparseSetArray lstOwn = elements[i]; + + IntArray arrnext = next[i]; + IntArray arrnextExtern = map.next[i]; int pointer = 0; do { - if (pointer >= lstOwn.length) { - lstOwn = ensureCapacity(i, lstExtern.length, true); + if (pointer >= lstOwn.length()) { + ensureCapacity(i, lstExtern.length(), true); arrnext = next[i]; } - FastSparseSet second = lstExtern[pointer]; + FastSparseSet second = lstExtern.get(pointer); if (second != null) { - FastSparseSet first = lstOwn[pointer]; + FastSparseSet first = lstOwn.get(pointer); if (first == null) { - lstOwn[pointer] = second.getCopy(); + lstOwn.set(pointer, second.getCopy()); size++; - changeNext(arrnext, pointer, arrnext[pointer], pointer); + changeNext(arrnext, pointer, arrnext.get(pointer), pointer); } else { first.union(second); } } - pointer = arrnextExtern[pointer]; + pointer = arrnextExtern.get(pointer); } while (pointer != 0); } @@ -338,7 +322,8 @@ public List>> entryList() { for (int i = 2; i >= 0; i--) { int ikey = 0; - for (final FastSparseSet ent : elements[i]) { + for (int j = 0; j < elements[i].length(); j++) { + final FastSparseSet ent = elements[i].get(j); if (ent != null) { final int key = i == 0 ? ikey : (i == 1 ? ikey + VarExprent.STACK_BASE : -ikey); @@ -368,29 +353,21 @@ public FastSparseSet setValue(FastSparseSet newvalue) { return list; } - private FastSparseSet[] ensureCapacity(int index, int size, boolean exact) { + private void ensureCapacity(int index, int size, boolean exact) { - FastSparseSet[] arr = elements[index]; - int[] arrnext = next[index]; + FastSparseSetArray arr = elements[index]; + IntArray arrnext = next[index]; int minsize = size; if (!exact) { - minsize = 2 * arr.length / 3 + 1; + minsize = 2 * arr.length() / 3 + 1; if (size > minsize) { minsize = size; } } - @SuppressWarnings("unchecked") FastSparseSet[] arrnew = new FastSparseSet[minsize]; - System.arraycopy(arr, 0, arrnew, 0, arr.length); - - int[] arrnextnew = new int[minsize]; - System.arraycopy(arrnext, 0, arrnextnew, 0, arrnext.length); - - elements[index] = arrnew; - next[index] = arrnextnew; - - return arrnew; + arr.resize(minsize); + arrnext.resize(minsize); } public void setCurrentVar(int var, int version) { @@ -414,4 +391,192 @@ public void setCurrentVar(VarVersionPair varExprent) { public FastSparseSet get(VarExprent varExprent) { return this.get(varExprent.getIndex()); } + + private static class FastSparseSetArray { + private ArrayTower tower = ArrayTower.None.INSTANCE; + private int size; + + public FastSparseSetArray(int size) { + this.size = size; + } + + public FastSparseSet get(int index) { + return tower.get(index); + } + + public void setNone() { + tower = ArrayTower.None.INSTANCE; + } + + public void set(int index, FastSparseSet value) { + if (!tower.canSet(index, value)) { + ArrayTower last = tower; + if (tower instanceof ArrayTower.None) { + tower = new ArrayTower.Single(index, value); + } else if (tower instanceof ArrayTower.Single single) { + // Different values, fall all the way down to array + if (index == single.index || single.value == null) { + // Size is one, just replace the value in single + tower = new ArrayTower.Single(index, value); + } else { + FastSparseSet[] ints = new FastSparseSet[size]; + ints[single.index] = single.value; + tower = new ArrayTower.Array(ints, 1); + } + } + + ValidationHelper.validateTrue(last != tower, "must have changed"); + } + + tower.set(index, value); + + if (tower instanceof ArrayTower.Array ary && ary.set == 0) { + tower = ArrayTower.None.INSTANCE; + } + } + + public void resize(int newSize) { + if (newSize > size) { + size = newSize; + + if (tower instanceof ArrayTower.Array ary) { + tower = new ArrayTower.Array(Arrays.copyOf(ary.ary, newSize), ary.set); + } + } + } + + public int cardinality() { + if (this.tower instanceof ArrayTower.None) { + return 0; + } else if (this.tower instanceof ArrayTower.Single) { + return 1; + } else if (this.tower instanceof ArrayTower.Array ary) { + return ary.set; + } + + throw new IllegalStateException("illegal state!"); + } + + public FastSparseSetArray copy() { + FastSparseSetArray next = new FastSparseSetArray(size); + next.tower = tower.copy(); + + return next; + } + + public int length() { + return size; + } + } + + private sealed interface ArrayTower { + FastSparseSet get(int i); + + default void set(int i, FastSparseSet v) { + if (canSet(i, v)) { + return; + } + + throw new IllegalStateException("Can't set " + i + " " + v); + } + + boolean canSet(int i, FastSparseSet v); + + ArrayTower copy(); + + record None() implements ArrayTower { + public static final None INSTANCE = new None(); + + @Override + public FastSparseSet get(int i) { + return null; + } + + @Override + public boolean canSet(int i, FastSparseSet v) { + return v == null; + } + + @Override + public ArrayTower copy() { + return INSTANCE; + } + + @Override + public String toString() { + return "Nil"; + } + } + + record Single(int index, FastSparseSet value) implements ArrayTower { + + @Override + public FastSparseSet get(int i) { + return i == index ? value : null; + } + + @Override + public boolean canSet(int i, FastSparseSet v) { + return (i == index && v == value) || (i != index && v == null); + } + + @Override + public ArrayTower copy() { + return new Single(index, value.getCopy()); + } + + @Override + public String toString() { + return value + "@" + index; + } + } + + final class Array implements ArrayTower { + private final FastSparseSet[] ary; + private int set; + + Array(FastSparseSet[] ary, int set) { + this.ary = ary; + this.set = set; + } + + @Override + public FastSparseSet get(int i) { + return ary[i]; + } + + @Override + public void set(int i, FastSparseSet v) { + FastSparseSet old = ary[i]; + ary[i] = v; + + if (old == null && v != null) { + set++; + } else if (old != null && v == null) { + set--; + } + } + + @Override + public boolean canSet(int i, FastSparseSet v) { + return true; + } + + @Override + public ArrayTower copy() { + FastSparseSet[] cpy = Arrays.copyOf(ary, ary.length); + for (int i = 0; i < cpy.length; i++) { + FastSparseSet v = cpy[i]; + cpy[i] = v == null ? null : v.getCopy(); + } + + return new ArrayTower.Array(cpy, set); + } + + @Override + public String toString() { + return Arrays.toString(ary); + } + } + } } \ No newline at end of file