From 4eceb95409e844fdc33c9c706e1dc307bfd40303 Mon Sep 17 00:00:00 2001 From: Yohann Roussel Date: Wed, 19 Mar 2014 16:25:37 +0100 Subject: Initial Jack import. Change-Id: I953cf0a520195a7187d791b2885848ad0d5a9b43 --- dx/src/Android.mk | 34 + dx/src/com/android/jack/dx/Version.java | 25 + dx/src/com/android/jack/dx/dex/DexFormat.java | 98 + dx/src/com/android/jack/dx/dex/DexOptions.java | 35 + dx/src/com/android/jack/dx/dex/SizeOf.java | 110 ++ .../com/android/jack/dx/dex/TableOfContents.java | 239 +++ dx/src/com/android/jack/dx/dex/code/ArrayData.java | 200 ++ .../android/jack/dx/dex/code/BlockAddresses.java | 143 ++ .../com/android/jack/dx/dex/code/CatchBuilder.java | 48 + .../android/jack/dx/dex/code/CatchHandlerList.java | 238 +++ .../com/android/jack/dx/dex/code/CatchTable.java | 192 ++ .../com/android/jack/dx/dex/code/CodeAddress.java | 91 + dx/src/com/android/jack/dx/dex/code/CstInsn.java | 205 ++ dx/src/com/android/jack/dx/dex/code/DalvCode.java | 232 +++ dx/src/com/android/jack/dx/dex/code/DalvInsn.java | 450 +++++ .../com/android/jack/dx/dex/code/DalvInsnList.java | 269 +++ dx/src/com/android/jack/dx/dex/code/Dop.java | 173 ++ dx/src/com/android/jack/dx/dex/code/Dops.java | 1229 ++++++++++++ .../android/jack/dx/dex/code/FixedSizeInsn.java | 73 + .../jack/dx/dex/code/HighRegisterPrefix.java | 147 ++ .../com/android/jack/dx/dex/code/InsnFormat.java | 714 +++++++ dx/src/com/android/jack/dx/dex/code/LocalEnd.java | 90 + dx/src/com/android/jack/dx/dex/code/LocalList.java | 948 +++++++++ .../android/jack/dx/dex/code/LocalSnapshot.java | 96 + .../com/android/jack/dx/dex/code/LocalStart.java | 98 + dx/src/com/android/jack/dx/dex/code/OddSpacer.java | 76 + .../android/jack/dx/dex/code/OutputCollector.java | 121 ++ .../android/jack/dx/dex/code/OutputFinisher.java | 784 ++++++++ .../com/android/jack/dx/dex/code/PositionList.java | 192 ++ dx/src/com/android/jack/dx/dex/code/RopToDop.java | 587 ++++++ .../android/jack/dx/dex/code/RopTranslator.java | 882 +++++++++ .../com/android/jack/dx/dex/code/SimpleInsn.java | 59 + .../android/jack/dx/dex/code/StdCatchBuilder.java | 316 +++ .../com/android/jack/dx/dex/code/SwitchData.java | 258 +++ .../com/android/jack/dx/dex/code/TargetInsn.java | 132 ++ .../android/jack/dx/dex/code/VariableSizeInsn.java | 49 + .../com/android/jack/dx/dex/code/ZeroSizeInsn.java | 62 + .../com/android/jack/dx/dex/code/form/Form10t.java | 86 + .../com/android/jack/dx/dex/code/form/Form10x.java | 72 + .../com/android/jack/dx/dex/code/form/Form11n.java | 110 ++ .../com/android/jack/dx/dex/code/form/Form11x.java | 88 + .../com/android/jack/dx/dex/code/form/Form12x.java | 162 ++ .../com/android/jack/dx/dex/code/form/Form20t.java | 86 + .../com/android/jack/dx/dex/code/form/Form21c.java | 149 ++ .../com/android/jack/dx/dex/code/form/Form21h.java | 126 ++ .../com/android/jack/dx/dex/code/form/Form21s.java | 110 ++ .../com/android/jack/dx/dex/code/form/Form21t.java | 106 + .../com/android/jack/dx/dex/code/form/Form22b.java | 113 ++ .../com/android/jack/dx/dex/code/form/Form22c.java | 115 ++ .../com/android/jack/dx/dex/code/form/Form22s.java | 114 ++ .../com/android/jack/dx/dex/code/form/Form22t.java | 110 ++ .../com/android/jack/dx/dex/code/form/Form22x.java | 93 + .../com/android/jack/dx/dex/code/form/Form23x.java | 96 + .../com/android/jack/dx/dex/code/form/Form30t.java | 82 + .../com/android/jack/dx/dex/code/form/Form31c.java | 142 ++ .../com/android/jack/dx/dex/code/form/Form31i.java | 106 + .../com/android/jack/dx/dex/code/form/Form31t.java | 100 + .../com/android/jack/dx/dex/code/form/Form32x.java | 94 + .../com/android/jack/dx/dex/code/form/Form35c.java | 211 ++ .../com/android/jack/dx/dex/code/form/Form3rc.java | 106 + .../com/android/jack/dx/dex/code/form/Form51l.java | 103 + .../jack/dx/dex/code/form/SpecialFormat.java | 72 + .../android/jack/dx/dex/file/AnnotationItem.java | 218 ++ .../jack/dx/dex/file/AnnotationSetItem.java | 157 ++ .../jack/dx/dex/file/AnnotationSetRefItem.java | 80 + .../android/jack/dx/dex/file/AnnotationUtils.java | 252 +++ .../jack/dx/dex/file/AnnotationsDirectoryItem.java | 385 ++++ .../com/android/jack/dx/dex/file/CatchStructs.java | 315 +++ .../android/jack/dx/dex/file/ClassDataItem.java | 426 ++++ .../com/android/jack/dx/dex/file/ClassDefItem.java | 408 ++++ .../android/jack/dx/dex/file/ClassDefsSection.java | 187 ++ dx/src/com/android/jack/dx/dex/file/Code.java | 24 + dx/src/com/android/jack/dx/dex/file/CodeItem.java | 324 +++ .../jack/dx/dex/file/DebugInfoConstants.java | 154 ++ .../android/jack/dx/dex/file/DebugInfoDecoder.java | 597 ++++++ .../android/jack/dx/dex/file/DebugInfoEncoder.java | 920 +++++++++ .../android/jack/dx/dex/file/DebugInfoItem.java | 194 ++ dx/src/com/android/jack/dx/dex/file/DexFile.java | 678 +++++++ .../android/jack/dx/dex/file/EncodedArrayItem.java | 122 ++ .../com/android/jack/dx/dex/file/EncodedField.java | 154 ++ .../android/jack/dx/dex/file/EncodedMember.java | 86 + .../android/jack/dx/dex/file/EncodedMethod.java | 191 ++ .../jack/dx/dex/file/FieldAnnotationStruct.java | 122 ++ .../com/android/jack/dx/dex/file/FieldIdItem.java | 70 + .../android/jack/dx/dex/file/FieldIdsSection.java | 137 ++ .../com/android/jack/dx/dex/file/HeaderItem.java | 115 ++ .../android/jack/dx/dex/file/HeaderSection.java | 63 + dx/src/com/android/jack/dx/dex/file/IdItem.java | 61 + .../android/jack/dx/dex/file/ImportedCodeItem.java | 400 ++++ .../jack/dx/dex/file/ImportedDebugInfoItem.java | 193 ++ .../com/android/jack/dx/dex/file/IndexedItem.java | 81 + dx/src/com/android/jack/dx/dex/file/Item.java | 80 + dx/src/com/android/jack/dx/dex/file/ItemType.java | 97 + dx/src/com/android/jack/dx/dex/file/MapItem.java | 235 +++ .../com/android/jack/dx/dex/file/MemberIdItem.java | 109 + .../android/jack/dx/dex/file/MemberIdsSection.java | 80 + .../jack/dx/dex/file/MethodAnnotationStruct.java | 122 ++ .../com/android/jack/dx/dex/file/MethodIdItem.java | 70 + .../android/jack/dx/dex/file/MethodIdsSection.java | 137 ++ .../android/jack/dx/dex/file/MixedItemSection.java | 362 ++++ .../android/jack/dx/dex/file/OffsettedItem.java | 314 +++ .../dx/dex/file/ParameterAnnotationStruct.java | 161 ++ .../com/android/jack/dx/dex/file/ProtoIdItem.java | 159 ++ .../android/jack/dx/dex/file/ProtoIdsSection.java | 140 ++ dx/src/com/android/jack/dx/dex/file/Section.java | 287 +++ .../com/android/jack/dx/dex/file/Statistics.java | 195 ++ .../android/jack/dx/dex/file/StringDataItem.java | 99 + .../com/android/jack/dx/dex/file/StringIdItem.java | 126 ++ .../android/jack/dx/dex/file/StringIdsSection.java | 182 ++ .../com/android/jack/dx/dex/file/TypeIdItem.java | 70 + .../android/jack/dx/dex/file/TypeIdsSection.java | 192 ++ .../com/android/jack/dx/dex/file/TypeListItem.java | 122 ++ .../jack/dx/dex/file/UniformItemSection.java | 112 ++ .../android/jack/dx/dex/file/UniformListItem.java | 216 ++ .../com/android/jack/dx/dex/file/ValueEncoder.java | 528 +++++ dx/src/com/android/jack/dx/io/Annotation.java | 104 + dx/src/com/android/jack/dx/io/ClassData.java | 104 + dx/src/com/android/jack/dx/io/ClassDef.java | 102 + dx/src/com/android/jack/dx/io/Code.java | 124 ++ dx/src/com/android/jack/dx/io/CodeReader.java | 121 ++ dx/src/com/android/jack/dx/io/DexBuffer.java | 710 +++++++ dx/src/com/android/jack/dx/io/DexHasher.java | 75 + dx/src/com/android/jack/dx/io/DexIndexPrinter.java | 125 ++ dx/src/com/android/jack/dx/io/EncodedValue.java | 57 + .../com/android/jack/dx/io/EncodedValueReader.java | 146 ++ dx/src/com/android/jack/dx/io/FieldId.java | 68 + dx/src/com/android/jack/dx/io/IndexType.java | 52 + dx/src/com/android/jack/dx/io/MethodId.java | 70 + dx/src/com/android/jack/dx/io/OpcodeInfo.java | 1265 ++++++++++++ dx/src/com/android/jack/dx/io/Opcodes.java | 346 ++++ dx/src/com/android/jack/dx/io/ProtoId.java | 68 + .../jack/dx/io/instructions/AddressMap.java | 52 + .../jack/dx/io/instructions/BaseCodeCursor.java | 61 + .../jack/dx/io/instructions/CodeCursor.java | 47 + .../android/jack/dx/io/instructions/CodeInput.java | 45 + .../jack/dx/io/instructions/CodeOutput.java | 77 + .../dx/io/instructions/DecodedInstruction.java | 479 +++++ .../FillArrayDataPayloadDecodedInstruction.java | 100 + .../FiveRegisterDecodedInstruction.java | 91 + .../FourRegisterDecodedInstruction.java | 82 + .../jack/dx/io/instructions/InstructionCodec.java | 962 +++++++++ .../OneRegisterDecodedInstruction.java | 55 + .../PackedSwitchPayloadDecodedInstruction.java | 62 + .../RegisterRangeDecodedInstruction.java | 60 + .../dx/io/instructions/ShortArrayCodeInput.java | 73 + .../dx/io/instructions/ShortArrayCodeOutput.java | 146 ++ .../SparseSwitchPayloadDecodedInstruction.java | 66 + .../ThreeRegisterDecodedInstruction.java | 73 + .../TwoRegisterDecodedInstruction.java | 64 + .../ZeroRegisterDecodedInstruction.java | 44 + .../com/android/jack/dx/merge/CollisionPolicy.java | 34 + dx/src/com/android/jack/dx/merge/DexMerger.java | 1086 ++++++++++ dx/src/com/android/jack/dx/merge/IndexMap.java | 307 +++ .../jack/dx/merge/InstructionTransformer.java | 112 ++ dx/src/com/android/jack/dx/merge/SortableType.java | 106 + dx/src/com/android/jack/dx/merge/TypeList.java | 58 + .../android/jack/dx/rop/annotation/Annotation.java | 224 +++ .../dx/rop/annotation/AnnotationVisibility.java | 46 + .../jack/dx/rop/annotation/Annotations.java | 213 ++ .../jack/dx/rop/annotation/AnnotationsList.java | 91 + .../jack/dx/rop/annotation/NameValuePair.java | 106 + .../com/android/jack/dx/rop/code/AccessFlags.java | 406 ++++ .../com/android/jack/dx/rop/code/BasicBlock.java | 281 +++ .../android/jack/dx/rop/code/BasicBlockList.java | 396 ++++ .../dx/rop/code/ConservativeTranslationAdvice.java | 52 + dx/src/com/android/jack/dx/rop/code/CstInsn.java | 74 + .../jack/dx/rop/code/DexTranslationAdvice.java | 130 ++ .../com/android/jack/dx/rop/code/Exceptions.java | 133 ++ .../jack/dx/rop/code/FillArrayDataInsn.java | 116 ++ dx/src/com/android/jack/dx/rop/code/Insn.java | 456 +++++ dx/src/com/android/jack/dx/rop/code/InsnList.java | 130 ++ dx/src/com/android/jack/dx/rop/code/LocalItem.java | 189 ++ .../jack/dx/rop/code/LocalVariableExtractor.java | 191 ++ .../jack/dx/rop/code/LocalVariableInfo.java | 254 +++ .../com/android/jack/dx/rop/code/PlainCstInsn.java | 87 + dx/src/com/android/jack/dx/rop/code/PlainInsn.java | 157 ++ dx/src/com/android/jack/dx/rop/code/RegOps.java | 399 ++++ .../com/android/jack/dx/rop/code/RegisterSpec.java | 657 ++++++ .../android/jack/dx/rop/code/RegisterSpecList.java | 409 ++++ .../android/jack/dx/rop/code/RegisterSpecSet.java | 396 ++++ dx/src/com/android/jack/dx/rop/code/Rop.java | 407 ++++ dx/src/com/android/jack/dx/rop/code/RopMethod.java | 207 ++ dx/src/com/android/jack/dx/rop/code/Rops.java | 2090 ++++++++++++++++++++ .../android/jack/dx/rop/code/SourcePosition.java | 168 ++ .../com/android/jack/dx/rop/code/SwitchInsn.java | 119 ++ .../android/jack/dx/rop/code/ThrowingCstInsn.java | 110 ++ .../com/android/jack/dx/rop/code/ThrowingInsn.java | 120 ++ .../jack/dx/rop/code/TranslationAdvice.java | 62 + dx/src/com/android/jack/dx/rop/code/package.html | 8 + dx/src/com/android/jack/dx/rop/cst/Constant.java | 68 + .../com/android/jack/dx/rop/cst/ConstantPool.java | 70 + .../com/android/jack/dx/rop/cst/CstAnnotation.java | 96 + dx/src/com/android/jack/dx/rop/cst/CstArray.java | 159 ++ .../android/jack/dx/rop/cst/CstBaseMethodRef.java | 151 ++ dx/src/com/android/jack/dx/rop/cst/CstBoolean.java | 99 + dx/src/com/android/jack/dx/rop/cst/CstByte.java | 99 + dx/src/com/android/jack/dx/rop/cst/CstChar.java | 99 + dx/src/com/android/jack/dx/rop/cst/CstDouble.java | 90 + dx/src/com/android/jack/dx/rop/cst/CstEnumRef.java | 68 + .../com/android/jack/dx/rop/cst/CstFieldRef.java | 79 + dx/src/com/android/jack/dx/rop/cst/CstFloat.java | 91 + .../com/android/jack/dx/rop/cst/CstIndexMap.java | 198 ++ dx/src/com/android/jack/dx/rop/cst/CstInteger.java | 116 ++ .../jack/dx/rop/cst/CstInterfaceMethodRef.java | 60 + .../com/android/jack/dx/rop/cst/CstKnownNull.java | 110 ++ .../com/android/jack/dx/rop/cst/CstLiteral32.java | 87 + .../com/android/jack/dx/rop/cst/CstLiteral64.java | 87 + .../android/jack/dx/rop/cst/CstLiteralBits.java | 82 + dx/src/com/android/jack/dx/rop/cst/CstLong.java | 87 + .../com/android/jack/dx/rop/cst/CstMemberRef.java | 122 ++ .../com/android/jack/dx/rop/cst/CstMethodRef.java | 39 + dx/src/com/android/jack/dx/rop/cst/CstNat.java | 170 ++ dx/src/com/android/jack/dx/rop/cst/CstShort.java | 100 + dx/src/com/android/jack/dx/rop/cst/CstString.java | 375 ++++ dx/src/com/android/jack/dx/rop/cst/CstType.java | 250 +++ .../android/jack/dx/rop/cst/StdConstantPool.java | 139 ++ .../com/android/jack/dx/rop/cst/TypedConstant.java | 49 + dx/src/com/android/jack/dx/rop/cst/Zeroes.java | 55 + dx/src/com/android/jack/dx/rop/cst/package.html | 9 + dx/src/com/android/jack/dx/rop/package-info.java | 200 ++ dx/src/com/android/jack/dx/rop/type/Prototype.java | 400 ++++ .../com/android/jack/dx/rop/type/StdTypeList.java | 407 ++++ dx/src/com/android/jack/dx/rop/type/Type.java | 862 ++++++++ .../com/android/jack/dx/rop/type/TypeBearer.java | 74 + dx/src/com/android/jack/dx/rop/type/TypeList.java | 69 + dx/src/com/android/jack/dx/rop/type/package.html | 8 + .../android/jack/dx/ssa/BasicRegisterMapper.java | 129 ++ dx/src/com/android/jack/dx/ssa/ConstCollector.java | 400 ++++ .../com/android/jack/dx/ssa/DeadCodeRemover.java | 272 +++ dx/src/com/android/jack/dx/ssa/DomFront.java | 204 ++ dx/src/com/android/jack/dx/ssa/Dominators.java | 285 +++ dx/src/com/android/jack/dx/ssa/EscapeAnalysis.java | 843 ++++++++ .../jack/dx/ssa/InterferenceRegisterMapper.java | 164 ++ .../com/android/jack/dx/ssa/LiteralOpUpgrader.java | 207 ++ .../jack/dx/ssa/LocalVariableExtractor.java | 208 ++ .../com/android/jack/dx/ssa/LocalVariableInfo.java | 251 +++ .../com/android/jack/dx/ssa/MoveParamCombiner.java | 156 ++ dx/src/com/android/jack/dx/ssa/NormalSsaInsn.java | 236 +++ dx/src/com/android/jack/dx/ssa/Optimizer.java | 257 +++ dx/src/com/android/jack/dx/ssa/PhiInsn.java | 398 ++++ .../com/android/jack/dx/ssa/PhiTypeResolver.java | 263 +++ dx/src/com/android/jack/dx/ssa/RegisterMapper.java | 61 + dx/src/com/android/jack/dx/ssa/SCCP.java | 697 +++++++ dx/src/com/android/jack/dx/ssa/SetFactory.java | 95 + dx/src/com/android/jack/dx/ssa/SsaBasicBlock.java | 1032 ++++++++++ dx/src/com/android/jack/dx/ssa/SsaConverter.java | 391 ++++ dx/src/com/android/jack/dx/ssa/SsaInsn.java | 289 +++ dx/src/com/android/jack/dx/ssa/SsaMethod.java | 873 ++++++++ dx/src/com/android/jack/dx/ssa/SsaRenamer.java | 662 +++++++ .../jack/dx/ssa/back/FirstFitAllocator.java | 151 ++ .../ssa/back/FirstFitLocalCombiningAllocator.java | 1139 +++++++++++ .../jack/dx/ssa/back/IdenticalBlockCombiner.java | 182 ++ .../jack/dx/ssa/back/InterferenceGraph.java | 113 ++ .../android/jack/dx/ssa/back/LivenessAnalyzer.java | 287 +++ .../jack/dx/ssa/back/NullRegisterAllocator.java | 58 + .../jack/dx/ssa/back/RegisterAllocator.java | 197 ++ dx/src/com/android/jack/dx/ssa/back/SsaToRop.java | 380 ++++ dx/src/com/android/jack/dx/ssa/package-info.java | 103 + .../com/android/jack/dx/util/AnnotatedOutput.java | 79 + dx/src/com/android/jack/dx/util/BitIntSet.java | 145 ++ dx/src/com/android/jack/dx/util/Bits.java | 236 +++ dx/src/com/android/jack/dx/util/ByteArray.java | 361 ++++ .../jack/dx/util/ByteArrayAnnotatedOutput.java | 632 ++++++ .../android/jack/dx/util/ByteArrayByteInput.java | 31 + dx/src/com/android/jack/dx/util/ByteInput.java | 30 + dx/src/com/android/jack/dx/util/ByteOutput.java | 30 + dx/src/com/android/jack/dx/util/DexException.java | 31 + .../android/jack/dx/util/ExceptionWithContext.java | 149 ++ dx/src/com/android/jack/dx/util/FileUtils.java | 101 + dx/src/com/android/jack/dx/util/FixedSizeList.java | 276 +++ dx/src/com/android/jack/dx/util/Hex.java | 303 +++ dx/src/com/android/jack/dx/util/HexParser.java | 145 ++ .../com/android/jack/dx/util/IndentingWriter.java | 169 ++ dx/src/com/android/jack/dx/util/IntIterator.java | 38 + dx/src/com/android/jack/dx/util/IntList.java | 453 +++++ dx/src/com/android/jack/dx/util/IntSet.java | 67 + dx/src/com/android/jack/dx/util/LabeledItem.java | 30 + dx/src/com/android/jack/dx/util/LabeledList.java | 187 ++ dx/src/com/android/jack/dx/util/Leb128Utils.java | 162 ++ dx/src/com/android/jack/dx/util/ListIntSet.java | 132 ++ .../android/jack/dx/util/MutabilityControl.java | 89 + .../android/jack/dx/util/MutabilityException.java | 35 + dx/src/com/android/jack/dx/util/Mutf8.java | 114 ++ dx/src/com/android/jack/dx/util/Output.java | 129 ++ dx/src/com/android/jack/dx/util/ToHuman.java | 31 + .../com/android/jack/dx/util/TwoColumnOutput.java | 254 +++ dx/src/com/android/jack/dx/util/Uint.java | 32 + dx/src/com/android/jack/dx/util/Unsigned.java | 42 + dx/src/com/android/jack/dx/util/Warning.java | 31 + dx/src/com/android/jack/dx/util/Writers.java | 48 + dx/src/com/android/jack/dx/util/package.html | 3 + 291 files changed, 61421 insertions(+) create mode 100644 dx/src/Android.mk create mode 100644 dx/src/com/android/jack/dx/Version.java create mode 100644 dx/src/com/android/jack/dx/dex/DexFormat.java create mode 100644 dx/src/com/android/jack/dx/dex/DexOptions.java create mode 100644 dx/src/com/android/jack/dx/dex/SizeOf.java create mode 100644 dx/src/com/android/jack/dx/dex/TableOfContents.java create mode 100644 dx/src/com/android/jack/dx/dex/code/ArrayData.java create mode 100644 dx/src/com/android/jack/dx/dex/code/BlockAddresses.java create mode 100644 dx/src/com/android/jack/dx/dex/code/CatchBuilder.java create mode 100644 dx/src/com/android/jack/dx/dex/code/CatchHandlerList.java create mode 100644 dx/src/com/android/jack/dx/dex/code/CatchTable.java create mode 100644 dx/src/com/android/jack/dx/dex/code/CodeAddress.java create mode 100644 dx/src/com/android/jack/dx/dex/code/CstInsn.java create mode 100644 dx/src/com/android/jack/dx/dex/code/DalvCode.java create mode 100644 dx/src/com/android/jack/dx/dex/code/DalvInsn.java create mode 100644 dx/src/com/android/jack/dx/dex/code/DalvInsnList.java create mode 100644 dx/src/com/android/jack/dx/dex/code/Dop.java create mode 100644 dx/src/com/android/jack/dx/dex/code/Dops.java create mode 100644 dx/src/com/android/jack/dx/dex/code/FixedSizeInsn.java create mode 100644 dx/src/com/android/jack/dx/dex/code/HighRegisterPrefix.java create mode 100644 dx/src/com/android/jack/dx/dex/code/InsnFormat.java create mode 100644 dx/src/com/android/jack/dx/dex/code/LocalEnd.java create mode 100644 dx/src/com/android/jack/dx/dex/code/LocalList.java create mode 100644 dx/src/com/android/jack/dx/dex/code/LocalSnapshot.java create mode 100644 dx/src/com/android/jack/dx/dex/code/LocalStart.java create mode 100644 dx/src/com/android/jack/dx/dex/code/OddSpacer.java create mode 100644 dx/src/com/android/jack/dx/dex/code/OutputCollector.java create mode 100644 dx/src/com/android/jack/dx/dex/code/OutputFinisher.java create mode 100644 dx/src/com/android/jack/dx/dex/code/PositionList.java create mode 100644 dx/src/com/android/jack/dx/dex/code/RopToDop.java create mode 100644 dx/src/com/android/jack/dx/dex/code/RopTranslator.java create mode 100644 dx/src/com/android/jack/dx/dex/code/SimpleInsn.java create mode 100644 dx/src/com/android/jack/dx/dex/code/StdCatchBuilder.java create mode 100644 dx/src/com/android/jack/dx/dex/code/SwitchData.java create mode 100644 dx/src/com/android/jack/dx/dex/code/TargetInsn.java create mode 100644 dx/src/com/android/jack/dx/dex/code/VariableSizeInsn.java create mode 100644 dx/src/com/android/jack/dx/dex/code/ZeroSizeInsn.java create mode 100644 dx/src/com/android/jack/dx/dex/code/form/Form10t.java create mode 100644 dx/src/com/android/jack/dx/dex/code/form/Form10x.java create mode 100644 dx/src/com/android/jack/dx/dex/code/form/Form11n.java create mode 100644 dx/src/com/android/jack/dx/dex/code/form/Form11x.java create mode 100644 dx/src/com/android/jack/dx/dex/code/form/Form12x.java create mode 100644 dx/src/com/android/jack/dx/dex/code/form/Form20t.java create mode 100644 dx/src/com/android/jack/dx/dex/code/form/Form21c.java create mode 100644 dx/src/com/android/jack/dx/dex/code/form/Form21h.java create mode 100644 dx/src/com/android/jack/dx/dex/code/form/Form21s.java create mode 100644 dx/src/com/android/jack/dx/dex/code/form/Form21t.java create mode 100644 dx/src/com/android/jack/dx/dex/code/form/Form22b.java create mode 100644 dx/src/com/android/jack/dx/dex/code/form/Form22c.java create mode 100644 dx/src/com/android/jack/dx/dex/code/form/Form22s.java create mode 100644 dx/src/com/android/jack/dx/dex/code/form/Form22t.java create mode 100644 dx/src/com/android/jack/dx/dex/code/form/Form22x.java create mode 100644 dx/src/com/android/jack/dx/dex/code/form/Form23x.java create mode 100644 dx/src/com/android/jack/dx/dex/code/form/Form30t.java create mode 100644 dx/src/com/android/jack/dx/dex/code/form/Form31c.java create mode 100644 dx/src/com/android/jack/dx/dex/code/form/Form31i.java create mode 100644 dx/src/com/android/jack/dx/dex/code/form/Form31t.java create mode 100644 dx/src/com/android/jack/dx/dex/code/form/Form32x.java create mode 100644 dx/src/com/android/jack/dx/dex/code/form/Form35c.java create mode 100644 dx/src/com/android/jack/dx/dex/code/form/Form3rc.java create mode 100644 dx/src/com/android/jack/dx/dex/code/form/Form51l.java create mode 100644 dx/src/com/android/jack/dx/dex/code/form/SpecialFormat.java create mode 100644 dx/src/com/android/jack/dx/dex/file/AnnotationItem.java create mode 100644 dx/src/com/android/jack/dx/dex/file/AnnotationSetItem.java create mode 100644 dx/src/com/android/jack/dx/dex/file/AnnotationSetRefItem.java create mode 100644 dx/src/com/android/jack/dx/dex/file/AnnotationUtils.java create mode 100644 dx/src/com/android/jack/dx/dex/file/AnnotationsDirectoryItem.java create mode 100644 dx/src/com/android/jack/dx/dex/file/CatchStructs.java create mode 100644 dx/src/com/android/jack/dx/dex/file/ClassDataItem.java create mode 100644 dx/src/com/android/jack/dx/dex/file/ClassDefItem.java create mode 100644 dx/src/com/android/jack/dx/dex/file/ClassDefsSection.java create mode 100644 dx/src/com/android/jack/dx/dex/file/Code.java create mode 100644 dx/src/com/android/jack/dx/dex/file/CodeItem.java create mode 100644 dx/src/com/android/jack/dx/dex/file/DebugInfoConstants.java create mode 100644 dx/src/com/android/jack/dx/dex/file/DebugInfoDecoder.java create mode 100644 dx/src/com/android/jack/dx/dex/file/DebugInfoEncoder.java create mode 100644 dx/src/com/android/jack/dx/dex/file/DebugInfoItem.java create mode 100644 dx/src/com/android/jack/dx/dex/file/DexFile.java create mode 100644 dx/src/com/android/jack/dx/dex/file/EncodedArrayItem.java create mode 100644 dx/src/com/android/jack/dx/dex/file/EncodedField.java create mode 100644 dx/src/com/android/jack/dx/dex/file/EncodedMember.java create mode 100644 dx/src/com/android/jack/dx/dex/file/EncodedMethod.java create mode 100644 dx/src/com/android/jack/dx/dex/file/FieldAnnotationStruct.java create mode 100644 dx/src/com/android/jack/dx/dex/file/FieldIdItem.java create mode 100644 dx/src/com/android/jack/dx/dex/file/FieldIdsSection.java create mode 100644 dx/src/com/android/jack/dx/dex/file/HeaderItem.java create mode 100644 dx/src/com/android/jack/dx/dex/file/HeaderSection.java create mode 100644 dx/src/com/android/jack/dx/dex/file/IdItem.java create mode 100644 dx/src/com/android/jack/dx/dex/file/ImportedCodeItem.java create mode 100644 dx/src/com/android/jack/dx/dex/file/ImportedDebugInfoItem.java create mode 100644 dx/src/com/android/jack/dx/dex/file/IndexedItem.java create mode 100644 dx/src/com/android/jack/dx/dex/file/Item.java create mode 100644 dx/src/com/android/jack/dx/dex/file/ItemType.java create mode 100644 dx/src/com/android/jack/dx/dex/file/MapItem.java create mode 100644 dx/src/com/android/jack/dx/dex/file/MemberIdItem.java create mode 100644 dx/src/com/android/jack/dx/dex/file/MemberIdsSection.java create mode 100644 dx/src/com/android/jack/dx/dex/file/MethodAnnotationStruct.java create mode 100644 dx/src/com/android/jack/dx/dex/file/MethodIdItem.java create mode 100644 dx/src/com/android/jack/dx/dex/file/MethodIdsSection.java create mode 100644 dx/src/com/android/jack/dx/dex/file/MixedItemSection.java create mode 100644 dx/src/com/android/jack/dx/dex/file/OffsettedItem.java create mode 100644 dx/src/com/android/jack/dx/dex/file/ParameterAnnotationStruct.java create mode 100644 dx/src/com/android/jack/dx/dex/file/ProtoIdItem.java create mode 100644 dx/src/com/android/jack/dx/dex/file/ProtoIdsSection.java create mode 100644 dx/src/com/android/jack/dx/dex/file/Section.java create mode 100644 dx/src/com/android/jack/dx/dex/file/Statistics.java create mode 100644 dx/src/com/android/jack/dx/dex/file/StringDataItem.java create mode 100644 dx/src/com/android/jack/dx/dex/file/StringIdItem.java create mode 100644 dx/src/com/android/jack/dx/dex/file/StringIdsSection.java create mode 100644 dx/src/com/android/jack/dx/dex/file/TypeIdItem.java create mode 100644 dx/src/com/android/jack/dx/dex/file/TypeIdsSection.java create mode 100644 dx/src/com/android/jack/dx/dex/file/TypeListItem.java create mode 100644 dx/src/com/android/jack/dx/dex/file/UniformItemSection.java create mode 100644 dx/src/com/android/jack/dx/dex/file/UniformListItem.java create mode 100644 dx/src/com/android/jack/dx/dex/file/ValueEncoder.java create mode 100644 dx/src/com/android/jack/dx/io/Annotation.java create mode 100644 dx/src/com/android/jack/dx/io/ClassData.java create mode 100644 dx/src/com/android/jack/dx/io/ClassDef.java create mode 100644 dx/src/com/android/jack/dx/io/Code.java create mode 100644 dx/src/com/android/jack/dx/io/CodeReader.java create mode 100644 dx/src/com/android/jack/dx/io/DexBuffer.java create mode 100644 dx/src/com/android/jack/dx/io/DexHasher.java create mode 100644 dx/src/com/android/jack/dx/io/DexIndexPrinter.java create mode 100644 dx/src/com/android/jack/dx/io/EncodedValue.java create mode 100644 dx/src/com/android/jack/dx/io/EncodedValueReader.java create mode 100644 dx/src/com/android/jack/dx/io/FieldId.java create mode 100644 dx/src/com/android/jack/dx/io/IndexType.java create mode 100644 dx/src/com/android/jack/dx/io/MethodId.java create mode 100644 dx/src/com/android/jack/dx/io/OpcodeInfo.java create mode 100644 dx/src/com/android/jack/dx/io/Opcodes.java create mode 100644 dx/src/com/android/jack/dx/io/ProtoId.java create mode 100644 dx/src/com/android/jack/dx/io/instructions/AddressMap.java create mode 100644 dx/src/com/android/jack/dx/io/instructions/BaseCodeCursor.java create mode 100644 dx/src/com/android/jack/dx/io/instructions/CodeCursor.java create mode 100644 dx/src/com/android/jack/dx/io/instructions/CodeInput.java create mode 100644 dx/src/com/android/jack/dx/io/instructions/CodeOutput.java create mode 100644 dx/src/com/android/jack/dx/io/instructions/DecodedInstruction.java create mode 100644 dx/src/com/android/jack/dx/io/instructions/FillArrayDataPayloadDecodedInstruction.java create mode 100644 dx/src/com/android/jack/dx/io/instructions/FiveRegisterDecodedInstruction.java create mode 100644 dx/src/com/android/jack/dx/io/instructions/FourRegisterDecodedInstruction.java create mode 100644 dx/src/com/android/jack/dx/io/instructions/InstructionCodec.java create mode 100644 dx/src/com/android/jack/dx/io/instructions/OneRegisterDecodedInstruction.java create mode 100644 dx/src/com/android/jack/dx/io/instructions/PackedSwitchPayloadDecodedInstruction.java create mode 100644 dx/src/com/android/jack/dx/io/instructions/RegisterRangeDecodedInstruction.java create mode 100644 dx/src/com/android/jack/dx/io/instructions/ShortArrayCodeInput.java create mode 100644 dx/src/com/android/jack/dx/io/instructions/ShortArrayCodeOutput.java create mode 100644 dx/src/com/android/jack/dx/io/instructions/SparseSwitchPayloadDecodedInstruction.java create mode 100644 dx/src/com/android/jack/dx/io/instructions/ThreeRegisterDecodedInstruction.java create mode 100644 dx/src/com/android/jack/dx/io/instructions/TwoRegisterDecodedInstruction.java create mode 100644 dx/src/com/android/jack/dx/io/instructions/ZeroRegisterDecodedInstruction.java create mode 100644 dx/src/com/android/jack/dx/merge/CollisionPolicy.java create mode 100644 dx/src/com/android/jack/dx/merge/DexMerger.java create mode 100644 dx/src/com/android/jack/dx/merge/IndexMap.java create mode 100644 dx/src/com/android/jack/dx/merge/InstructionTransformer.java create mode 100644 dx/src/com/android/jack/dx/merge/SortableType.java create mode 100644 dx/src/com/android/jack/dx/merge/TypeList.java create mode 100644 dx/src/com/android/jack/dx/rop/annotation/Annotation.java create mode 100644 dx/src/com/android/jack/dx/rop/annotation/AnnotationVisibility.java create mode 100644 dx/src/com/android/jack/dx/rop/annotation/Annotations.java create mode 100644 dx/src/com/android/jack/dx/rop/annotation/AnnotationsList.java create mode 100644 dx/src/com/android/jack/dx/rop/annotation/NameValuePair.java create mode 100644 dx/src/com/android/jack/dx/rop/code/AccessFlags.java create mode 100644 dx/src/com/android/jack/dx/rop/code/BasicBlock.java create mode 100644 dx/src/com/android/jack/dx/rop/code/BasicBlockList.java create mode 100644 dx/src/com/android/jack/dx/rop/code/ConservativeTranslationAdvice.java create mode 100644 dx/src/com/android/jack/dx/rop/code/CstInsn.java create mode 100644 dx/src/com/android/jack/dx/rop/code/DexTranslationAdvice.java create mode 100644 dx/src/com/android/jack/dx/rop/code/Exceptions.java create mode 100644 dx/src/com/android/jack/dx/rop/code/FillArrayDataInsn.java create mode 100644 dx/src/com/android/jack/dx/rop/code/Insn.java create mode 100644 dx/src/com/android/jack/dx/rop/code/InsnList.java create mode 100644 dx/src/com/android/jack/dx/rop/code/LocalItem.java create mode 100644 dx/src/com/android/jack/dx/rop/code/LocalVariableExtractor.java create mode 100644 dx/src/com/android/jack/dx/rop/code/LocalVariableInfo.java create mode 100644 dx/src/com/android/jack/dx/rop/code/PlainCstInsn.java create mode 100644 dx/src/com/android/jack/dx/rop/code/PlainInsn.java create mode 100644 dx/src/com/android/jack/dx/rop/code/RegOps.java create mode 100644 dx/src/com/android/jack/dx/rop/code/RegisterSpec.java create mode 100644 dx/src/com/android/jack/dx/rop/code/RegisterSpecList.java create mode 100644 dx/src/com/android/jack/dx/rop/code/RegisterSpecSet.java create mode 100644 dx/src/com/android/jack/dx/rop/code/Rop.java create mode 100644 dx/src/com/android/jack/dx/rop/code/RopMethod.java create mode 100644 dx/src/com/android/jack/dx/rop/code/Rops.java create mode 100644 dx/src/com/android/jack/dx/rop/code/SourcePosition.java create mode 100644 dx/src/com/android/jack/dx/rop/code/SwitchInsn.java create mode 100644 dx/src/com/android/jack/dx/rop/code/ThrowingCstInsn.java create mode 100644 dx/src/com/android/jack/dx/rop/code/ThrowingInsn.java create mode 100644 dx/src/com/android/jack/dx/rop/code/TranslationAdvice.java create mode 100644 dx/src/com/android/jack/dx/rop/code/package.html create mode 100644 dx/src/com/android/jack/dx/rop/cst/Constant.java create mode 100644 dx/src/com/android/jack/dx/rop/cst/ConstantPool.java create mode 100644 dx/src/com/android/jack/dx/rop/cst/CstAnnotation.java create mode 100644 dx/src/com/android/jack/dx/rop/cst/CstArray.java create mode 100644 dx/src/com/android/jack/dx/rop/cst/CstBaseMethodRef.java create mode 100644 dx/src/com/android/jack/dx/rop/cst/CstBoolean.java create mode 100644 dx/src/com/android/jack/dx/rop/cst/CstByte.java create mode 100644 dx/src/com/android/jack/dx/rop/cst/CstChar.java create mode 100644 dx/src/com/android/jack/dx/rop/cst/CstDouble.java create mode 100644 dx/src/com/android/jack/dx/rop/cst/CstEnumRef.java create mode 100644 dx/src/com/android/jack/dx/rop/cst/CstFieldRef.java create mode 100644 dx/src/com/android/jack/dx/rop/cst/CstFloat.java create mode 100644 dx/src/com/android/jack/dx/rop/cst/CstIndexMap.java create mode 100644 dx/src/com/android/jack/dx/rop/cst/CstInteger.java create mode 100644 dx/src/com/android/jack/dx/rop/cst/CstInterfaceMethodRef.java create mode 100644 dx/src/com/android/jack/dx/rop/cst/CstKnownNull.java create mode 100644 dx/src/com/android/jack/dx/rop/cst/CstLiteral32.java create mode 100644 dx/src/com/android/jack/dx/rop/cst/CstLiteral64.java create mode 100644 dx/src/com/android/jack/dx/rop/cst/CstLiteralBits.java create mode 100644 dx/src/com/android/jack/dx/rop/cst/CstLong.java create mode 100644 dx/src/com/android/jack/dx/rop/cst/CstMemberRef.java create mode 100644 dx/src/com/android/jack/dx/rop/cst/CstMethodRef.java create mode 100644 dx/src/com/android/jack/dx/rop/cst/CstNat.java create mode 100644 dx/src/com/android/jack/dx/rop/cst/CstShort.java create mode 100644 dx/src/com/android/jack/dx/rop/cst/CstString.java create mode 100644 dx/src/com/android/jack/dx/rop/cst/CstType.java create mode 100644 dx/src/com/android/jack/dx/rop/cst/StdConstantPool.java create mode 100644 dx/src/com/android/jack/dx/rop/cst/TypedConstant.java create mode 100644 dx/src/com/android/jack/dx/rop/cst/Zeroes.java create mode 100644 dx/src/com/android/jack/dx/rop/cst/package.html create mode 100644 dx/src/com/android/jack/dx/rop/package-info.java create mode 100644 dx/src/com/android/jack/dx/rop/type/Prototype.java create mode 100644 dx/src/com/android/jack/dx/rop/type/StdTypeList.java create mode 100644 dx/src/com/android/jack/dx/rop/type/Type.java create mode 100644 dx/src/com/android/jack/dx/rop/type/TypeBearer.java create mode 100644 dx/src/com/android/jack/dx/rop/type/TypeList.java create mode 100644 dx/src/com/android/jack/dx/rop/type/package.html create mode 100644 dx/src/com/android/jack/dx/ssa/BasicRegisterMapper.java create mode 100644 dx/src/com/android/jack/dx/ssa/ConstCollector.java create mode 100644 dx/src/com/android/jack/dx/ssa/DeadCodeRemover.java create mode 100644 dx/src/com/android/jack/dx/ssa/DomFront.java create mode 100644 dx/src/com/android/jack/dx/ssa/Dominators.java create mode 100644 dx/src/com/android/jack/dx/ssa/EscapeAnalysis.java create mode 100644 dx/src/com/android/jack/dx/ssa/InterferenceRegisterMapper.java create mode 100644 dx/src/com/android/jack/dx/ssa/LiteralOpUpgrader.java create mode 100644 dx/src/com/android/jack/dx/ssa/LocalVariableExtractor.java create mode 100644 dx/src/com/android/jack/dx/ssa/LocalVariableInfo.java create mode 100644 dx/src/com/android/jack/dx/ssa/MoveParamCombiner.java create mode 100644 dx/src/com/android/jack/dx/ssa/NormalSsaInsn.java create mode 100644 dx/src/com/android/jack/dx/ssa/Optimizer.java create mode 100644 dx/src/com/android/jack/dx/ssa/PhiInsn.java create mode 100644 dx/src/com/android/jack/dx/ssa/PhiTypeResolver.java create mode 100644 dx/src/com/android/jack/dx/ssa/RegisterMapper.java create mode 100644 dx/src/com/android/jack/dx/ssa/SCCP.java create mode 100644 dx/src/com/android/jack/dx/ssa/SetFactory.java create mode 100644 dx/src/com/android/jack/dx/ssa/SsaBasicBlock.java create mode 100644 dx/src/com/android/jack/dx/ssa/SsaConverter.java create mode 100644 dx/src/com/android/jack/dx/ssa/SsaInsn.java create mode 100644 dx/src/com/android/jack/dx/ssa/SsaMethod.java create mode 100644 dx/src/com/android/jack/dx/ssa/SsaRenamer.java create mode 100644 dx/src/com/android/jack/dx/ssa/back/FirstFitAllocator.java create mode 100644 dx/src/com/android/jack/dx/ssa/back/FirstFitLocalCombiningAllocator.java create mode 100644 dx/src/com/android/jack/dx/ssa/back/IdenticalBlockCombiner.java create mode 100644 dx/src/com/android/jack/dx/ssa/back/InterferenceGraph.java create mode 100644 dx/src/com/android/jack/dx/ssa/back/LivenessAnalyzer.java create mode 100644 dx/src/com/android/jack/dx/ssa/back/NullRegisterAllocator.java create mode 100644 dx/src/com/android/jack/dx/ssa/back/RegisterAllocator.java create mode 100644 dx/src/com/android/jack/dx/ssa/back/SsaToRop.java create mode 100644 dx/src/com/android/jack/dx/ssa/package-info.java create mode 100644 dx/src/com/android/jack/dx/util/AnnotatedOutput.java create mode 100644 dx/src/com/android/jack/dx/util/BitIntSet.java create mode 100644 dx/src/com/android/jack/dx/util/Bits.java create mode 100644 dx/src/com/android/jack/dx/util/ByteArray.java create mode 100644 dx/src/com/android/jack/dx/util/ByteArrayAnnotatedOutput.java create mode 100644 dx/src/com/android/jack/dx/util/ByteArrayByteInput.java create mode 100644 dx/src/com/android/jack/dx/util/ByteInput.java create mode 100644 dx/src/com/android/jack/dx/util/ByteOutput.java create mode 100644 dx/src/com/android/jack/dx/util/DexException.java create mode 100644 dx/src/com/android/jack/dx/util/ExceptionWithContext.java create mode 100644 dx/src/com/android/jack/dx/util/FileUtils.java create mode 100644 dx/src/com/android/jack/dx/util/FixedSizeList.java create mode 100644 dx/src/com/android/jack/dx/util/Hex.java create mode 100644 dx/src/com/android/jack/dx/util/HexParser.java create mode 100644 dx/src/com/android/jack/dx/util/IndentingWriter.java create mode 100644 dx/src/com/android/jack/dx/util/IntIterator.java create mode 100644 dx/src/com/android/jack/dx/util/IntList.java create mode 100644 dx/src/com/android/jack/dx/util/IntSet.java create mode 100644 dx/src/com/android/jack/dx/util/LabeledItem.java create mode 100644 dx/src/com/android/jack/dx/util/LabeledList.java create mode 100644 dx/src/com/android/jack/dx/util/Leb128Utils.java create mode 100644 dx/src/com/android/jack/dx/util/ListIntSet.java create mode 100644 dx/src/com/android/jack/dx/util/MutabilityControl.java create mode 100644 dx/src/com/android/jack/dx/util/MutabilityException.java create mode 100644 dx/src/com/android/jack/dx/util/Mutf8.java create mode 100644 dx/src/com/android/jack/dx/util/Output.java create mode 100644 dx/src/com/android/jack/dx/util/ToHuman.java create mode 100644 dx/src/com/android/jack/dx/util/TwoColumnOutput.java create mode 100644 dx/src/com/android/jack/dx/util/Uint.java create mode 100644 dx/src/com/android/jack/dx/util/Unsigned.java create mode 100644 dx/src/com/android/jack/dx/util/Warning.java create mode 100644 dx/src/com/android/jack/dx/util/Writers.java create mode 100644 dx/src/com/android/jack/dx/util/package.html (limited to 'dx/src') diff --git a/dx/src/Android.mk b/dx/src/Android.mk new file mode 100644 index 00000000..942a076b --- /dev/null +++ b/dx/src/Android.mk @@ -0,0 +1,34 @@ +# Copyright 2006 The Android Open Source Project +# +LOCAL_PATH := $(call my-dir) + +# This tool is prebuilt if we're doing an app-only build. +ifeq ($(TARGET_BUILD_APPS),) + +# dx java library +# ============================================================ +include $(CLEAR_VARS) + +LOCAL_SRC_FILES := $(call all-subdir-java-files) +LOCAL_JAR_MANIFEST := ../etc/manifest.txt +LOCAL_MODULE_TAGS := optional +LOCAL_MODULE:= dx-jack + +include $(BUILD_HOST_JAVA_LIBRARY) + +INTERNAL_DALVIK_MODULES += $(LOCAL_INSTALLED_MODULE) + +endif # TARGET_BUILD_APPS + +# the documentation +# ============================================================ +include $(CLEAR_VARS) + +LOCAL_SRC_FILES := $(call all-subdir-java-files) $(call all-subdir-html-files) +LOCAL_MODULE_TAGS := optional +LOCAL_MODULE:= dx-jack +LOCAL_DROIDDOC_OPTIONS := -hidden +LOCAL_MODULE_CLASS := JAVA_LIBRARIES +LOCAL_IS_HOST_MODULE := true + +include $(BUILD_DROIDDOC) diff --git a/dx/src/com/android/jack/dx/Version.java b/dx/src/com/android/jack/dx/Version.java new file mode 100644 index 00000000..1152dd8a --- /dev/null +++ b/dx/src/com/android/jack/dx/Version.java @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx; + +/** + * Version number for dx. + */ +public class Version { + /** {@code non-null;} version string */ + public static final String VERSION = "1.7"; +} diff --git a/dx/src/com/android/jack/dx/dex/DexFormat.java b/dx/src/com/android/jack/dx/dex/DexFormat.java new file mode 100644 index 00000000..bf526bc5 --- /dev/null +++ b/dx/src/com/android/jack/dx/dex/DexFormat.java @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.dex; + +/** + * Constants that show up in and are otherwise related to {@code .dex} + * files, and helper methods for same. + */ +public final class DexFormat { + private DexFormat() {} + + /** + * API level to target in order to produce the most modern file + * format + */ + public static final int API_CURRENT = 14; + + /** API level to target in order to suppress extended opcode usage */ + public static final int API_NO_EXTENDED_OPCODES = 13; + + /** + * file name of the primary {@code .dex} file inside an + * application or library {@code .jar} file + */ + public static final String DEX_IN_JAR_NAME = "classes.dex"; + + /** common prefix for all dex file "magic numbers" */ + public static final String MAGIC_PREFIX = "dex\n"; + + /** common suffix for all dex file "magic numbers" */ + public static final String MAGIC_SUFFIX = "\0"; + + /** dex file version number for the current format variant */ + public static final String VERSION_CURRENT = "036"; + + /** dex file version number for API level 13 and earlier */ + public static final String VERSION_FOR_API_13 = "035"; + + /** + * value used to indicate endianness of file contents + */ + public static final int ENDIAN_TAG = 0x12345678; + + /** + * Returns the API level corresponding to the given magic number, + * or {@code -1} if the given array is not a well-formed dex file + * magic number. + */ + public static int magicToApi(byte[] magic) { + if (magic.length != 8) { + return -1; + } + + if ((magic[0] != 'd') || (magic[1] != 'e') || (magic[2] != 'x') || (magic[3] != '\n') || + (magic[7] != '\0')) { + return -1; + } + + String version = "" + ((char) magic[4]) + ((char) magic[5]) +((char) magic[6]); + + if (version.equals(VERSION_CURRENT)) { + return API_CURRENT; + } else if (version.equals(VERSION_FOR_API_13)) { + return 13; + } + + return -1; + } + + /** + * Returns the magic number corresponding to the given target API level. + */ + public static String apiToMagic(int targetApiLevel) { + String version; + + if (targetApiLevel >= API_CURRENT) { + version = VERSION_CURRENT; + } else { + version = VERSION_FOR_API_13; + } + + return MAGIC_PREFIX + version + MAGIC_SUFFIX; + } +} diff --git a/dx/src/com/android/jack/dx/dex/DexOptions.java b/dx/src/com/android/jack/dx/dex/DexOptions.java new file mode 100644 index 00000000..81cf288e --- /dev/null +++ b/dx/src/com/android/jack/dx/dex/DexOptions.java @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.dex; + +/** + * Container for options used to control details of dex file generation. + */ +public class DexOptions { + /** target API level */ + public int targetApiLevel = DexFormat.API_NO_EXTENDED_OPCODES; + + /** force generation of jumbo opcodes */ + public boolean forceJumbo = false; + + /** + * Gets the dex file magic number corresponding to this instance. + */ + public String getMagic() { + return DexFormat.apiToMagic(targetApiLevel); + } +} diff --git a/dx/src/com/android/jack/dx/dex/SizeOf.java b/dx/src/com/android/jack/dx/dex/SizeOf.java new file mode 100644 index 00000000..ae118d0e --- /dev/null +++ b/dx/src/com/android/jack/dx/dex/SizeOf.java @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.dex; + +public final class SizeOf { + private SizeOf() {} + + public static final int UBYTE = 1; + public static final int USHORT = 2; + public static final int UINT = 4; + + public static final int SIGNATURE = UBYTE * 20; + + /** + * magic ubyte[8] + * checksum uint + * signature ubyte[20] + * file_size uint + * header_size uint + * endian_tag uint + * link_size uint + * link_off uint + * map_off uint + * string_ids_size uint + * string_ids_off uint + * type_ids_size uint + * type_ids_off uint + * proto_ids_size uint + * proto_ids_off uint + * field_ids_size uint + * field_ids_off uint + * method_ids_size uint + * method_ids_off uint + * class_defs_size uint + * class_defs_off uint + * data_size uint + * data_off uint + */ + public static final int HEADER_ITEM = (8 * UBYTE) + UINT + SIGNATURE + (20 * UINT); // 0x70 + + /** + * string_data_off uint + */ + public static final int STRING_ID_ITEM = UINT; + + /** + * descriptor_idx uint + */ + public static final int TYPE_ID_ITEM = UINT; + + /** + * type_idx ushort + */ + public static final int TYPE_ITEM = USHORT; + + /** + * shorty_idx uint + * return_type_idx uint + * return_type_idx uint + */ + public static final int PROTO_ID_ITEM = UINT + UINT + UINT; + + /** + * class_idx ushort + * type_idx/proto_idx ushort + * name_idx uint + */ + public static final int MEMBER_ID_ITEM = USHORT + USHORT + UINT; + + /** + * class_idx uint + * access_flags uint + * superclass_idx uint + * interfaces_off uint + * source_file_idx uint + * annotations_off uint + * class_data_off uint + * static_values_off uint + */ + public static final int CLASS_DEF_ITEM = 8 * UINT; + + /** + * type ushort + * unused ushort + * size uint + * offset uint + */ + public static final int MAP_ITEM = USHORT + USHORT + UINT + UINT; + + /** + * start_addr uint + * insn_count ushort + * handler_off ushort + */ + public static final int TRY_ITEM = UINT + USHORT + USHORT; +} diff --git a/dx/src/com/android/jack/dx/dex/TableOfContents.java b/dx/src/com/android/jack/dx/dex/TableOfContents.java new file mode 100644 index 00000000..11b7bfe7 --- /dev/null +++ b/dx/src/com/android/jack/dx/dex/TableOfContents.java @@ -0,0 +1,239 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.dex; + +import com.android.jack.dx.io.DexBuffer; +import com.android.jack.dx.util.DexException; + +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.util.Arrays; + +/** + * The file header and map. + */ +public final class TableOfContents { + + /* + * TODO: factor out ID constants. + */ + + public final Section header = new Section(0x0000); + public final Section stringIds = new Section(0x0001); + public final Section typeIds = new Section(0x0002); + public final Section protoIds = new Section(0x0003); + public final Section fieldIds = new Section(0x0004); + public final Section methodIds = new Section(0x0005); + public final Section classDefs = new Section(0x0006); + public final Section mapList = new Section(0x1000); + public final Section typeLists = new Section(0x1001); + public final Section annotationSetRefLists = new Section(0x1002); + public final Section annotationSets = new Section(0x1003); + public final Section classDatas = new Section(0x2000); + public final Section codes = new Section(0x2001); + public final Section stringDatas = new Section(0x2002); + public final Section debugInfos = new Section(0x2003); + public final Section annotations = new Section(0x2004); + public final Section encodedArrays = new Section(0x2005); + public final Section annotationsDirectories = new Section(0x2006); + public final Section[] sections = { + header, stringIds, typeIds, protoIds, fieldIds, methodIds, classDefs, mapList, + typeLists, annotationSetRefLists, annotationSets, classDatas, codes, stringDatas, + debugInfos, annotations, encodedArrays, annotationsDirectories + }; + + public int checksum; + public byte[] signature; + public int fileSize; + public int linkSize; + public int linkOff; + public int dataSize; + public int dataOff; + + public TableOfContents() { + signature = new byte[20]; + } + + public void readFrom(DexBuffer buffer) throws IOException { + readHeader(buffer.open(0)); + readMap(buffer.open(mapList.off)); + computeSizesFromOffsets(); + } + + private void readHeader(DexBuffer.Section headerIn) throws UnsupportedEncodingException { + byte[] magic = headerIn.readByteArray(8); + int apiTarget = DexFormat.magicToApi(magic); + + if (apiTarget != DexFormat.API_NO_EXTENDED_OPCODES) { + throw new DexException("Unexpected magic: " + Arrays.toString(magic)); + } + + checksum = headerIn.readInt(); + signature = headerIn.readByteArray(20); + fileSize = headerIn.readInt(); + int headerSize = headerIn.readInt(); + if (headerSize != SizeOf.HEADER_ITEM) { + throw new DexException("Unexpected header: 0x" + Integer.toHexString(headerSize)); + } + int endianTag = headerIn.readInt(); + if (endianTag != DexFormat.ENDIAN_TAG) { + throw new DexException("Unexpected endian tag: 0x" + Integer.toHexString(endianTag)); + } + linkSize = headerIn.readInt(); + linkOff = headerIn.readInt(); + mapList.off = headerIn.readInt(); + if (mapList.off == 0) { + throw new DexException("Cannot merge dex files that do not contain a map"); + } + stringIds.size = headerIn.readInt(); + stringIds.off = headerIn.readInt(); + typeIds.size = headerIn.readInt(); + typeIds.off = headerIn.readInt(); + protoIds.size = headerIn.readInt(); + protoIds.off = headerIn.readInt(); + fieldIds.size = headerIn.readInt(); + fieldIds.off = headerIn.readInt(); + methodIds.size = headerIn.readInt(); + methodIds.off = headerIn.readInt(); + classDefs.size = headerIn.readInt(); + classDefs.off = headerIn.readInt(); + dataSize = headerIn.readInt(); + dataOff = headerIn.readInt(); + } + + private void readMap(DexBuffer.Section in) throws IOException { + int mapSize = in.readInt(); + Section previous = null; + for (int i = 0; i < mapSize; i++) { + short type = in.readShort(); + in.readShort(); // unused + Section section = getSection(type); + int size = in.readInt(); + int offset = in.readInt(); + + if ((section.size != 0 && section.size != size) + || (section.off != -1 && section.off != offset)) { + throw new DexException("Unexpected map value for 0x" + Integer.toHexString(type)); + } + + section.size = size; + section.off = offset; + + if (previous != null && previous.off > section.off) { + throw new DexException("Map is unsorted at " + previous + ", " + section); + } + + previous = section; + } + Arrays.sort(sections); + } + + public void computeSizesFromOffsets() { + int end = dataOff + dataSize; + for (int i = sections.length - 1; i >= 0; i--) { + Section section = sections[i]; + if (section.off == -1) { + continue; + } + if (section.off > end) { + throw new DexException("Map is unsorted at " + section); + } + section.byteCount = end - section.off; + end = section.off; + } + } + + private Section getSection(short type) { + for (Section section : sections) { + if (section.type == type) { + return section; + } + } + throw new IllegalArgumentException("No such map item: " + type); + } + + public void writeHeader(DexBuffer.Section out) throws IOException { + out.write(DexFormat.apiToMagic(DexFormat.API_NO_EXTENDED_OPCODES).getBytes("UTF-8")); + out.writeInt(checksum); + out.write(signature); + out.writeInt(fileSize); + out.writeInt(SizeOf.HEADER_ITEM); + out.writeInt(DexFormat.ENDIAN_TAG); + out.writeInt(linkSize); + out.writeInt(linkOff); + out.writeInt(mapList.off); + out.writeInt(stringIds.size); + out.writeInt(stringIds.off); + out.writeInt(typeIds.size); + out.writeInt(typeIds.off); + out.writeInt(protoIds.size); + out.writeInt(protoIds.off); + out.writeInt(fieldIds.size); + out.writeInt(fieldIds.off); + out.writeInt(methodIds.size); + out.writeInt(methodIds.off); + out.writeInt(classDefs.size); + out.writeInt(classDefs.off); + out.writeInt(dataSize); + out.writeInt(dataOff); + } + + public void writeMap(DexBuffer.Section out) throws IOException { + int count = 0; + for (Section section : sections) { + if (section.exists()) { + count++; + } + } + + out.writeInt(count); + for (Section section : sections) { + if (section.exists()) { + out.writeShort(section.type); + out.writeShort((short) 0); + out.writeInt(section.size); + out.writeInt(section.off); + } + } + } + + public static class Section implements Comparable
{ + public final short type; + public int size = 0; + public int off = -1; + public int byteCount = 0; + + public Section(int type) { + this.type = (short) type; + } + + public boolean exists() { + return size > 0; + } + + public int compareTo(Section section) { + if (off != section.off) { + return off < section.off ? -1 : 1; + } + return 0; + } + + @Override public String toString() { + return String.format("Section[type=%#x,off=%#x,size=%#x]", type, off, size); + } + } +} diff --git a/dx/src/com/android/jack/dx/dex/code/ArrayData.java b/dx/src/com/android/jack/dx/dex/code/ArrayData.java new file mode 100644 index 00000000..f8b76846 --- /dev/null +++ b/dx/src/com/android/jack/dx/dex/code/ArrayData.java @@ -0,0 +1,200 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.dex.code; + +import com.android.jack.dx.io.Opcodes; +import com.android.jack.dx.rop.code.RegisterSpecList; +import com.android.jack.dx.rop.code.SourcePosition; +import com.android.jack.dx.rop.cst.*; +import com.android.jack.dx.rop.type.Type; +import com.android.jack.dx.util.AnnotatedOutput; +import com.android.jack.dx.util.Hex; + +import java.util.ArrayList; + +/** + * Pseudo-instruction which holds fill array data. + */ +public final class ArrayData extends VariableSizeInsn { + /** + * {@code non-null;} address representing the instruction that uses this + * instance + */ + private final CodeAddress user; + + /** {@code non-null;} initial values to be filled into an array */ + private final ArrayList values; + + /** non-null: type of constant that initializes the array */ + private final Constant arrayType; + + /** Width of the init value element */ + private final int elemWidth; + + /** Length of the init list */ + private final int initLength; + + /** + * Constructs an instance. The output address of this instance is initially + * unknown ({@code -1}). + * + * @param position {@code non-null;} source position + * @param user {@code non-null;} address representing the instruction that + * uses this instance + * @param values {@code non-null;} initial values to be filled into an array + */ + public ArrayData(SourcePosition position, CodeAddress user, + ArrayList values, + Constant arrayType) { + super(position, RegisterSpecList.EMPTY); + + if (user == null) { + throw new NullPointerException("user == null"); + } + + if (values == null) { + throw new NullPointerException("values == null"); + } + + int sz = values.size(); + + if (sz <= 0) { + throw new IllegalArgumentException("Illegal number of init values"); + } + + this.arrayType = arrayType; + + if (arrayType == CstType.BYTE_ARRAY || + arrayType == CstType.BOOLEAN_ARRAY) { + elemWidth = 1; + } else if (arrayType == CstType.SHORT_ARRAY || + arrayType == CstType.CHAR_ARRAY) { + elemWidth = 2; + } else if (arrayType == CstType.INT_ARRAY || + arrayType == CstType.FLOAT_ARRAY) { + elemWidth = 4; + } else if (arrayType == CstType.LONG_ARRAY || + arrayType == CstType.DOUBLE_ARRAY) { + elemWidth = 8; + } else { + throw new IllegalArgumentException("Unexpected constant type"); + } + this.user = user; + this.values = values; + initLength = values.size(); + } + + /** {@inheritDoc} */ + @Override + public int codeSize() { + int sz = initLength; + // Note: the unit here is 16-bit + return 4 + ((sz * elemWidth) + 1) / 2; + } + + /** {@inheritDoc} */ + @Override + public void writeTo(AnnotatedOutput out) { + int sz = values.size(); + + out.writeShort(Opcodes.FILL_ARRAY_DATA_PAYLOAD); + out.writeShort(elemWidth); + out.writeInt(initLength); + + + // For speed reasons, replicate the for loop in each case + switch (elemWidth) { + case 1: { + for (int i = 0; i < sz; i++) { + Constant cst = values.get(i); + out.writeByte((byte) ((CstLiteral32) cst).getIntBits()); + } + break; + } + case 2: { + for (int i = 0; i < sz; i++) { + Constant cst = values.get(i); + out.writeShort((short) ((CstLiteral32) cst).getIntBits()); + } + break; + } + case 4: { + for (int i = 0; i < sz; i++) { + Constant cst = values.get(i); + out.writeInt(((CstLiteral32) cst).getIntBits()); + } + break; + } + case 8: { + for (int i = 0; i < sz; i++) { + Constant cst = values.get(i); + out.writeLong(((CstLiteral64) cst).getLongBits()); + } + break; + } + default: + break; + } + + // Pad one byte to make the size of data table multiples of 16-bits + if (elemWidth == 1 && (sz % 2 != 0)) { + out.writeByte(0x00); + } + } + + /** {@inheritDoc} */ + @Override + public DalvInsn withRegisters(RegisterSpecList registers) { + return new ArrayData(getPosition(), user, values, arrayType); + } + + /** {@inheritDoc} */ + @Override + protected String argString() { + StringBuffer sb = new StringBuffer(100); + + int sz = values.size(); + for (int i = 0; i < sz; i++) { + sb.append("\n "); + sb.append(i); + sb.append(": "); + sb.append(values.get(i).toHuman()); + } + + return sb.toString(); + } + + /** {@inheritDoc} */ + @Override + protected String listingString0(boolean noteIndices) { + int baseAddress = user.getAddress(); + StringBuffer sb = new StringBuffer(100); + int sz = values.size(); + + sb.append("fill-array-data-payload // for fill-array-data @ "); + sb.append(Hex.u2(baseAddress)); + + for (int i = 0; i < sz; i++) { + sb.append("\n "); + sb.append(i); + sb.append(": "); + sb.append(values.get(i).toHuman()); + } + + return sb.toString(); + } +} diff --git a/dx/src/com/android/jack/dx/dex/code/BlockAddresses.java b/dx/src/com/android/jack/dx/dex/code/BlockAddresses.java new file mode 100644 index 00000000..4ec10f8c --- /dev/null +++ b/dx/src/com/android/jack/dx/dex/code/BlockAddresses.java @@ -0,0 +1,143 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.dex.code; + +import com.android.jack.dx.rop.code.BasicBlock; +import com.android.jack.dx.rop.code.BasicBlockList; +import com.android.jack.dx.rop.code.Insn; +import com.android.jack.dx.rop.code.RopMethod; +import com.android.jack.dx.rop.code.SourcePosition; + +/** + * Container for the set of {@link CodeAddress} instances associated with + * the blocks of a particular method. Each block has a corresponding + * start address, end address, and last instruction address. + */ +public final class BlockAddresses { + /** {@code non-null;} array containing addresses for the start of each basic + * block (indexed by basic block label) */ + private final CodeAddress[] starts; + + /** {@code non-null;} array containing addresses for the final instruction + * of each basic block (indexed by basic block label) */ + private final CodeAddress[] lasts; + + /** {@code non-null;} array containing addresses for the end (just past the + * final instruction) of each basic block (indexed by basic block + * label) */ + private final CodeAddress[] ends; + + /** + * Constructs an instance. + * + * @param method {@code non-null;} the method to have block addresses for + */ + public BlockAddresses(RopMethod method) { + BasicBlockList blocks = method.getBlocks(); + int maxLabel = blocks.getMaxLabel(); + + this.starts = new CodeAddress[maxLabel]; + this.lasts = new CodeAddress[maxLabel]; + this.ends = new CodeAddress[maxLabel]; + + setupArrays(method); + } + + /** + * Gets the instance for the start of the given block. + * + * @param block {@code non-null;} the block in question + * @return {@code non-null;} the appropriate instance + */ + public CodeAddress getStart(BasicBlock block) { + return starts[block.getLabel()]; + } + + /** + * Gets the instance for the start of the block with the given label. + * + * @param label {@code non-null;} the label of the block in question + * @return {@code non-null;} the appropriate instance + */ + public CodeAddress getStart(int label) { + return starts[label]; + } + + /** + * Gets the instance for the final instruction of the given block. + * + * @param block {@code non-null;} the block in question + * @return {@code non-null;} the appropriate instance + */ + public CodeAddress getLast(BasicBlock block) { + return lasts[block.getLabel()]; + } + + /** + * Gets the instance for the final instruction of the block with + * the given label. + * + * @param label {@code non-null;} the label of the block in question + * @return {@code non-null;} the appropriate instance + */ + public CodeAddress getLast(int label) { + return lasts[label]; + } + + /** + * Gets the instance for the end (address after the final instruction) + * of the given block. + * + * @param block {@code non-null;} the block in question + * @return {@code non-null;} the appropriate instance + */ + public CodeAddress getEnd(BasicBlock block) { + return ends[block.getLabel()]; + } + + /** + * Gets the instance for the end (address after the final instruction) + * of the block with the given label. + * + * @param label {@code non-null;} the label of the block in question + * @return {@code non-null;} the appropriate instance + */ + public CodeAddress getEnd(int label) { + return ends[label]; + } + + /** + * Sets up the address arrays. + */ + private void setupArrays(RopMethod method) { + BasicBlockList blocks = method.getBlocks(); + int sz = blocks.size(); + + for (int i = 0; i < sz; i++) { + BasicBlock one = blocks.get(i); + int label = one.getLabel(); + Insn insn = one.getInsns().get(0); + + starts[label] = new CodeAddress(insn.getPosition()); + + SourcePosition pos = one.getLastInsn().getPosition(); + + lasts[label] = new CodeAddress(pos); + ends[label] = new CodeAddress(pos); + } + } +} diff --git a/dx/src/com/android/jack/dx/dex/code/CatchBuilder.java b/dx/src/com/android/jack/dx/dex/code/CatchBuilder.java new file mode 100644 index 00000000..9b19792b --- /dev/null +++ b/dx/src/com/android/jack/dx/dex/code/CatchBuilder.java @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.dex.code; + +import com.android.jack.dx.rop.type.Type; + +import java.util.HashSet; + +/** + * Interface for the construction of {@link CatchTable} instances. + */ +public interface CatchBuilder { + /** + * Builds and returns the catch table for this instance. + * + * @return {@code non-null;} the constructed table + */ + public CatchTable build(); + + /** + * Gets whether this instance has any catches at all (either typed + * or catch-all). + * + * @return whether this instance has any catches at all + */ + public boolean hasAnyCatches(); + + /** + * Gets the set of catch types associated with this instance. + * + * @return {@code non-null;} the set of catch types + */ + public HashSet getCatchTypes(); +} diff --git a/dx/src/com/android/jack/dx/dex/code/CatchHandlerList.java b/dx/src/com/android/jack/dx/dex/code/CatchHandlerList.java new file mode 100644 index 00000000..1f5ae6d3 --- /dev/null +++ b/dx/src/com/android/jack/dx/dex/code/CatchHandlerList.java @@ -0,0 +1,238 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.dex.code; + +import com.android.jack.dx.rop.cst.CstType; +import com.android.jack.dx.util.FixedSizeList; +import com.android.jack.dx.util.Hex; + +/** + * Ordered list of (exception type, handler address) entries. + */ +public final class CatchHandlerList extends FixedSizeList + implements Comparable { + /** {@code non-null;} empty instance */ + public static final CatchHandlerList EMPTY = new CatchHandlerList(0); + + /** + * Constructs an instance. All indices initially contain {@code null}. + * + * @param size {@code >= 0;} the size of the list + */ + public CatchHandlerList(int size) { + super(size); + } + + /** + * Gets the element at the given index. It is an error to call + * this with the index for an element which was never set; if you + * do that, this will throw {@code NullPointerException}. + * + * @param n {@code >= 0, < size();} which index + * @return {@code non-null;} element at that index + */ + public Entry get(int n) { + return (Entry) get0(n); + } + + /** {@inheritDoc} */ + public String toHuman() { + return toHuman("", ""); + } + + /** + * Get the human form of this instance, prefixed on each line + * with the string. + * + * @param prefix {@code non-null;} the prefix for every line + * @param header {@code non-null;} the header for the first line (after the + * first prefix) + * @return {@code non-null;} the human form + */ + public String toHuman(String prefix, String header) { + StringBuilder sb = new StringBuilder(100); + int size = size(); + + sb.append(prefix); + sb.append(header); + sb.append("catch "); + + for (int i = 0; i < size; i++) { + Entry entry = get(i); + + if (i != 0) { + sb.append(",\n"); + sb.append(prefix); + sb.append(" "); + } + + if ((i == (size - 1)) && catchesAll()) { + sb.append(""); + } else { + sb.append(entry.getExceptionType().toHuman()); + } + + sb.append(" -> "); + sb.append(Hex.u2or4(entry.getHandler())); + } + + return sb.toString(); + } + + /** + * Returns whether or not this instance ends with a "catch-all" + * handler. + * + * @return {@code true} if this instance ends with a "catch-all" + * handler or {@code false} if not + */ + public boolean catchesAll() { + int size = size(); + + if (size == 0) { + return false; + } + + Entry last = get(size - 1); + return last.getExceptionType().equals(CstType.OBJECT); + } + + /** + * Sets the entry at the given index. + * + * @param n {@code >= 0, < size();} which index + * @param exceptionType {@code non-null;} type of exception handled + * @param handler {@code >= 0;} exception handler address + */ + public void set(int n, CstType exceptionType, int handler) { + set0(n, new Entry(exceptionType, handler)); + } + + /** + * Sets the entry at the given index. + * + * @param n {@code >= 0, < size();} which index + * @param entry {@code non-null;} the entry to set at {@code n} + */ + public void set(int n, Entry entry) { + set0(n, entry); + } + + /** {@inheritDoc} */ + public int compareTo(CatchHandlerList other) { + if (this == other) { + // Easy out. + return 0; + } + + int thisSize = size(); + int otherSize = other.size(); + int checkSize = Math.min(thisSize, otherSize); + + for (int i = 0; i < checkSize; i++) { + Entry thisEntry = get(i); + Entry otherEntry = other.get(i); + int compare = thisEntry.compareTo(otherEntry); + if (compare != 0) { + return compare; + } + } + + if (thisSize < otherSize) { + return -1; + } else if (thisSize > otherSize) { + return 1; + } + + return 0; + } + + /** + * Entry in the list. + */ + public static class Entry implements Comparable { + /** {@code non-null;} type of exception handled */ + private final CstType exceptionType; + + /** {@code >= 0;} exception handler address */ + private final int handler; + + /** + * Constructs an instance. + * + * @param exceptionType {@code non-null;} type of exception handled + * @param handler {@code >= 0;} exception handler address + */ + public Entry(CstType exceptionType, int handler) { + if (handler < 0) { + throw new IllegalArgumentException("handler < 0"); + } + + if (exceptionType == null) { + throw new NullPointerException("exceptionType == null"); + } + + this.handler = handler; + this.exceptionType = exceptionType; + } + + /** {@inheritDoc} */ + @Override + public int hashCode() { + return (handler * 31) + exceptionType.hashCode(); + } + + /** {@inheritDoc} */ + @Override + public boolean equals(Object other) { + if (other instanceof Entry) { + return (compareTo((Entry) other) == 0); + } + + return false; + } + + /** {@inheritDoc} */ + public int compareTo(Entry other) { + if (handler < other.handler) { + return -1; + } else if (handler > other.handler) { + return 1; + } + + return exceptionType.compareTo(other.exceptionType); + } + + /** + * Gets the exception type handled. + * + * @return {@code non-null;} the exception type + */ + public CstType getExceptionType() { + return exceptionType; + } + + /** + * Gets the handler address. + * + * @return {@code >= 0;} the handler address + */ + public int getHandler() { + return handler; + } + } +} diff --git a/dx/src/com/android/jack/dx/dex/code/CatchTable.java b/dx/src/com/android/jack/dx/dex/code/CatchTable.java new file mode 100644 index 00000000..6e10e8aa --- /dev/null +++ b/dx/src/com/android/jack/dx/dex/code/CatchTable.java @@ -0,0 +1,192 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.dex.code; + +import com.android.jack.dx.rop.cst.CstType; +import com.android.jack.dx.util.FixedSizeList; + +/** + * Table of catch entries. Each entry includes a range of code + * addresses for which it is valid and an associated {@link + * CatchHandlerList}. + */ +public final class CatchTable extends FixedSizeList + implements Comparable { + /** {@code non-null;} empty instance */ + public static final CatchTable EMPTY = new CatchTable(0); + + /** + * Constructs an instance. All indices initially contain {@code null}. + * + * @param size {@code >= 0;} the size of the table + */ + public CatchTable(int size) { + super(size); + } + + /** + * Gets the element at the given index. It is an error to call + * this with the index for an element which was never set; if you + * do that, this will throw {@code NullPointerException}. + * + * @param n {@code >= 0, < size();} which index + * @return {@code non-null;} element at that index + */ + public Entry get(int n) { + return (Entry) get0(n); + } + + /** + * Sets the entry at the given index. + * + * @param n {@code >= 0, < size();} which index + * @param entry {@code non-null;} the entry to set at {@code n} + */ + public void set(int n, Entry entry) { + set0(n, entry); + } + + /** {@inheritDoc} */ + public int compareTo(CatchTable other) { + if (this == other) { + // Easy out. + return 0; + } + + int thisSize = size(); + int otherSize = other.size(); + int checkSize = Math.min(thisSize, otherSize); + + for (int i = 0; i < checkSize; i++) { + Entry thisEntry = get(i); + Entry otherEntry = other.get(i); + int compare = thisEntry.compareTo(otherEntry); + if (compare != 0) { + return compare; + } + } + + if (thisSize < otherSize) { + return -1; + } else if (thisSize > otherSize) { + return 1; + } + + return 0; + } + + /** + * Entry in a catch list. + */ + public static class Entry implements Comparable { + /** {@code >= 0;} start address */ + private final int start; + + /** {@code > start;} end address (exclusive) */ + private final int end; + + /** {@code non-null;} list of catch handlers */ + private final CatchHandlerList handlers; + + /** + * Constructs an instance. + * + * @param start {@code >= 0;} start address + * @param end {@code > start;} end address (exclusive) + * @param handlers {@code non-null;} list of catch handlers + */ + public Entry(int start, int end, CatchHandlerList handlers) { + if (start < 0) { + throw new IllegalArgumentException("start < 0"); + } + + if (end <= start) { + throw new IllegalArgumentException("end <= start"); + } + + if (handlers.isMutable()) { + throw new IllegalArgumentException("handlers.isMutable()"); + } + + this.start = start; + this.end = end; + this.handlers = handlers; + } + + /** {@inheritDoc} */ + @Override + public int hashCode() { + int hash = (start * 31) + end; + hash = (hash * 31) + handlers.hashCode(); + return hash; + } + + /** {@inheritDoc} */ + @Override + public boolean equals(Object other) { + if (other instanceof Entry) { + return (compareTo((Entry) other) == 0); + } + + return false; + } + + /** {@inheritDoc} */ + public int compareTo(Entry other) { + if (start < other.start) { + return -1; + } else if (start > other.start) { + return 1; + } + + if (end < other.end) { + return -1; + } else if (end > other.end) { + return 1; + } + + return handlers.compareTo(other.handlers); + } + + /** + * Gets the start address. + * + * @return {@code >= 0;} the start address + */ + public int getStart() { + return start; + } + + /** + * Gets the end address (exclusive). + * + * @return {@code > start;} the end address (exclusive) + */ + public int getEnd() { + return end; + } + + /** + * Gets the handlers. + * + * @return {@code non-null;} the handlers + */ + public CatchHandlerList getHandlers() { + return handlers; + } + } +} diff --git a/dx/src/com/android/jack/dx/dex/code/CodeAddress.java b/dx/src/com/android/jack/dx/dex/code/CodeAddress.java new file mode 100644 index 00000000..bb4904db --- /dev/null +++ b/dx/src/com/android/jack/dx/dex/code/CodeAddress.java @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.dex.code; + +import com.android.jack.dx.rop.code.RegisterSpecList; +import com.android.jack.dx.rop.code.SourcePosition; + +/** + * Pseudo-instruction which is used to track an address within a code + * array. Instances are used for such things as branch targets and + * exception handler ranges. Its code size is zero, and so instances + * do not in general directly wind up in any output (either + * human-oriented or binary file). + */ +public final class CodeAddress extends ZeroSizeInsn { + /** If this address should bind closely to the following real instruction */ + private final boolean bindsClosely; + + /** + * Constructs an instance. The output address of this instance is initially + * unknown ({@code -1}). + * + * @param position {@code non-null;} source position + */ + public CodeAddress(SourcePosition position) { + this(position, false); + } + + /** + * Constructs an instance. The output address of this instance is initially + * unknown ({@code -1}). + * + * @param position {@code non-null;} source position + * @param bindsClosely if the address should bind closely to the following + * real instruction. + */ + public CodeAddress(SourcePosition position, boolean bindsClosely) { + super(position); + this.bindsClosely = bindsClosely; + } + + /** {@inheritDoc} */ + @Override + public final DalvInsn withRegisters(RegisterSpecList registers) { + return new CodeAddress(getPosition()); + } + + /** {@inheritDoc} */ + @Override + protected String argString() { + return null; + } + + /** {@inheritDoc} */ + @Override + protected String listingString0(boolean noteIndices) { + return "code-address"; + } + + /** + * Gets whether this address binds closely to the following "real" + * (non-zero-length) instruction. + * + * When a prefix is added to an instruction (for example, to move a value + * from a high register to a low register), this determines whether this + * {@code CodeAddress} will point to the prefix, or to the instruction + * itself. + * + * If bindsClosely is true, the address will point to the instruction + * itself, otherwise it will point to the prefix (if any) + * + * @return true if this address binds closely to the next real instruction + */ + public boolean getBindsClosely() { + return bindsClosely; + } +} diff --git a/dx/src/com/android/jack/dx/dex/code/CstInsn.java b/dx/src/com/android/jack/dx/dex/code/CstInsn.java new file mode 100644 index 00000000..a83e4853 --- /dev/null +++ b/dx/src/com/android/jack/dx/dex/code/CstInsn.java @@ -0,0 +1,205 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.dex.code; + +import com.android.jack.dx.rop.code.RegisterSpecList; +import com.android.jack.dx.rop.code.SourcePosition; +import com.android.jack.dx.rop.cst.Constant; + +/** + * Instruction which has a single constant argument in addition + * to all the normal instruction information. + */ +public final class CstInsn extends FixedSizeInsn { + /** {@code non-null;} the constant argument for this instruction */ + private final Constant constant; + + /** + * {@code >= -1;} the constant pool index for {@link #constant}, or + * {@code -1} if not yet set + */ + private int index; + + /** + * {@code >= -1;} the constant pool index for the class reference in + * {@link #constant} if any, or {@code -1} if not yet set + */ + private int classIndex; + + /** + * Constructs an instance. The output address of this instance is + * initially unknown ({@code -1}) as is the constant pool index. + * + * @param opcode the opcode; one of the constants from {@link Dops} + * @param position {@code non-null;} source position + * @param registers {@code non-null;} register list, including a + * result register if appropriate (that is, registers may be either + * ins or outs) + * @param constant {@code non-null;} constant argument + */ + public CstInsn(Dop opcode, SourcePosition position, + RegisterSpecList registers, Constant constant) { + super(opcode, position, registers); + + if (constant == null) { + throw new NullPointerException("constant == null"); + } + + this.constant = constant; + this.index = -1; + this.classIndex = -1; + } + + /** {@inheritDoc} */ + @Override + public DalvInsn withOpcode(Dop opcode) { + CstInsn result = + new CstInsn(opcode, getPosition(), getRegisters(), constant); + + if (index >= 0) { + result.setIndex(index); + } + + if (classIndex >= 0) { + result.setClassIndex(classIndex); + } + + return result; + } + + /** {@inheritDoc} */ + @Override + public DalvInsn withRegisters(RegisterSpecList registers) { + CstInsn result = + new CstInsn(getOpcode(), getPosition(), registers, constant); + + if (index >= 0) { + result.setIndex(index); + } + + if (classIndex >= 0) { + result.setClassIndex(classIndex); + } + + return result; + } + + /** + * Gets the constant argument. + * + * @return {@code non-null;} the constant argument + */ + public Constant getConstant() { + return constant; + } + + /** + * Gets the constant's index. It is only valid to call this after + * {@link #setIndex} has been called. + * + * @return {@code >= 0;} the constant pool index + */ + public int getIndex() { + if (index < 0) { + throw new RuntimeException("index not yet set for " + constant); + } + + return index; + } + + /** + * Returns whether the constant's index has been set for this instance. + * + * @see #setIndex + * + * @return {@code true} iff the index has been set + */ + public boolean hasIndex() { + return (index >= 0); + } + + /** + * Sets the constant's index. It is only valid to call this method once + * per instance. + * + * @param index {@code >= 0;} the constant pool index + */ + public void setIndex(int index) { + if (index < 0) { + throw new IllegalArgumentException("index < 0"); + } + + if (this.index >= 0) { + throw new RuntimeException("index already set"); + } + + this.index = index; + } + + /** + * Gets the constant's class index. It is only valid to call this after + * {@link #setClassIndex} has been called. + * + * @return {@code >= 0;} the constant's class's constant pool index + */ + public int getClassIndex() { + if (classIndex < 0) { + throw new RuntimeException("class index not yet set"); + } + + return classIndex; + } + + /** + * Returns whether the constant's class index has been set for this + * instance. + * + * @see #setClassIndex + * + * @return {@code true} iff the index has been set + */ + public boolean hasClassIndex() { + return (classIndex >= 0); + } + + /** + * Sets the constant's class index. This is the constant pool index + * for the class referred to by this instance's constant. Only + * reference constants have a class, so it is only on instances + * with reference constants that this method should ever be + * called. It is only valid to call this method once per instance. + * + * @param index {@code >= 0;} the constant's class's constant pool index + */ + public void setClassIndex(int index) { + if (index < 0) { + throw new IllegalArgumentException("index < 0"); + } + + if (this.classIndex >= 0) { + throw new RuntimeException("class index already set"); + } + + this.classIndex = index; + } + + /** {@inheritDoc} */ + @Override + protected String argString() { + return constant.toHuman(); + } +} diff --git a/dx/src/com/android/jack/dx/dex/code/DalvCode.java b/dx/src/com/android/jack/dx/dex/code/DalvCode.java new file mode 100644 index 00000000..e691ef1b --- /dev/null +++ b/dx/src/com/android/jack/dx/dex/code/DalvCode.java @@ -0,0 +1,232 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.dex.code; + +import com.android.jack.dx.rop.cst.Constant; +import com.android.jack.dx.rop.type.Type; + +import java.util.HashSet; + +/** + * Container for all the pieces of a concrete method. Each instance + * corresponds to a {@code code} structure in a {@code .dex} file. + */ +public final class DalvCode { + /** + * how much position info to preserve; one of the static + * constants in {@link PositionList} + */ + private final int positionInfo; + + /** + * {@code null-ok;} the instruction list, ready for final processing; + * nulled out in {@link #finishProcessingIfNecessary} + */ + private OutputFinisher unprocessedInsns; + + /** + * {@code non-null;} unprocessed catch table; + * nulled out in {@link #finishProcessingIfNecessary} + */ + private CatchBuilder unprocessedCatches; + + /** + * {@code null-ok;} catch table; set in + * {@link #finishProcessingIfNecessary} + */ + private CatchTable catches; + + /** + * {@code null-ok;} source positions list; set in + * {@link #finishProcessingIfNecessary} + */ + private PositionList positions; + + /** + * {@code null-ok;} local variable list; set in + * {@link #finishProcessingIfNecessary} + */ + private LocalList locals; + + /** + * {@code null-ok;} the processed instruction list; set in + * {@link #finishProcessingIfNecessary} + */ + private DalvInsnList insns; + + /** + * Constructs an instance. + * + * @param positionInfo how much position info to preserve; one of the + * static constants in {@link PositionList} + * @param unprocessedInsns {@code non-null;} the instruction list, ready + * for final processing + * @param unprocessedCatches {@code non-null;} unprocessed catch + * (exception handler) table + */ + public DalvCode(int positionInfo, OutputFinisher unprocessedInsns, + CatchBuilder unprocessedCatches) { + if (unprocessedInsns == null) { + throw new NullPointerException("unprocessedInsns == null"); + } + + if (unprocessedCatches == null) { + throw new NullPointerException("unprocessedCatches == null"); + } + + this.positionInfo = positionInfo; + this.unprocessedInsns = unprocessedInsns; + this.unprocessedCatches = unprocessedCatches; + this.catches = null; + this.positions = null; + this.locals = null; + this.insns = null; + } + + /** + * Finish up processing of the method. + */ + private void finishProcessingIfNecessary() { + if (insns != null) { + return; + } + + insns = unprocessedInsns.finishProcessingAndGetList(); + positions = PositionList.make(insns, positionInfo); + locals = LocalList.make(insns); + catches = unprocessedCatches.build(); + + // Let them be gc'ed. + unprocessedInsns = null; + unprocessedCatches = null; + } + + /** + * Assign indices in all instructions that need them, using the + * given callback to perform lookups. This must be called before + * {@link #getInsns}. + * + * @param callback {@code non-null;} callback object + */ + public void assignIndices(AssignIndicesCallback callback) { + unprocessedInsns.assignIndices(callback); + } + + /** + * Gets whether this instance has any position data to represent. + * + * @return {@code true} iff this instance has any position + * data to represent + */ + public boolean hasPositions() { + return (positionInfo != PositionList.NONE) + && unprocessedInsns.hasAnyPositionInfo(); + } + + /** + * Gets whether this instance has any local variable data to represent. + * + * @return {@code true} iff this instance has any local variable + * data to represent + */ + public boolean hasLocals() { + return unprocessedInsns.hasAnyLocalInfo(); + } + + /** + * Gets whether this instance has any catches at all (either typed + * or catch-all). + * + * @return whether this instance has any catches at all + */ + public boolean hasAnyCatches() { + return unprocessedCatches.hasAnyCatches(); + } + + /** + * Gets the set of catch types handled anywhere in the code. + * + * @return {@code non-null;} the set of catch types + */ + public HashSet getCatchTypes() { + return unprocessedCatches.getCatchTypes(); + } + + /** + * Gets the set of all constants referred to by instructions in + * the code. + * + * @return {@code non-null;} the set of constants + */ + public HashSet getInsnConstants() { + return unprocessedInsns.getAllConstants(); + } + + /** + * Gets the list of instructions. + * + * @return {@code non-null;} the instruction list + */ + public DalvInsnList getInsns() { + finishProcessingIfNecessary(); + return insns; + } + + /** + * Gets the catch (exception handler) table. + * + * @return {@code non-null;} the catch table + */ + public CatchTable getCatches() { + finishProcessingIfNecessary(); + return catches; + } + + /** + * Gets the source positions list. + * + * @return {@code non-null;} the source positions list + */ + public PositionList getPositions() { + finishProcessingIfNecessary(); + return positions; + } + + /** + * Gets the source positions list. + * + * @return {@code non-null;} the source positions list + */ + public LocalList getLocals() { + finishProcessingIfNecessary(); + return locals; + } + + /** + * Class used as a callback for {@link #assignIndices}. + */ + public static interface AssignIndicesCallback { + /** + * Gets the index for the given constant. + * + * @param cst {@code non-null;} the constant + * @return {@code >= -1;} the index or {@code -1} if the constant + * shouldn't actually be reified with an index + */ + public int getIndex(Constant cst); + } +} diff --git a/dx/src/com/android/jack/dx/dex/code/DalvInsn.java b/dx/src/com/android/jack/dx/dex/code/DalvInsn.java new file mode 100644 index 00000000..a6a501e7 --- /dev/null +++ b/dx/src/com/android/jack/dx/dex/code/DalvInsn.java @@ -0,0 +1,450 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.dex.code; + +import com.android.jack.dx.rop.code.RegisterSpec; +import com.android.jack.dx.rop.code.RegisterSpecList; +import com.android.jack.dx.rop.code.SourcePosition; +import com.android.jack.dx.util.AnnotatedOutput; +import com.android.jack.dx.util.Hex; +import com.android.jack.dx.util.TwoColumnOutput; + +import java.util.BitSet; + +/** + * Base class for Dalvik instructions. + */ +public abstract class DalvInsn { + /** + * the actual output address of this instance, if known, or + * {@code -1} if not + */ + private int address; + + /** the opcode; one of the constants from {@link Dops} */ + private final Dop opcode; + + /** {@code non-null;} source position */ + private final SourcePosition position; + + /** {@code non-null;} list of register arguments */ + private final RegisterSpecList registers; + + /** + * Makes a move instruction, appropriate and ideal for the given arguments. + * + * @param position {@code non-null;} source position information + * @param dest {@code non-null;} destination register + * @param src {@code non-null;} source register + * @return {@code non-null;} an appropriately-constructed instance + */ + public static SimpleInsn makeMove(SourcePosition position, + RegisterSpec dest, RegisterSpec src) { + boolean category1 = dest.getCategory() == 1; + boolean reference = dest.getType().isReference(); + int destReg = dest.getReg(); + int srcReg = src.getReg(); + Dop opcode; + + if ((srcReg | destReg) < 16) { + opcode = reference ? Dops.MOVE_OBJECT : + (category1 ? Dops.MOVE : Dops.MOVE_WIDE); + } else if (destReg < 256) { + opcode = reference ? Dops.MOVE_OBJECT_FROM16 : + (category1 ? Dops.MOVE_FROM16 : Dops.MOVE_WIDE_FROM16); + } else { + opcode = reference ? Dops.MOVE_OBJECT_16 : + (category1 ? Dops.MOVE_16 : Dops.MOVE_WIDE_16); + } + + return new SimpleInsn(opcode, position, + RegisterSpecList.make(dest, src)); + } + + /** + * Constructs an instance. The output address of this instance is initially + * unknown ({@code -1}). + * + *

Note: In the unlikely event that an instruction takes + * absolutely no registers (e.g., a {@code nop} or a + * no-argument no-result static method call), then the given + * register list may be passed as {@link + * RegisterSpecList#EMPTY}.

+ * + * @param opcode the opcode; one of the constants from {@link Dops} + * @param position {@code non-null;} source position + * @param registers {@code non-null;} register list, including a + * result register if appropriate (that is, registers may be either + * ins and outs) + */ + public DalvInsn(Dop opcode, SourcePosition position, + RegisterSpecList registers) { + if (opcode == null) { + throw new NullPointerException("opcode == null"); + } + + if (position == null) { + throw new NullPointerException("position == null"); + } + + if (registers == null) { + throw new NullPointerException("registers == null"); + } + + this.address = -1; + this.opcode = opcode; + this.position = position; + this.registers = registers; + } + + /** {@inheritDoc} */ + @Override + public final String toString() { + StringBuffer sb = new StringBuffer(100); + + sb.append(identifierString()); + sb.append(' '); + sb.append(position); + + sb.append(": "); + sb.append(opcode.getName()); + + boolean needComma = false; + if (registers.size() != 0) { + sb.append(registers.toHuman(" ", ", ", null)); + needComma = true; + } + + String extra = argString(); + if (extra != null) { + if (needComma) { + sb.append(','); + } + sb.append(' '); + sb.append(extra); + } + + return sb.toString(); + } + + /** + * Gets whether the address of this instruction is known. + * + * @see #getAddress + * @see #setAddress + */ + public final boolean hasAddress() { + return (address >= 0); + } + + /** + * Gets the output address of this instruction, if it is known. This throws + * a {@code RuntimeException} if it has not yet been set. + * + * @see #setAddress + * + * @return {@code >= 0;} the output address + */ + public final int getAddress() { + if (address < 0) { + throw new RuntimeException("address not yet known"); + } + + return address; + } + + /** + * Gets the opcode. + * + * @return {@code non-null;} the opcode + */ + public final Dop getOpcode() { + return opcode; + } + + /** + * Gets the source position. + * + * @return {@code non-null;} the source position + */ + public final SourcePosition getPosition() { + return position; + } + + /** + * Gets the register list for this instruction. + * + * @return {@code non-null;} the registers + */ + public final RegisterSpecList getRegisters() { + return registers; + } + + /** + * Returns whether this instance's opcode uses a result register. + * This method is a convenient shorthand for + * {@code getOpcode().hasResult()}. + * + * @return {@code true} iff this opcode uses a result register + */ + public final boolean hasResult() { + return opcode.hasResult(); + } + + /** + * Gets the minimum distinct registers required for this instruction. + * Uses the given BitSet to determine which registers require + * replacement, and ignores registers that are already compatible. + * This assumes that the result (if any) can share registers with the + * sources (if any), that each source register is unique, and that + * (to be explicit here) category-2 values take up two consecutive + * registers. + * + * @param compatRegs {@code non-null;} set of compatible registers + * @return {@code >= 0;} the minimum distinct register requirement + */ + public final int getMinimumRegisterRequirement(BitSet compatRegs) { + boolean hasResult = hasResult(); + int regSz = registers.size(); + int resultRequirement = 0; + int sourceRequirement = 0; + + if (hasResult && !compatRegs.get(0)) { + resultRequirement = registers.get(0).getCategory(); + } + + for (int i = hasResult ? 1 : 0; i < regSz; i++) { + if (!compatRegs.get(i)) { + sourceRequirement += registers.get(i).getCategory(); + } + } + + return Math.max(sourceRequirement, resultRequirement); + } + + /** + * Gets the instruction that is equivalent to this one, except that + * it uses sequential registers starting at {@code 0} (storing + * the result, if any, in register {@code 0} as well). + * + * @return {@code non-null;} the replacement + */ + public DalvInsn getLowRegVersion() { + RegisterSpecList regs = + registers.withExpandedRegisters(0, hasResult(), null); + return withRegisters(regs); + } + + /** + * Gets the instruction prefix required, if any, to use in an expanded + * version of this instance. Will not generate moves for registers + * marked compatible to the format by the given BitSet. + * + * @see #expandedVersion + * + * @param compatRegs {@code non-null;} set of compatible registers + * @return {@code null-ok;} the prefix, if any + */ + public DalvInsn expandedPrefix(BitSet compatRegs) { + RegisterSpecList regs = registers; + boolean firstBit = compatRegs.get(0); + + if (hasResult()) compatRegs.set(0); + + regs = regs.subset(compatRegs); + + if (hasResult()) compatRegs.set(0, firstBit); + + if (regs.size() == 0) return null; + + return new HighRegisterPrefix(position, regs); + } + + /** + * Gets the instruction suffix required, if any, to use in an expanded + * version of this instance. Will not generate a move for a register + * marked compatible to the format by the given BitSet. + * + * @see #expandedVersion + * + * @param compatRegs {@code non-null;} set of compatible registers + * @return {@code null-ok;} the suffix, if any + */ + public DalvInsn expandedSuffix(BitSet compatRegs) { + if (hasResult() && !compatRegs.get(0)) { + RegisterSpec r = registers.get(0); + return makeMove(position, r, r.withReg(0)); + } else { + return null; + } + } + + /** + * Gets the instruction that is equivalent to this one, except that + * it replaces incompatible registers with sequential registers + * starting at {@code 0} (storing the result, if any, in register + * {@code 0} as well). The sequence of instructions from + * {@link #expandedPrefix} and {@link #expandedSuffix} (if non-null) + * surrounding the result of a call to this method are the expanded + * transformation of this instance, and it is guaranteed that the + * number of low registers used will be the number returned by + * {@link #getMinimumRegisterRequirement}. + * + * @param compatRegs {@code non-null;} set of compatible registers + * @return {@code non-null;} the replacement + */ + public DalvInsn expandedVersion(BitSet compatRegs) { + RegisterSpecList regs = + registers.withExpandedRegisters(0, hasResult(), compatRegs); + return withRegisters(regs); + } + + /** + * Gets the short identifier for this instruction. This is its + * address, if assigned, or its identity hashcode if not. + * + * @return {@code non-null;} the identifier + */ + public final String identifierString() { + if (address != -1) { + return String.format("%04x", address); + } + + return Hex.u4(System.identityHashCode(this)); + } + + /** + * Returns the string form of this instance suitable for inclusion in + * a human-oriented listing dump. This method will return {@code null} + * if this instance should not appear in a listing. + * + * @param prefix {@code non-null;} prefix before the address; each follow-on + * line will be indented to match as well + * @param width {@code >= 0;} the width of the output or {@code 0} for + * unlimited width + * @param noteIndices whether to include an explicit notation of + * constant pool indices + * @return {@code null-ok;} the string form or {@code null} if this + * instance should not appear in a listing + */ + public final String listingString(String prefix, int width, + boolean noteIndices) { + String insnPerSe = listingString0(noteIndices); + + if (insnPerSe == null) { + return null; + } + + String addr = prefix + identifierString() + ": "; + int w1 = addr.length(); + int w2 = (width == 0) ? insnPerSe.length() : (width - w1); + + return TwoColumnOutput.toString(addr, w1, "", insnPerSe, w2); + } + + /** + * Sets the output address. + * + * @param address {@code >= 0;} the output address + */ + public final void setAddress(int address) { + if (address < 0) { + throw new IllegalArgumentException("address < 0"); + } + + this.address = address; + } + + /** + * Gets the address immediately after this instance. This is only + * calculable if this instance's address is known, and it is equal + * to the address plus the length of the instruction format of this + * instance's opcode. + * + * @return {@code >= 0;} the next address + */ + public final int getNextAddress() { + return getAddress() + codeSize(); + } + + /** + * Gets the size of this instruction, in 16-bit code units. + * + * @return {@code >= 0;} the code size of this instruction + */ + public abstract int codeSize(); + + /** + * Writes this instance to the given output. This method should + * never annotate the output. + * + * @param out {@code non-null;} where to write to + */ + public abstract void writeTo(AnnotatedOutput out); + + /** + * Returns an instance that is just like this one, except that its + * opcode is replaced by the one given, and its address is reset. + * + * @param opcode {@code non-null;} the new opcode + * @return {@code non-null;} an appropriately-constructed instance + */ + public abstract DalvInsn withOpcode(Dop opcode); + + /** + * Returns an instance that is just like this one, except that all + * register references have been offset by the given delta, and its + * address is reset. + * + * @param delta the amount to offset register references by + * @return {@code non-null;} an appropriately-constructed instance + */ + public abstract DalvInsn withRegisterOffset(int delta); + + /** + * Returns an instance that is just like this one, except that the + * register list is replaced by the given one, and its address is + * reset. + * + * @param registers {@code non-null;} new register list + * @return {@code non-null;} an appropriately-constructed instance + */ + public abstract DalvInsn withRegisters(RegisterSpecList registers); + + /** + * Gets the string form for any arguments to this instance. Subclasses + * must override this. + * + * @return {@code null-ok;} the string version of any arguments or + * {@code null} if there are none + */ + protected abstract String argString(); + + /** + * Helper for {@link #listingString}, which returns the string + * form of this instance suitable for inclusion in a + * human-oriented listing dump, not including the instruction + * address and without respect for any output formatting. This + * method should return {@code null} if this instance should + * not appear in a listing. + * + * @param noteIndices whether to include an explicit notation of + * constant pool indices + * @return {@code null-ok;} the listing string + */ + protected abstract String listingString0(boolean noteIndices); +} diff --git a/dx/src/com/android/jack/dx/dex/code/DalvInsnList.java b/dx/src/com/android/jack/dx/dex/code/DalvInsnList.java new file mode 100644 index 00000000..80fe920f --- /dev/null +++ b/dx/src/com/android/jack/dx/dex/code/DalvInsnList.java @@ -0,0 +1,269 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.dex.code; + +import com.android.jack.dx.io.Opcodes; +import com.android.jack.dx.rop.cst.Constant; +import com.android.jack.dx.rop.cst.CstBaseMethodRef; +import com.android.jack.dx.util.AnnotatedOutput; +import com.android.jack.dx.util.ExceptionWithContext; +import com.android.jack.dx.util.FixedSizeList; +import com.android.jack.dx.util.IndentingWriter; + +import java.io.IOException; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.Writer; +import java.util.ArrayList; + +/** + * List of {@link DalvInsn} instances. + */ +public final class DalvInsnList extends FixedSizeList { + + /** + * The amount of register space, in register units, required for this + * code block. This may be greater than the largest observed register+ + * category because the method this code block exists in may + * specify arguments that are unused by the method. + */ + private final int regCount; + + /** + * Constructs and returns an immutable instance whose elements are + * identical to the ones in the given list, in the same order. + * + * @param list {@code non-null;} the list to use for elements + * @param regCount count, in register-units, of the number of registers + * this code block requires. + * @return {@code non-null;} an appropriately-constructed instance of this + * class + */ + public static DalvInsnList makeImmutable(ArrayList list, + int regCount) { + int size = list.size(); + DalvInsnList result = new DalvInsnList(size, regCount); + + for (int i = 0; i < size; i++) { + result.set(i, list.get(i)); + } + + result.setImmutable(); + return result; + } + + /** + * Constructs an instance. All indices initially contain {@code null}. + * + * @param size the size of the list + */ + public DalvInsnList(int size, int regCount) { + super(size); + this.regCount = regCount; + } + + /** + * Gets the element at the given index. It is an error to call + * this with the index for an element which was never set; if you + * do that, this will throw {@code NullPointerException}. + * + * @param n {@code >= 0, < size();} which index + * @return {@code non-null;} element at that index + */ + public DalvInsn get(int n) { + return (DalvInsn) get0(n); + } + + /** + * Sets the instruction at the given index. + * + * @param n {@code >= 0, < size();} which index + * @param insn {@code non-null;} the instruction to set at {@code n} + */ + public void set(int n, DalvInsn insn) { + set0(n, insn); + } + + /** + * Gets the size of this instance, in 16-bit code units. This will only + * return a meaningful result if the instructions in this instance all + * have valid addresses. + * + * @return {@code >= 0;} the size + */ + public int codeSize() { + int sz = size(); + + if (sz == 0) { + return 0; + } + + DalvInsn last = get(sz - 1); + return last.getNextAddress(); + } + + /** + * Writes all the instructions in this instance to the given output + * destination. + * + * @param out {@code non-null;} where to write to + */ + public void writeTo(AnnotatedOutput out) { + int startCursor = out.getCursor(); + int sz = size(); + + if (out.annotates()) { + boolean verbose = out.isVerbose(); + + for (int i = 0; i < sz; i++) { + DalvInsn insn = (DalvInsn) get0(i); + int codeBytes = insn.codeSize() * 2; + String s; + + if ((codeBytes != 0) || verbose) { + s = insn.listingString(" ", out.getAnnotationWidth(), + true); + } else { + s = null; + } + + if (s != null) { + out.annotate(codeBytes, s); + } else if (codeBytes != 0) { + out.annotate(codeBytes, ""); + } + } + } + + for (int i = 0; i < sz; i++) { + DalvInsn insn = (DalvInsn) get0(i); + try { + insn.writeTo(out); + } catch (RuntimeException ex) { + throw ExceptionWithContext.withContext(ex, + "...while writing " + insn); + } + } + + // Sanity check of the amount written. + int written = (out.getCursor() - startCursor) / 2; + if (written != codeSize()) { + throw new RuntimeException("write length mismatch; expected " + + codeSize() + " but actually wrote " + written); + } + } + + /** + * Gets the minimum required register count implied by this + * instance. This includes any unused parameters that could + * potentially be at the top of the register space. + * @return {@code >= 0;} the required registers size + */ + public int getRegistersSize() { + return regCount; + } + + /** + * Gets the size of the outgoing arguments area required by this + * method. This is equal to the largest argument word count of any + * method referred to by this instance. + * + * @return {@code >= 0;} the required outgoing arguments size + */ + public int getOutsSize() { + int sz = size(); + int result = 0; + + for (int i = 0; i < sz; i++) { + DalvInsn insn = (DalvInsn) get0(i); + + if (!(insn instanceof CstInsn)) { + continue; + } + + Constant cst = ((CstInsn) insn).getConstant(); + + if (!(cst instanceof CstBaseMethodRef)) { + continue; + } + + boolean isStatic = + (insn.getOpcode().getFamily() == Opcodes.INVOKE_STATIC); + int count = + ((CstBaseMethodRef) cst).getParameterWordCount(isStatic); + + if (count > result) { + result = count; + } + } + + return result; + } + + /** + * Does a human-friendly dump of this instance. + * + * @param out {@code non-null;} where to dump + * @param prefix {@code non-null;} prefix to attach to each line of output + * @param verbose whether to be verbose; verbose output includes + * lines for zero-size instructions and explicit constant pool indices + */ + public void debugPrint(Writer out, String prefix, boolean verbose) { + IndentingWriter iw = new IndentingWriter(out, 0, prefix); + int sz = size(); + + try { + for (int i = 0; i < sz; i++) { + DalvInsn insn = (DalvInsn) get0(i); + String s; + + if ((insn.codeSize() != 0) || verbose) { + s = insn.listingString("", 0, verbose); + } else { + s = null; + } + + if (s != null) { + iw.write(s); + } + } + + iw.flush(); + } catch (IOException ex) { + throw new RuntimeException(ex); + } + } + + /** + * Does a human-friendly dump of this instance. + * + * @param out {@code non-null;} where to dump + * @param prefix {@code non-null;} prefix to attach to each line of output + * @param verbose whether to be verbose; verbose output includes + * lines for zero-size instructions + */ + public void debugPrint(OutputStream out, String prefix, boolean verbose) { + Writer w = new OutputStreamWriter(out); + debugPrint(w, prefix, verbose); + + try { + w.flush(); + } catch (IOException ex) { + throw new RuntimeException(ex); + } + } +} diff --git a/dx/src/com/android/jack/dx/dex/code/Dop.java b/dx/src/com/android/jack/dx/dex/code/Dop.java new file mode 100644 index 00000000..5b390aa6 --- /dev/null +++ b/dx/src/com/android/jack/dx/dex/code/Dop.java @@ -0,0 +1,173 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.dex.code; + +import com.android.jack.dx.io.OpcodeInfo; +import com.android.jack.dx.io.Opcodes; + +/** + * Representation of an opcode. + */ +public final class Dop { + /** {@code Opcodes.isValid();} the opcode value itself */ + private final int opcode; + + /** {@code Opcodes.isValid();} the opcode family */ + private final int family; + + /** + * {@code Opcodes.isValid();} what opcode (by number) to try next + * when attempting to match an opcode to particular arguments; + * {@code Opcodes.NO_NEXT} to indicate that this is the last + * opcode to try in a particular chain + */ + private final int nextOpcode; + + /** {@code non-null;} the instruction format */ + private final InsnFormat format; + + /** whether this opcode uses a result register */ + private final boolean hasResult; + + /** + * Constructs an instance. + * + * @param opcode {@code Opcodes.isValid();} the opcode value + * itself + * @param family {@code Opcodes.isValid();} the opcode family + * @param nextOpcode {@code Opcodes.isValid();} what opcode (by + * number) to try next when attempting to match an opcode to + * particular arguments; {@code Opcodes.NO_NEXT} to indicate that + * this is the last opcode to try in a particular chain + * @param format {@code non-null;} the instruction format + * @param hasResult whether the opcode has a result register; if so it + * is always the first register + */ + public Dop(int opcode, int family, int nextOpcode, InsnFormat format, + boolean hasResult) { + if (!Opcodes.isValidShape(opcode)) { + throw new IllegalArgumentException("bogus opcode"); + } + + if (!Opcodes.isValidShape(family)) { + throw new IllegalArgumentException("bogus family"); + } + + if (!Opcodes.isValidShape(nextOpcode)) { + throw new IllegalArgumentException("bogus nextOpcode"); + } + + if (format == null) { + throw new NullPointerException("format == null"); + } + + this.opcode = opcode; + this.family = family; + this.nextOpcode = nextOpcode; + this.format = format; + this.hasResult = hasResult; + } + + /** {@inheritDoc} */ + @Override + public String toString() { + return getName(); + } + + /** + * Gets the opcode value. + * + * @return {@code Opcodes.MIN_VALUE..Opcodes.MAX_VALUE;} the opcode value + */ + public int getOpcode() { + return opcode; + } + + /** + * Gets the opcode family. The opcode family is the unmarked (no + * "/...") opcode that has equivalent semantics to this one. + * + * @return {@code Opcodes.MIN_VALUE..Opcodes.MAX_VALUE;} the opcode family + */ + public int getFamily() { + return family; + } + + /** + * Gets the instruction format. + * + * @return {@code non-null;} the instruction format + */ + public InsnFormat getFormat() { + return format; + } + + /** + * Returns whether this opcode uses a result register. + * + * @return {@code true} iff this opcode uses a result register + */ + public boolean hasResult() { + return hasResult; + } + + /** + * Gets the opcode name. + * + * @return {@code non-null;} the opcode name + */ + public String getName() { + return OpcodeInfo.getName(opcode); + } + + /** + * Gets the opcode value to try next when attempting to match an + * opcode to particular arguments. This returns {@code + * Opcodes.NO_NEXT} to indicate that this is the last opcode to + * try in a particular chain. + * + * @return {@code Opcodes.MIN_VALUE..Opcodes.MAX_VALUE;} the opcode value + */ + public int getNextOpcode() { + return nextOpcode; + } + + /** + * Gets the opcode for the opposite test of this instance. This is only + * valid for opcodes which are in fact tests. + * + * @return {@code non-null;} the opposite test + */ + public Dop getOppositeTest() { + switch (opcode) { + case Opcodes.IF_EQ: return Dops.IF_NE; + case Opcodes.IF_NE: return Dops.IF_EQ; + case Opcodes.IF_LT: return Dops.IF_GE; + case Opcodes.IF_GE: return Dops.IF_LT; + case Opcodes.IF_GT: return Dops.IF_LE; + case Opcodes.IF_LE: return Dops.IF_GT; + case Opcodes.IF_EQZ: return Dops.IF_NEZ; + case Opcodes.IF_NEZ: return Dops.IF_EQZ; + case Opcodes.IF_LTZ: return Dops.IF_GEZ; + case Opcodes.IF_GEZ: return Dops.IF_LTZ; + case Opcodes.IF_GTZ: return Dops.IF_LEZ; + case Opcodes.IF_LEZ: return Dops.IF_GTZ; + } + + throw new IllegalArgumentException("bogus opcode: " + this); + } +} diff --git a/dx/src/com/android/jack/dx/dex/code/Dops.java b/dx/src/com/android/jack/dx/dex/code/Dops.java new file mode 100644 index 00000000..39ea0d35 --- /dev/null +++ b/dx/src/com/android/jack/dx/dex/code/Dops.java @@ -0,0 +1,1229 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.dex.code; + +import com.android.jack.dx.dex.DexOptions; +import com.android.jack.dx.dex.code.form.Form10t; +import com.android.jack.dx.dex.code.form.Form10x; +import com.android.jack.dx.dex.code.form.Form11n; +import com.android.jack.dx.dex.code.form.Form11x; +import com.android.jack.dx.dex.code.form.Form12x; +import com.android.jack.dx.dex.code.form.Form20t; +import com.android.jack.dx.dex.code.form.Form21c; +import com.android.jack.dx.dex.code.form.Form21h; +import com.android.jack.dx.dex.code.form.Form21s; +import com.android.jack.dx.dex.code.form.Form21t; +import com.android.jack.dx.dex.code.form.Form22b; +import com.android.jack.dx.dex.code.form.Form22c; +import com.android.jack.dx.dex.code.form.Form22s; +import com.android.jack.dx.dex.code.form.Form22t; +import com.android.jack.dx.dex.code.form.Form22x; +import com.android.jack.dx.dex.code.form.Form23x; +import com.android.jack.dx.dex.code.form.Form30t; +import com.android.jack.dx.dex.code.form.Form31c; +import com.android.jack.dx.dex.code.form.Form31i; +import com.android.jack.dx.dex.code.form.Form31t; +import com.android.jack.dx.dex.code.form.Form32x; +import com.android.jack.dx.dex.code.form.Form35c; +import com.android.jack.dx.dex.code.form.Form3rc; +import com.android.jack.dx.dex.code.form.Form51l; +import com.android.jack.dx.dex.code.form.SpecialFormat; +import com.android.jack.dx.io.Opcodes; + +/** + * Standard instances of {@link Dop} and utility methods for getting + * them. + */ +public final class Dops { + /** {@code non-null;} array containing all the standard instances */ + private static final Dop[] DOPS; + + /** + * pseudo-opcode used for nonstandard formatted "instructions" + * (which are mostly not actually instructions, though they do + * appear in instruction lists). TODO: Retire the usage of this + * constant. + */ + public static final Dop SPECIAL_FORMAT = + new Dop(Opcodes.SPECIAL_FORMAT, Opcodes.SPECIAL_FORMAT, + Opcodes.NO_NEXT, SpecialFormat.THE_ONE, false); + + // BEGIN(dops); GENERATED AUTOMATICALLY BY opcode-gen + public static final Dop NOP = + new Dop(Opcodes.NOP, Opcodes.NOP, + Opcodes.NO_NEXT, Form10x.THE_ONE, false); + + public static final Dop MOVE = + new Dop(Opcodes.MOVE, Opcodes.MOVE, + Opcodes.MOVE_FROM16, Form12x.THE_ONE, true); + + public static final Dop MOVE_FROM16 = + new Dop(Opcodes.MOVE_FROM16, Opcodes.MOVE, + Opcodes.MOVE_16, Form22x.THE_ONE, true); + + public static final Dop MOVE_16 = + new Dop(Opcodes.MOVE_16, Opcodes.MOVE, + Opcodes.NO_NEXT, Form32x.THE_ONE, true); + + public static final Dop MOVE_WIDE = + new Dop(Opcodes.MOVE_WIDE, Opcodes.MOVE_WIDE, + Opcodes.MOVE_WIDE_FROM16, Form12x.THE_ONE, true); + + public static final Dop MOVE_WIDE_FROM16 = + new Dop(Opcodes.MOVE_WIDE_FROM16, Opcodes.MOVE_WIDE, + Opcodes.MOVE_WIDE_16, Form22x.THE_ONE, true); + + public static final Dop MOVE_WIDE_16 = + new Dop(Opcodes.MOVE_WIDE_16, Opcodes.MOVE_WIDE, + Opcodes.NO_NEXT, Form32x.THE_ONE, true); + + public static final Dop MOVE_OBJECT = + new Dop(Opcodes.MOVE_OBJECT, Opcodes.MOVE_OBJECT, + Opcodes.MOVE_OBJECT_FROM16, Form12x.THE_ONE, true); + + public static final Dop MOVE_OBJECT_FROM16 = + new Dop(Opcodes.MOVE_OBJECT_FROM16, Opcodes.MOVE_OBJECT, + Opcodes.MOVE_OBJECT_16, Form22x.THE_ONE, true); + + public static final Dop MOVE_OBJECT_16 = + new Dop(Opcodes.MOVE_OBJECT_16, Opcodes.MOVE_OBJECT, + Opcodes.NO_NEXT, Form32x.THE_ONE, true); + + public static final Dop MOVE_RESULT = + new Dop(Opcodes.MOVE_RESULT, Opcodes.MOVE_RESULT, + Opcodes.NO_NEXT, Form11x.THE_ONE, true); + + public static final Dop MOVE_RESULT_WIDE = + new Dop(Opcodes.MOVE_RESULT_WIDE, Opcodes.MOVE_RESULT_WIDE, + Opcodes.NO_NEXT, Form11x.THE_ONE, true); + + public static final Dop MOVE_RESULT_OBJECT = + new Dop(Opcodes.MOVE_RESULT_OBJECT, Opcodes.MOVE_RESULT_OBJECT, + Opcodes.NO_NEXT, Form11x.THE_ONE, true); + + public static final Dop MOVE_EXCEPTION = + new Dop(Opcodes.MOVE_EXCEPTION, Opcodes.MOVE_EXCEPTION, + Opcodes.NO_NEXT, Form11x.THE_ONE, true); + + public static final Dop RETURN_VOID = + new Dop(Opcodes.RETURN_VOID, Opcodes.RETURN_VOID, + Opcodes.NO_NEXT, Form10x.THE_ONE, false); + + public static final Dop RETURN = + new Dop(Opcodes.RETURN, Opcodes.RETURN, + Opcodes.NO_NEXT, Form11x.THE_ONE, false); + + public static final Dop RETURN_WIDE = + new Dop(Opcodes.RETURN_WIDE, Opcodes.RETURN_WIDE, + Opcodes.NO_NEXT, Form11x.THE_ONE, false); + + public static final Dop RETURN_OBJECT = + new Dop(Opcodes.RETURN_OBJECT, Opcodes.RETURN_OBJECT, + Opcodes.NO_NEXT, Form11x.THE_ONE, false); + + public static final Dop CONST_4 = + new Dop(Opcodes.CONST_4, Opcodes.CONST, + Opcodes.CONST_16, Form11n.THE_ONE, true); + + public static final Dop CONST_16 = + new Dop(Opcodes.CONST_16, Opcodes.CONST, + Opcodes.CONST_HIGH16, Form21s.THE_ONE, true); + + public static final Dop CONST = + new Dop(Opcodes.CONST, Opcodes.CONST, + Opcodes.NO_NEXT, Form31i.THE_ONE, true); + + public static final Dop CONST_HIGH16 = + new Dop(Opcodes.CONST_HIGH16, Opcodes.CONST, + Opcodes.CONST, Form21h.THE_ONE, true); + + public static final Dop CONST_WIDE_16 = + new Dop(Opcodes.CONST_WIDE_16, Opcodes.CONST_WIDE, + Opcodes.CONST_WIDE_HIGH16, Form21s.THE_ONE, true); + + public static final Dop CONST_WIDE_32 = + new Dop(Opcodes.CONST_WIDE_32, Opcodes.CONST_WIDE, + Opcodes.CONST_WIDE, Form31i.THE_ONE, true); + + public static final Dop CONST_WIDE = + new Dop(Opcodes.CONST_WIDE, Opcodes.CONST_WIDE, + Opcodes.NO_NEXT, Form51l.THE_ONE, true); + + public static final Dop CONST_WIDE_HIGH16 = + new Dop(Opcodes.CONST_WIDE_HIGH16, Opcodes.CONST_WIDE, + Opcodes.CONST_WIDE_32, Form21h.THE_ONE, true); + + public static final Dop CONST_STRING = + new Dop(Opcodes.CONST_STRING, Opcodes.CONST_STRING, + Opcodes.CONST_STRING_JUMBO, Form21c.THE_ONE, true); + + public static final Dop CONST_STRING_JUMBO = + new Dop(Opcodes.CONST_STRING_JUMBO, Opcodes.CONST_STRING, + Opcodes.NO_NEXT, Form31c.THE_ONE, true); + + public static final Dop CONST_CLASS = + new Dop(Opcodes.CONST_CLASS, Opcodes.CONST_CLASS, + Opcodes.NO_NEXT, Form21c.THE_ONE, true); + + public static final Dop MONITOR_ENTER = + new Dop(Opcodes.MONITOR_ENTER, Opcodes.MONITOR_ENTER, + Opcodes.NO_NEXT, Form11x.THE_ONE, false); + + public static final Dop MONITOR_EXIT = + new Dop(Opcodes.MONITOR_EXIT, Opcodes.MONITOR_EXIT, + Opcodes.NO_NEXT, Form11x.THE_ONE, false); + + public static final Dop CHECK_CAST = + new Dop(Opcodes.CHECK_CAST, Opcodes.CHECK_CAST, + Opcodes.NO_NEXT, Form21c.THE_ONE, true); + + public static final Dop INSTANCE_OF = + new Dop(Opcodes.INSTANCE_OF, Opcodes.INSTANCE_OF, + Opcodes.NO_NEXT, Form22c.THE_ONE, true); + + public static final Dop ARRAY_LENGTH = + new Dop(Opcodes.ARRAY_LENGTH, Opcodes.ARRAY_LENGTH, + Opcodes.NO_NEXT, Form12x.THE_ONE, true); + + public static final Dop NEW_INSTANCE = + new Dop(Opcodes.NEW_INSTANCE, Opcodes.NEW_INSTANCE, + Opcodes.NO_NEXT, Form21c.THE_ONE, true); + + public static final Dop NEW_ARRAY = + new Dop(Opcodes.NEW_ARRAY, Opcodes.NEW_ARRAY, + Opcodes.NO_NEXT, Form22c.THE_ONE, true); + + public static final Dop FILLED_NEW_ARRAY = + new Dop(Opcodes.FILLED_NEW_ARRAY, Opcodes.FILLED_NEW_ARRAY, + Opcodes.FILLED_NEW_ARRAY_RANGE, Form35c.THE_ONE, false); + + public static final Dop FILLED_NEW_ARRAY_RANGE = + new Dop(Opcodes.FILLED_NEW_ARRAY_RANGE, Opcodes.FILLED_NEW_ARRAY, + Opcodes.NO_NEXT, Form3rc.THE_ONE, false); + + public static final Dop FILL_ARRAY_DATA = + new Dop(Opcodes.FILL_ARRAY_DATA, Opcodes.FILL_ARRAY_DATA, + Opcodes.NO_NEXT, Form31t.THE_ONE, false); + + public static final Dop THROW = + new Dop(Opcodes.THROW, Opcodes.THROW, + Opcodes.NO_NEXT, Form11x.THE_ONE, false); + + public static final Dop GOTO = + new Dop(Opcodes.GOTO, Opcodes.GOTO, + Opcodes.GOTO_16, Form10t.THE_ONE, false); + + public static final Dop GOTO_16 = + new Dop(Opcodes.GOTO_16, Opcodes.GOTO, + Opcodes.GOTO_32, Form20t.THE_ONE, false); + + public static final Dop GOTO_32 = + new Dop(Opcodes.GOTO_32, Opcodes.GOTO, + Opcodes.NO_NEXT, Form30t.THE_ONE, false); + + public static final Dop PACKED_SWITCH = + new Dop(Opcodes.PACKED_SWITCH, Opcodes.PACKED_SWITCH, + Opcodes.NO_NEXT, Form31t.THE_ONE, false); + + public static final Dop SPARSE_SWITCH = + new Dop(Opcodes.SPARSE_SWITCH, Opcodes.SPARSE_SWITCH, + Opcodes.NO_NEXT, Form31t.THE_ONE, false); + + public static final Dop CMPL_FLOAT = + new Dop(Opcodes.CMPL_FLOAT, Opcodes.CMPL_FLOAT, + Opcodes.NO_NEXT, Form23x.THE_ONE, true); + + public static final Dop CMPG_FLOAT = + new Dop(Opcodes.CMPG_FLOAT, Opcodes.CMPG_FLOAT, + Opcodes.NO_NEXT, Form23x.THE_ONE, true); + + public static final Dop CMPL_DOUBLE = + new Dop(Opcodes.CMPL_DOUBLE, Opcodes.CMPL_DOUBLE, + Opcodes.NO_NEXT, Form23x.THE_ONE, true); + + public static final Dop CMPG_DOUBLE = + new Dop(Opcodes.CMPG_DOUBLE, Opcodes.CMPG_DOUBLE, + Opcodes.NO_NEXT, Form23x.THE_ONE, true); + + public static final Dop CMP_LONG = + new Dop(Opcodes.CMP_LONG, Opcodes.CMP_LONG, + Opcodes.NO_NEXT, Form23x.THE_ONE, true); + + public static final Dop IF_EQ = + new Dop(Opcodes.IF_EQ, Opcodes.IF_EQ, + Opcodes.NO_NEXT, Form22t.THE_ONE, false); + + public static final Dop IF_NE = + new Dop(Opcodes.IF_NE, Opcodes.IF_NE, + Opcodes.NO_NEXT, Form22t.THE_ONE, false); + + public static final Dop IF_LT = + new Dop(Opcodes.IF_LT, Opcodes.IF_LT, + Opcodes.NO_NEXT, Form22t.THE_ONE, false); + + public static final Dop IF_GE = + new Dop(Opcodes.IF_GE, Opcodes.IF_GE, + Opcodes.NO_NEXT, Form22t.THE_ONE, false); + + public static final Dop IF_GT = + new Dop(Opcodes.IF_GT, Opcodes.IF_GT, + Opcodes.NO_NEXT, Form22t.THE_ONE, false); + + public static final Dop IF_LE = + new Dop(Opcodes.IF_LE, Opcodes.IF_LE, + Opcodes.NO_NEXT, Form22t.THE_ONE, false); + + public static final Dop IF_EQZ = + new Dop(Opcodes.IF_EQZ, Opcodes.IF_EQZ, + Opcodes.NO_NEXT, Form21t.THE_ONE, false); + + public static final Dop IF_NEZ = + new Dop(Opcodes.IF_NEZ, Opcodes.IF_NEZ, + Opcodes.NO_NEXT, Form21t.THE_ONE, false); + + public static final Dop IF_LTZ = + new Dop(Opcodes.IF_LTZ, Opcodes.IF_LTZ, + Opcodes.NO_NEXT, Form21t.THE_ONE, false); + + public static final Dop IF_GEZ = + new Dop(Opcodes.IF_GEZ, Opcodes.IF_GEZ, + Opcodes.NO_NEXT, Form21t.THE_ONE, false); + + public static final Dop IF_GTZ = + new Dop(Opcodes.IF_GTZ, Opcodes.IF_GTZ, + Opcodes.NO_NEXT, Form21t.THE_ONE, false); + + public static final Dop IF_LEZ = + new Dop(Opcodes.IF_LEZ, Opcodes.IF_LEZ, + Opcodes.NO_NEXT, Form21t.THE_ONE, false); + + public static final Dop AGET = + new Dop(Opcodes.AGET, Opcodes.AGET, + Opcodes.NO_NEXT, Form23x.THE_ONE, true); + + public static final Dop AGET_WIDE = + new Dop(Opcodes.AGET_WIDE, Opcodes.AGET_WIDE, + Opcodes.NO_NEXT, Form23x.THE_ONE, true); + + public static final Dop AGET_OBJECT = + new Dop(Opcodes.AGET_OBJECT, Opcodes.AGET_OBJECT, + Opcodes.NO_NEXT, Form23x.THE_ONE, true); + + public static final Dop AGET_BOOLEAN = + new Dop(Opcodes.AGET_BOOLEAN, Opcodes.AGET_BOOLEAN, + Opcodes.NO_NEXT, Form23x.THE_ONE, true); + + public static final Dop AGET_BYTE = + new Dop(Opcodes.AGET_BYTE, Opcodes.AGET_BYTE, + Opcodes.NO_NEXT, Form23x.THE_ONE, true); + + public static final Dop AGET_CHAR = + new Dop(Opcodes.AGET_CHAR, Opcodes.AGET_CHAR, + Opcodes.NO_NEXT, Form23x.THE_ONE, true); + + public static final Dop AGET_SHORT = + new Dop(Opcodes.AGET_SHORT, Opcodes.AGET_SHORT, + Opcodes.NO_NEXT, Form23x.THE_ONE, true); + + public static final Dop APUT = + new Dop(Opcodes.APUT, Opcodes.APUT, + Opcodes.NO_NEXT, Form23x.THE_ONE, false); + + public static final Dop APUT_WIDE = + new Dop(Opcodes.APUT_WIDE, Opcodes.APUT_WIDE, + Opcodes.NO_NEXT, Form23x.THE_ONE, false); + + public static final Dop APUT_OBJECT = + new Dop(Opcodes.APUT_OBJECT, Opcodes.APUT_OBJECT, + Opcodes.NO_NEXT, Form23x.THE_ONE, false); + + public static final Dop APUT_BOOLEAN = + new Dop(Opcodes.APUT_BOOLEAN, Opcodes.APUT_BOOLEAN, + Opcodes.NO_NEXT, Form23x.THE_ONE, false); + + public static final Dop APUT_BYTE = + new Dop(Opcodes.APUT_BYTE, Opcodes.APUT_BYTE, + Opcodes.NO_NEXT, Form23x.THE_ONE, false); + + public static final Dop APUT_CHAR = + new Dop(Opcodes.APUT_CHAR, Opcodes.APUT_CHAR, + Opcodes.NO_NEXT, Form23x.THE_ONE, false); + + public static final Dop APUT_SHORT = + new Dop(Opcodes.APUT_SHORT, Opcodes.APUT_SHORT, + Opcodes.NO_NEXT, Form23x.THE_ONE, false); + + public static final Dop IGET = + new Dop(Opcodes.IGET, Opcodes.IGET, + Opcodes.NO_NEXT, Form22c.THE_ONE, true); + + public static final Dop IGET_WIDE = + new Dop(Opcodes.IGET_WIDE, Opcodes.IGET_WIDE, + Opcodes.NO_NEXT, Form22c.THE_ONE, true); + + public static final Dop IGET_OBJECT = + new Dop(Opcodes.IGET_OBJECT, Opcodes.IGET_OBJECT, + Opcodes.NO_NEXT, Form22c.THE_ONE, true); + + public static final Dop IGET_BOOLEAN = + new Dop(Opcodes.IGET_BOOLEAN, Opcodes.IGET_BOOLEAN, + Opcodes.NO_NEXT, Form22c.THE_ONE, true); + + public static final Dop IGET_BYTE = + new Dop(Opcodes.IGET_BYTE, Opcodes.IGET_BYTE, + Opcodes.NO_NEXT, Form22c.THE_ONE, true); + + public static final Dop IGET_CHAR = + new Dop(Opcodes.IGET_CHAR, Opcodes.IGET_CHAR, + Opcodes.NO_NEXT, Form22c.THE_ONE, true); + + public static final Dop IGET_SHORT = + new Dop(Opcodes.IGET_SHORT, Opcodes.IGET_SHORT, + Opcodes.NO_NEXT, Form22c.THE_ONE, true); + + public static final Dop IPUT = + new Dop(Opcodes.IPUT, Opcodes.IPUT, + Opcodes.NO_NEXT, Form22c.THE_ONE, false); + + public static final Dop IPUT_WIDE = + new Dop(Opcodes.IPUT_WIDE, Opcodes.IPUT_WIDE, + Opcodes.NO_NEXT, Form22c.THE_ONE, false); + + public static final Dop IPUT_OBJECT = + new Dop(Opcodes.IPUT_OBJECT, Opcodes.IPUT_OBJECT, + Opcodes.NO_NEXT, Form22c.THE_ONE, false); + + public static final Dop IPUT_BOOLEAN = + new Dop(Opcodes.IPUT_BOOLEAN, Opcodes.IPUT_BOOLEAN, + Opcodes.NO_NEXT, Form22c.THE_ONE, false); + + public static final Dop IPUT_BYTE = + new Dop(Opcodes.IPUT_BYTE, Opcodes.IPUT_BYTE, + Opcodes.NO_NEXT, Form22c.THE_ONE, false); + + public static final Dop IPUT_CHAR = + new Dop(Opcodes.IPUT_CHAR, Opcodes.IPUT_CHAR, + Opcodes.NO_NEXT, Form22c.THE_ONE, false); + + public static final Dop IPUT_SHORT = + new Dop(Opcodes.IPUT_SHORT, Opcodes.IPUT_SHORT, + Opcodes.NO_NEXT, Form22c.THE_ONE, false); + + public static final Dop SGET = + new Dop(Opcodes.SGET, Opcodes.SGET, + Opcodes.NO_NEXT, Form21c.THE_ONE, true); + + public static final Dop SGET_WIDE = + new Dop(Opcodes.SGET_WIDE, Opcodes.SGET_WIDE, + Opcodes.NO_NEXT, Form21c.THE_ONE, true); + + public static final Dop SGET_OBJECT = + new Dop(Opcodes.SGET_OBJECT, Opcodes.SGET_OBJECT, + Opcodes.NO_NEXT, Form21c.THE_ONE, true); + + public static final Dop SGET_BOOLEAN = + new Dop(Opcodes.SGET_BOOLEAN, Opcodes.SGET_BOOLEAN, + Opcodes.NO_NEXT, Form21c.THE_ONE, true); + + public static final Dop SGET_BYTE = + new Dop(Opcodes.SGET_BYTE, Opcodes.SGET_BYTE, + Opcodes.NO_NEXT, Form21c.THE_ONE, true); + + public static final Dop SGET_CHAR = + new Dop(Opcodes.SGET_CHAR, Opcodes.SGET_CHAR, + Opcodes.NO_NEXT, Form21c.THE_ONE, true); + + public static final Dop SGET_SHORT = + new Dop(Opcodes.SGET_SHORT, Opcodes.SGET_SHORT, + Opcodes.NO_NEXT, Form21c.THE_ONE, true); + + public static final Dop SPUT = + new Dop(Opcodes.SPUT, Opcodes.SPUT, + Opcodes.NO_NEXT, Form21c.THE_ONE, false); + + public static final Dop SPUT_WIDE = + new Dop(Opcodes.SPUT_WIDE, Opcodes.SPUT_WIDE, + Opcodes.NO_NEXT, Form21c.THE_ONE, false); + + public static final Dop SPUT_OBJECT = + new Dop(Opcodes.SPUT_OBJECT, Opcodes.SPUT_OBJECT, + Opcodes.NO_NEXT, Form21c.THE_ONE, false); + + public static final Dop SPUT_BOOLEAN = + new Dop(Opcodes.SPUT_BOOLEAN, Opcodes.SPUT_BOOLEAN, + Opcodes.NO_NEXT, Form21c.THE_ONE, false); + + public static final Dop SPUT_BYTE = + new Dop(Opcodes.SPUT_BYTE, Opcodes.SPUT_BYTE, + Opcodes.NO_NEXT, Form21c.THE_ONE, false); + + public static final Dop SPUT_CHAR = + new Dop(Opcodes.SPUT_CHAR, Opcodes.SPUT_CHAR, + Opcodes.NO_NEXT, Form21c.THE_ONE, false); + + public static final Dop SPUT_SHORT = + new Dop(Opcodes.SPUT_SHORT, Opcodes.SPUT_SHORT, + Opcodes.NO_NEXT, Form21c.THE_ONE, false); + + public static final Dop INVOKE_VIRTUAL = + new Dop(Opcodes.INVOKE_VIRTUAL, Opcodes.INVOKE_VIRTUAL, + Opcodes.INVOKE_VIRTUAL_RANGE, Form35c.THE_ONE, false); + + public static final Dop INVOKE_SUPER = + new Dop(Opcodes.INVOKE_SUPER, Opcodes.INVOKE_SUPER, + Opcodes.INVOKE_SUPER_RANGE, Form35c.THE_ONE, false); + + public static final Dop INVOKE_DIRECT = + new Dop(Opcodes.INVOKE_DIRECT, Opcodes.INVOKE_DIRECT, + Opcodes.INVOKE_DIRECT_RANGE, Form35c.THE_ONE, false); + + public static final Dop INVOKE_STATIC = + new Dop(Opcodes.INVOKE_STATIC, Opcodes.INVOKE_STATIC, + Opcodes.INVOKE_STATIC_RANGE, Form35c.THE_ONE, false); + + public static final Dop INVOKE_INTERFACE = + new Dop(Opcodes.INVOKE_INTERFACE, Opcodes.INVOKE_INTERFACE, + Opcodes.INVOKE_INTERFACE_RANGE, Form35c.THE_ONE, false); + + public static final Dop INVOKE_VIRTUAL_RANGE = + new Dop(Opcodes.INVOKE_VIRTUAL_RANGE, Opcodes.INVOKE_VIRTUAL, + Opcodes.NO_NEXT, Form3rc.THE_ONE, false); + + public static final Dop INVOKE_SUPER_RANGE = + new Dop(Opcodes.INVOKE_SUPER_RANGE, Opcodes.INVOKE_SUPER, + Opcodes.NO_NEXT, Form3rc.THE_ONE, false); + + public static final Dop INVOKE_DIRECT_RANGE = + new Dop(Opcodes.INVOKE_DIRECT_RANGE, Opcodes.INVOKE_DIRECT, + Opcodes.NO_NEXT, Form3rc.THE_ONE, false); + + public static final Dop INVOKE_STATIC_RANGE = + new Dop(Opcodes.INVOKE_STATIC_RANGE, Opcodes.INVOKE_STATIC, + Opcodes.NO_NEXT, Form3rc.THE_ONE, false); + + public static final Dop INVOKE_INTERFACE_RANGE = + new Dop(Opcodes.INVOKE_INTERFACE_RANGE, Opcodes.INVOKE_INTERFACE, + Opcodes.NO_NEXT, Form3rc.THE_ONE, false); + + public static final Dop NEG_INT = + new Dop(Opcodes.NEG_INT, Opcodes.NEG_INT, + Opcodes.NO_NEXT, Form12x.THE_ONE, true); + + public static final Dop NOT_INT = + new Dop(Opcodes.NOT_INT, Opcodes.NOT_INT, + Opcodes.NO_NEXT, Form12x.THE_ONE, true); + + public static final Dop NEG_LONG = + new Dop(Opcodes.NEG_LONG, Opcodes.NEG_LONG, + Opcodes.NO_NEXT, Form12x.THE_ONE, true); + + public static final Dop NOT_LONG = + new Dop(Opcodes.NOT_LONG, Opcodes.NOT_LONG, + Opcodes.NO_NEXT, Form12x.THE_ONE, true); + + public static final Dop NEG_FLOAT = + new Dop(Opcodes.NEG_FLOAT, Opcodes.NEG_FLOAT, + Opcodes.NO_NEXT, Form12x.THE_ONE, true); + + public static final Dop NEG_DOUBLE = + new Dop(Opcodes.NEG_DOUBLE, Opcodes.NEG_DOUBLE, + Opcodes.NO_NEXT, Form12x.THE_ONE, true); + + public static final Dop INT_TO_LONG = + new Dop(Opcodes.INT_TO_LONG, Opcodes.INT_TO_LONG, + Opcodes.NO_NEXT, Form12x.THE_ONE, true); + + public static final Dop INT_TO_FLOAT = + new Dop(Opcodes.INT_TO_FLOAT, Opcodes.INT_TO_FLOAT, + Opcodes.NO_NEXT, Form12x.THE_ONE, true); + + public static final Dop INT_TO_DOUBLE = + new Dop(Opcodes.INT_TO_DOUBLE, Opcodes.INT_TO_DOUBLE, + Opcodes.NO_NEXT, Form12x.THE_ONE, true); + + public static final Dop LONG_TO_INT = + new Dop(Opcodes.LONG_TO_INT, Opcodes.LONG_TO_INT, + Opcodes.NO_NEXT, Form12x.THE_ONE, true); + + public static final Dop LONG_TO_FLOAT = + new Dop(Opcodes.LONG_TO_FLOAT, Opcodes.LONG_TO_FLOAT, + Opcodes.NO_NEXT, Form12x.THE_ONE, true); + + public static final Dop LONG_TO_DOUBLE = + new Dop(Opcodes.LONG_TO_DOUBLE, Opcodes.LONG_TO_DOUBLE, + Opcodes.NO_NEXT, Form12x.THE_ONE, true); + + public static final Dop FLOAT_TO_INT = + new Dop(Opcodes.FLOAT_TO_INT, Opcodes.FLOAT_TO_INT, + Opcodes.NO_NEXT, Form12x.THE_ONE, true); + + public static final Dop FLOAT_TO_LONG = + new Dop(Opcodes.FLOAT_TO_LONG, Opcodes.FLOAT_TO_LONG, + Opcodes.NO_NEXT, Form12x.THE_ONE, true); + + public static final Dop FLOAT_TO_DOUBLE = + new Dop(Opcodes.FLOAT_TO_DOUBLE, Opcodes.FLOAT_TO_DOUBLE, + Opcodes.NO_NEXT, Form12x.THE_ONE, true); + + public static final Dop DOUBLE_TO_INT = + new Dop(Opcodes.DOUBLE_TO_INT, Opcodes.DOUBLE_TO_INT, + Opcodes.NO_NEXT, Form12x.THE_ONE, true); + + public static final Dop DOUBLE_TO_LONG = + new Dop(Opcodes.DOUBLE_TO_LONG, Opcodes.DOUBLE_TO_LONG, + Opcodes.NO_NEXT, Form12x.THE_ONE, true); + + public static final Dop DOUBLE_TO_FLOAT = + new Dop(Opcodes.DOUBLE_TO_FLOAT, Opcodes.DOUBLE_TO_FLOAT, + Opcodes.NO_NEXT, Form12x.THE_ONE, true); + + public static final Dop INT_TO_BYTE = + new Dop(Opcodes.INT_TO_BYTE, Opcodes.INT_TO_BYTE, + Opcodes.NO_NEXT, Form12x.THE_ONE, true); + + public static final Dop INT_TO_CHAR = + new Dop(Opcodes.INT_TO_CHAR, Opcodes.INT_TO_CHAR, + Opcodes.NO_NEXT, Form12x.THE_ONE, true); + + public static final Dop INT_TO_SHORT = + new Dop(Opcodes.INT_TO_SHORT, Opcodes.INT_TO_SHORT, + Opcodes.NO_NEXT, Form12x.THE_ONE, true); + + public static final Dop ADD_INT = + new Dop(Opcodes.ADD_INT, Opcodes.ADD_INT, + Opcodes.NO_NEXT, Form23x.THE_ONE, true); + + public static final Dop SUB_INT = + new Dop(Opcodes.SUB_INT, Opcodes.SUB_INT, + Opcodes.NO_NEXT, Form23x.THE_ONE, true); + + public static final Dop MUL_INT = + new Dop(Opcodes.MUL_INT, Opcodes.MUL_INT, + Opcodes.NO_NEXT, Form23x.THE_ONE, true); + + public static final Dop DIV_INT = + new Dop(Opcodes.DIV_INT, Opcodes.DIV_INT, + Opcodes.NO_NEXT, Form23x.THE_ONE, true); + + public static final Dop REM_INT = + new Dop(Opcodes.REM_INT, Opcodes.REM_INT, + Opcodes.NO_NEXT, Form23x.THE_ONE, true); + + public static final Dop AND_INT = + new Dop(Opcodes.AND_INT, Opcodes.AND_INT, + Opcodes.NO_NEXT, Form23x.THE_ONE, true); + + public static final Dop OR_INT = + new Dop(Opcodes.OR_INT, Opcodes.OR_INT, + Opcodes.NO_NEXT, Form23x.THE_ONE, true); + + public static final Dop XOR_INT = + new Dop(Opcodes.XOR_INT, Opcodes.XOR_INT, + Opcodes.NO_NEXT, Form23x.THE_ONE, true); + + public static final Dop SHL_INT = + new Dop(Opcodes.SHL_INT, Opcodes.SHL_INT, + Opcodes.NO_NEXT, Form23x.THE_ONE, true); + + public static final Dop SHR_INT = + new Dop(Opcodes.SHR_INT, Opcodes.SHR_INT, + Opcodes.NO_NEXT, Form23x.THE_ONE, true); + + public static final Dop USHR_INT = + new Dop(Opcodes.USHR_INT, Opcodes.USHR_INT, + Opcodes.NO_NEXT, Form23x.THE_ONE, true); + + public static final Dop ADD_LONG = + new Dop(Opcodes.ADD_LONG, Opcodes.ADD_LONG, + Opcodes.NO_NEXT, Form23x.THE_ONE, true); + + public static final Dop SUB_LONG = + new Dop(Opcodes.SUB_LONG, Opcodes.SUB_LONG, + Opcodes.NO_NEXT, Form23x.THE_ONE, true); + + public static final Dop MUL_LONG = + new Dop(Opcodes.MUL_LONG, Opcodes.MUL_LONG, + Opcodes.NO_NEXT, Form23x.THE_ONE, true); + + public static final Dop DIV_LONG = + new Dop(Opcodes.DIV_LONG, Opcodes.DIV_LONG, + Opcodes.NO_NEXT, Form23x.THE_ONE, true); + + public static final Dop REM_LONG = + new Dop(Opcodes.REM_LONG, Opcodes.REM_LONG, + Opcodes.NO_NEXT, Form23x.THE_ONE, true); + + public static final Dop AND_LONG = + new Dop(Opcodes.AND_LONG, Opcodes.AND_LONG, + Opcodes.NO_NEXT, Form23x.THE_ONE, true); + + public static final Dop OR_LONG = + new Dop(Opcodes.OR_LONG, Opcodes.OR_LONG, + Opcodes.NO_NEXT, Form23x.THE_ONE, true); + + public static final Dop XOR_LONG = + new Dop(Opcodes.XOR_LONG, Opcodes.XOR_LONG, + Opcodes.NO_NEXT, Form23x.THE_ONE, true); + + public static final Dop SHL_LONG = + new Dop(Opcodes.SHL_LONG, Opcodes.SHL_LONG, + Opcodes.NO_NEXT, Form23x.THE_ONE, true); + + public static final Dop SHR_LONG = + new Dop(Opcodes.SHR_LONG, Opcodes.SHR_LONG, + Opcodes.NO_NEXT, Form23x.THE_ONE, true); + + public static final Dop USHR_LONG = + new Dop(Opcodes.USHR_LONG, Opcodes.USHR_LONG, + Opcodes.NO_NEXT, Form23x.THE_ONE, true); + + public static final Dop ADD_FLOAT = + new Dop(Opcodes.ADD_FLOAT, Opcodes.ADD_FLOAT, + Opcodes.NO_NEXT, Form23x.THE_ONE, true); + + public static final Dop SUB_FLOAT = + new Dop(Opcodes.SUB_FLOAT, Opcodes.SUB_FLOAT, + Opcodes.NO_NEXT, Form23x.THE_ONE, true); + + public static final Dop MUL_FLOAT = + new Dop(Opcodes.MUL_FLOAT, Opcodes.MUL_FLOAT, + Opcodes.NO_NEXT, Form23x.THE_ONE, true); + + public static final Dop DIV_FLOAT = + new Dop(Opcodes.DIV_FLOAT, Opcodes.DIV_FLOAT, + Opcodes.NO_NEXT, Form23x.THE_ONE, true); + + public static final Dop REM_FLOAT = + new Dop(Opcodes.REM_FLOAT, Opcodes.REM_FLOAT, + Opcodes.NO_NEXT, Form23x.THE_ONE, true); + + public static final Dop ADD_DOUBLE = + new Dop(Opcodes.ADD_DOUBLE, Opcodes.ADD_DOUBLE, + Opcodes.NO_NEXT, Form23x.THE_ONE, true); + + public static final Dop SUB_DOUBLE = + new Dop(Opcodes.SUB_DOUBLE, Opcodes.SUB_DOUBLE, + Opcodes.NO_NEXT, Form23x.THE_ONE, true); + + public static final Dop MUL_DOUBLE = + new Dop(Opcodes.MUL_DOUBLE, Opcodes.MUL_DOUBLE, + Opcodes.NO_NEXT, Form23x.THE_ONE, true); + + public static final Dop DIV_DOUBLE = + new Dop(Opcodes.DIV_DOUBLE, Opcodes.DIV_DOUBLE, + Opcodes.NO_NEXT, Form23x.THE_ONE, true); + + public static final Dop REM_DOUBLE = + new Dop(Opcodes.REM_DOUBLE, Opcodes.REM_DOUBLE, + Opcodes.NO_NEXT, Form23x.THE_ONE, true); + + public static final Dop ADD_INT_2ADDR = + new Dop(Opcodes.ADD_INT_2ADDR, Opcodes.ADD_INT, + Opcodes.ADD_INT, Form12x.THE_ONE, true); + + public static final Dop SUB_INT_2ADDR = + new Dop(Opcodes.SUB_INT_2ADDR, Opcodes.SUB_INT, + Opcodes.SUB_INT, Form12x.THE_ONE, true); + + public static final Dop MUL_INT_2ADDR = + new Dop(Opcodes.MUL_INT_2ADDR, Opcodes.MUL_INT, + Opcodes.MUL_INT, Form12x.THE_ONE, true); + + public static final Dop DIV_INT_2ADDR = + new Dop(Opcodes.DIV_INT_2ADDR, Opcodes.DIV_INT, + Opcodes.DIV_INT, Form12x.THE_ONE, true); + + public static final Dop REM_INT_2ADDR = + new Dop(Opcodes.REM_INT_2ADDR, Opcodes.REM_INT, + Opcodes.REM_INT, Form12x.THE_ONE, true); + + public static final Dop AND_INT_2ADDR = + new Dop(Opcodes.AND_INT_2ADDR, Opcodes.AND_INT, + Opcodes.AND_INT, Form12x.THE_ONE, true); + + public static final Dop OR_INT_2ADDR = + new Dop(Opcodes.OR_INT_2ADDR, Opcodes.OR_INT, + Opcodes.OR_INT, Form12x.THE_ONE, true); + + public static final Dop XOR_INT_2ADDR = + new Dop(Opcodes.XOR_INT_2ADDR, Opcodes.XOR_INT, + Opcodes.XOR_INT, Form12x.THE_ONE, true); + + public static final Dop SHL_INT_2ADDR = + new Dop(Opcodes.SHL_INT_2ADDR, Opcodes.SHL_INT, + Opcodes.SHL_INT, Form12x.THE_ONE, true); + + public static final Dop SHR_INT_2ADDR = + new Dop(Opcodes.SHR_INT_2ADDR, Opcodes.SHR_INT, + Opcodes.SHR_INT, Form12x.THE_ONE, true); + + public static final Dop USHR_INT_2ADDR = + new Dop(Opcodes.USHR_INT_2ADDR, Opcodes.USHR_INT, + Opcodes.USHR_INT, Form12x.THE_ONE, true); + + public static final Dop ADD_LONG_2ADDR = + new Dop(Opcodes.ADD_LONG_2ADDR, Opcodes.ADD_LONG, + Opcodes.ADD_LONG, Form12x.THE_ONE, true); + + public static final Dop SUB_LONG_2ADDR = + new Dop(Opcodes.SUB_LONG_2ADDR, Opcodes.SUB_LONG, + Opcodes.SUB_LONG, Form12x.THE_ONE, true); + + public static final Dop MUL_LONG_2ADDR = + new Dop(Opcodes.MUL_LONG_2ADDR, Opcodes.MUL_LONG, + Opcodes.MUL_LONG, Form12x.THE_ONE, true); + + public static final Dop DIV_LONG_2ADDR = + new Dop(Opcodes.DIV_LONG_2ADDR, Opcodes.DIV_LONG, + Opcodes.DIV_LONG, Form12x.THE_ONE, true); + + public static final Dop REM_LONG_2ADDR = + new Dop(Opcodes.REM_LONG_2ADDR, Opcodes.REM_LONG, + Opcodes.REM_LONG, Form12x.THE_ONE, true); + + public static final Dop AND_LONG_2ADDR = + new Dop(Opcodes.AND_LONG_2ADDR, Opcodes.AND_LONG, + Opcodes.AND_LONG, Form12x.THE_ONE, true); + + public static final Dop OR_LONG_2ADDR = + new Dop(Opcodes.OR_LONG_2ADDR, Opcodes.OR_LONG, + Opcodes.OR_LONG, Form12x.THE_ONE, true); + + public static final Dop XOR_LONG_2ADDR = + new Dop(Opcodes.XOR_LONG_2ADDR, Opcodes.XOR_LONG, + Opcodes.XOR_LONG, Form12x.THE_ONE, true); + + public static final Dop SHL_LONG_2ADDR = + new Dop(Opcodes.SHL_LONG_2ADDR, Opcodes.SHL_LONG, + Opcodes.SHL_LONG, Form12x.THE_ONE, true); + + public static final Dop SHR_LONG_2ADDR = + new Dop(Opcodes.SHR_LONG_2ADDR, Opcodes.SHR_LONG, + Opcodes.SHR_LONG, Form12x.THE_ONE, true); + + public static final Dop USHR_LONG_2ADDR = + new Dop(Opcodes.USHR_LONG_2ADDR, Opcodes.USHR_LONG, + Opcodes.USHR_LONG, Form12x.THE_ONE, true); + + public static final Dop ADD_FLOAT_2ADDR = + new Dop(Opcodes.ADD_FLOAT_2ADDR, Opcodes.ADD_FLOAT, + Opcodes.ADD_FLOAT, Form12x.THE_ONE, true); + + public static final Dop SUB_FLOAT_2ADDR = + new Dop(Opcodes.SUB_FLOAT_2ADDR, Opcodes.SUB_FLOAT, + Opcodes.SUB_FLOAT, Form12x.THE_ONE, true); + + public static final Dop MUL_FLOAT_2ADDR = + new Dop(Opcodes.MUL_FLOAT_2ADDR, Opcodes.MUL_FLOAT, + Opcodes.MUL_FLOAT, Form12x.THE_ONE, true); + + public static final Dop DIV_FLOAT_2ADDR = + new Dop(Opcodes.DIV_FLOAT_2ADDR, Opcodes.DIV_FLOAT, + Opcodes.DIV_FLOAT, Form12x.THE_ONE, true); + + public static final Dop REM_FLOAT_2ADDR = + new Dop(Opcodes.REM_FLOAT_2ADDR, Opcodes.REM_FLOAT, + Opcodes.REM_FLOAT, Form12x.THE_ONE, true); + + public static final Dop ADD_DOUBLE_2ADDR = + new Dop(Opcodes.ADD_DOUBLE_2ADDR, Opcodes.ADD_DOUBLE, + Opcodes.ADD_DOUBLE, Form12x.THE_ONE, true); + + public static final Dop SUB_DOUBLE_2ADDR = + new Dop(Opcodes.SUB_DOUBLE_2ADDR, Opcodes.SUB_DOUBLE, + Opcodes.SUB_DOUBLE, Form12x.THE_ONE, true); + + public static final Dop MUL_DOUBLE_2ADDR = + new Dop(Opcodes.MUL_DOUBLE_2ADDR, Opcodes.MUL_DOUBLE, + Opcodes.MUL_DOUBLE, Form12x.THE_ONE, true); + + public static final Dop DIV_DOUBLE_2ADDR = + new Dop(Opcodes.DIV_DOUBLE_2ADDR, Opcodes.DIV_DOUBLE, + Opcodes.DIV_DOUBLE, Form12x.THE_ONE, true); + + public static final Dop REM_DOUBLE_2ADDR = + new Dop(Opcodes.REM_DOUBLE_2ADDR, Opcodes.REM_DOUBLE, + Opcodes.REM_DOUBLE, Form12x.THE_ONE, true); + + public static final Dop ADD_INT_LIT16 = + new Dop(Opcodes.ADD_INT_LIT16, Opcodes.ADD_INT, + Opcodes.NO_NEXT, Form22s.THE_ONE, true); + + public static final Dop RSUB_INT = + new Dop(Opcodes.RSUB_INT, Opcodes.RSUB_INT, + Opcodes.NO_NEXT, Form22s.THE_ONE, true); + + public static final Dop MUL_INT_LIT16 = + new Dop(Opcodes.MUL_INT_LIT16, Opcodes.MUL_INT, + Opcodes.NO_NEXT, Form22s.THE_ONE, true); + + public static final Dop DIV_INT_LIT16 = + new Dop(Opcodes.DIV_INT_LIT16, Opcodes.DIV_INT, + Opcodes.NO_NEXT, Form22s.THE_ONE, true); + + public static final Dop REM_INT_LIT16 = + new Dop(Opcodes.REM_INT_LIT16, Opcodes.REM_INT, + Opcodes.NO_NEXT, Form22s.THE_ONE, true); + + public static final Dop AND_INT_LIT16 = + new Dop(Opcodes.AND_INT_LIT16, Opcodes.AND_INT, + Opcodes.NO_NEXT, Form22s.THE_ONE, true); + + public static final Dop OR_INT_LIT16 = + new Dop(Opcodes.OR_INT_LIT16, Opcodes.OR_INT, + Opcodes.NO_NEXT, Form22s.THE_ONE, true); + + public static final Dop XOR_INT_LIT16 = + new Dop(Opcodes.XOR_INT_LIT16, Opcodes.XOR_INT, + Opcodes.NO_NEXT, Form22s.THE_ONE, true); + + public static final Dop ADD_INT_LIT8 = + new Dop(Opcodes.ADD_INT_LIT8, Opcodes.ADD_INT, + Opcodes.ADD_INT_LIT16, Form22b.THE_ONE, true); + + public static final Dop RSUB_INT_LIT8 = + new Dop(Opcodes.RSUB_INT_LIT8, Opcodes.RSUB_INT, + Opcodes.RSUB_INT, Form22b.THE_ONE, true); + + public static final Dop MUL_INT_LIT8 = + new Dop(Opcodes.MUL_INT_LIT8, Opcodes.MUL_INT, + Opcodes.MUL_INT_LIT16, Form22b.THE_ONE, true); + + public static final Dop DIV_INT_LIT8 = + new Dop(Opcodes.DIV_INT_LIT8, Opcodes.DIV_INT, + Opcodes.DIV_INT_LIT16, Form22b.THE_ONE, true); + + public static final Dop REM_INT_LIT8 = + new Dop(Opcodes.REM_INT_LIT8, Opcodes.REM_INT, + Opcodes.REM_INT_LIT16, Form22b.THE_ONE, true); + + public static final Dop AND_INT_LIT8 = + new Dop(Opcodes.AND_INT_LIT8, Opcodes.AND_INT, + Opcodes.AND_INT_LIT16, Form22b.THE_ONE, true); + + public static final Dop OR_INT_LIT8 = + new Dop(Opcodes.OR_INT_LIT8, Opcodes.OR_INT, + Opcodes.OR_INT_LIT16, Form22b.THE_ONE, true); + + public static final Dop XOR_INT_LIT8 = + new Dop(Opcodes.XOR_INT_LIT8, Opcodes.XOR_INT, + Opcodes.XOR_INT_LIT16, Form22b.THE_ONE, true); + + public static final Dop SHL_INT_LIT8 = + new Dop(Opcodes.SHL_INT_LIT8, Opcodes.SHL_INT, + Opcodes.NO_NEXT, Form22b.THE_ONE, true); + + public static final Dop SHR_INT_LIT8 = + new Dop(Opcodes.SHR_INT_LIT8, Opcodes.SHR_INT, + Opcodes.NO_NEXT, Form22b.THE_ONE, true); + + public static final Dop USHR_INT_LIT8 = + new Dop(Opcodes.USHR_INT_LIT8, Opcodes.USHR_INT, + Opcodes.NO_NEXT, Form22b.THE_ONE, true); + + // END(dops) + + // Static initialization. + static { + DOPS = new Dop[Opcodes.MAX_VALUE - Opcodes.MIN_VALUE + 1]; + + set(SPECIAL_FORMAT); + + // BEGIN(dops-init); GENERATED AUTOMATICALLY BY opcode-gen + set(NOP); + set(MOVE); + set(MOVE_FROM16); + set(MOVE_16); + set(MOVE_WIDE); + set(MOVE_WIDE_FROM16); + set(MOVE_WIDE_16); + set(MOVE_OBJECT); + set(MOVE_OBJECT_FROM16); + set(MOVE_OBJECT_16); + set(MOVE_RESULT); + set(MOVE_RESULT_WIDE); + set(MOVE_RESULT_OBJECT); + set(MOVE_EXCEPTION); + set(RETURN_VOID); + set(RETURN); + set(RETURN_WIDE); + set(RETURN_OBJECT); + set(CONST_4); + set(CONST_16); + set(CONST); + set(CONST_HIGH16); + set(CONST_WIDE_16); + set(CONST_WIDE_32); + set(CONST_WIDE); + set(CONST_WIDE_HIGH16); + set(CONST_STRING); + set(CONST_STRING_JUMBO); + set(CONST_CLASS); + set(MONITOR_ENTER); + set(MONITOR_EXIT); + set(CHECK_CAST); + set(INSTANCE_OF); + set(ARRAY_LENGTH); + set(NEW_INSTANCE); + set(NEW_ARRAY); + set(FILLED_NEW_ARRAY); + set(FILLED_NEW_ARRAY_RANGE); + set(FILL_ARRAY_DATA); + set(THROW); + set(GOTO); + set(GOTO_16); + set(GOTO_32); + set(PACKED_SWITCH); + set(SPARSE_SWITCH); + set(CMPL_FLOAT); + set(CMPG_FLOAT); + set(CMPL_DOUBLE); + set(CMPG_DOUBLE); + set(CMP_LONG); + set(IF_EQ); + set(IF_NE); + set(IF_LT); + set(IF_GE); + set(IF_GT); + set(IF_LE); + set(IF_EQZ); + set(IF_NEZ); + set(IF_LTZ); + set(IF_GEZ); + set(IF_GTZ); + set(IF_LEZ); + set(AGET); + set(AGET_WIDE); + set(AGET_OBJECT); + set(AGET_BOOLEAN); + set(AGET_BYTE); + set(AGET_CHAR); + set(AGET_SHORT); + set(APUT); + set(APUT_WIDE); + set(APUT_OBJECT); + set(APUT_BOOLEAN); + set(APUT_BYTE); + set(APUT_CHAR); + set(APUT_SHORT); + set(IGET); + set(IGET_WIDE); + set(IGET_OBJECT); + set(IGET_BOOLEAN); + set(IGET_BYTE); + set(IGET_CHAR); + set(IGET_SHORT); + set(IPUT); + set(IPUT_WIDE); + set(IPUT_OBJECT); + set(IPUT_BOOLEAN); + set(IPUT_BYTE); + set(IPUT_CHAR); + set(IPUT_SHORT); + set(SGET); + set(SGET_WIDE); + set(SGET_OBJECT); + set(SGET_BOOLEAN); + set(SGET_BYTE); + set(SGET_CHAR); + set(SGET_SHORT); + set(SPUT); + set(SPUT_WIDE); + set(SPUT_OBJECT); + set(SPUT_BOOLEAN); + set(SPUT_BYTE); + set(SPUT_CHAR); + set(SPUT_SHORT); + set(INVOKE_VIRTUAL); + set(INVOKE_SUPER); + set(INVOKE_DIRECT); + set(INVOKE_STATIC); + set(INVOKE_INTERFACE); + set(INVOKE_VIRTUAL_RANGE); + set(INVOKE_SUPER_RANGE); + set(INVOKE_DIRECT_RANGE); + set(INVOKE_STATIC_RANGE); + set(INVOKE_INTERFACE_RANGE); + set(NEG_INT); + set(NOT_INT); + set(NEG_LONG); + set(NOT_LONG); + set(NEG_FLOAT); + set(NEG_DOUBLE); + set(INT_TO_LONG); + set(INT_TO_FLOAT); + set(INT_TO_DOUBLE); + set(LONG_TO_INT); + set(LONG_TO_FLOAT); + set(LONG_TO_DOUBLE); + set(FLOAT_TO_INT); + set(FLOAT_TO_LONG); + set(FLOAT_TO_DOUBLE); + set(DOUBLE_TO_INT); + set(DOUBLE_TO_LONG); + set(DOUBLE_TO_FLOAT); + set(INT_TO_BYTE); + set(INT_TO_CHAR); + set(INT_TO_SHORT); + set(ADD_INT); + set(SUB_INT); + set(MUL_INT); + set(DIV_INT); + set(REM_INT); + set(AND_INT); + set(OR_INT); + set(XOR_INT); + set(SHL_INT); + set(SHR_INT); + set(USHR_INT); + set(ADD_LONG); + set(SUB_LONG); + set(MUL_LONG); + set(DIV_LONG); + set(REM_LONG); + set(AND_LONG); + set(OR_LONG); + set(XOR_LONG); + set(SHL_LONG); + set(SHR_LONG); + set(USHR_LONG); + set(ADD_FLOAT); + set(SUB_FLOAT); + set(MUL_FLOAT); + set(DIV_FLOAT); + set(REM_FLOAT); + set(ADD_DOUBLE); + set(SUB_DOUBLE); + set(MUL_DOUBLE); + set(DIV_DOUBLE); + set(REM_DOUBLE); + set(ADD_INT_2ADDR); + set(SUB_INT_2ADDR); + set(MUL_INT_2ADDR); + set(DIV_INT_2ADDR); + set(REM_INT_2ADDR); + set(AND_INT_2ADDR); + set(OR_INT_2ADDR); + set(XOR_INT_2ADDR); + set(SHL_INT_2ADDR); + set(SHR_INT_2ADDR); + set(USHR_INT_2ADDR); + set(ADD_LONG_2ADDR); + set(SUB_LONG_2ADDR); + set(MUL_LONG_2ADDR); + set(DIV_LONG_2ADDR); + set(REM_LONG_2ADDR); + set(AND_LONG_2ADDR); + set(OR_LONG_2ADDR); + set(XOR_LONG_2ADDR); + set(SHL_LONG_2ADDR); + set(SHR_LONG_2ADDR); + set(USHR_LONG_2ADDR); + set(ADD_FLOAT_2ADDR); + set(SUB_FLOAT_2ADDR); + set(MUL_FLOAT_2ADDR); + set(DIV_FLOAT_2ADDR); + set(REM_FLOAT_2ADDR); + set(ADD_DOUBLE_2ADDR); + set(SUB_DOUBLE_2ADDR); + set(MUL_DOUBLE_2ADDR); + set(DIV_DOUBLE_2ADDR); + set(REM_DOUBLE_2ADDR); + set(ADD_INT_LIT16); + set(RSUB_INT); + set(MUL_INT_LIT16); + set(DIV_INT_LIT16); + set(REM_INT_LIT16); + set(AND_INT_LIT16); + set(OR_INT_LIT16); + set(XOR_INT_LIT16); + set(ADD_INT_LIT8); + set(RSUB_INT_LIT8); + set(MUL_INT_LIT8); + set(DIV_INT_LIT8); + set(REM_INT_LIT8); + set(AND_INT_LIT8); + set(OR_INT_LIT8); + set(XOR_INT_LIT8); + set(SHL_INT_LIT8); + set(SHR_INT_LIT8); + set(USHR_INT_LIT8); + // END(dops-init) + } + + /** + * This class is uninstantiable. + */ + private Dops() { + // This space intentionally left blank. + } + + /** + * Gets the {@link Dop} for the given opcode value. + * + * @param opcode {@code Opcodes.MIN_VALUE..Opcodes.MAX_VALUE;} the + * opcode value + * @return {@code non-null;} the associated opcode instance + */ + public static Dop get(int opcode) { + int idx = opcode - Opcodes.MIN_VALUE; + + try { + Dop result = DOPS[idx]; + if (result != null) { + return result; + } + } catch (ArrayIndexOutOfBoundsException ex) { + // Fall through. + } + + throw new IllegalArgumentException("bogus opcode"); + } + + /** + * Gets the next {@link Dop} in the instruction fitting chain after the + * given instance, if any. + * + * @param opcode {@code non-null;} the opcode + * @param options {@code non-null;} options, used to determine + * which opcodes are potentially off-limits + * @return {@code null-ok;} the next opcode in the same family, in the + * chain of opcodes to try, or {@code null} if the given opcode is + * the last in its chain + */ + public static Dop getNextOrNull(Dop opcode, DexOptions options) { + int nextOpcode = opcode.getNextOpcode(); + + if (nextOpcode == Opcodes.NO_NEXT) { + return null; + } + + opcode = get(nextOpcode); + + return opcode; + } + + /** + * Puts the given opcode into the table of all ops. + * + * @param opcode {@code non-null;} the opcode + */ + private static void set(Dop opcode) { + int idx = opcode.getOpcode() - Opcodes.MIN_VALUE; + DOPS[idx] = opcode; + } +} diff --git a/dx/src/com/android/jack/dx/dex/code/FixedSizeInsn.java b/dx/src/com/android/jack/dx/dex/code/FixedSizeInsn.java new file mode 100644 index 00000000..d3ab83ce --- /dev/null +++ b/dx/src/com/android/jack/dx/dex/code/FixedSizeInsn.java @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.dex.code; + +import com.android.jack.dx.rop.code.RegisterSpecList; +import com.android.jack.dx.rop.code.SourcePosition; +import com.android.jack.dx.util.AnnotatedOutput; + +/** + * Base class for instructions which are of a fixed code size and + * which use {@link InsnFormat} methods to write themselves. This + * includes most — but not all — instructions. + */ +public abstract class FixedSizeInsn extends DalvInsn { + /** + * Constructs an instance. The output address of this instance is initially + * unknown ({@code -1}). + * + *

Note: In the unlikely event that an instruction takes + * absolutely no registers (e.g., a {@code nop} or a + * no-argument no-result * static method call), then the given + * register list may be passed as {@link + * RegisterSpecList#EMPTY}.

+ * + * @param opcode the opcode; one of the constants from {@link Dops} + * @param position {@code non-null;} source position + * @param registers {@code non-null;} register list, including a + * result register if appropriate (that is, registers may be either + * ins or outs) + */ + public FixedSizeInsn(Dop opcode, SourcePosition position, + RegisterSpecList registers) { + super(opcode, position, registers); + } + + /** {@inheritDoc} */ + @Override + public final int codeSize() { + return getOpcode().getFormat().codeSize(); + } + + /** {@inheritDoc} */ + @Override + public final void writeTo(AnnotatedOutput out) { + getOpcode().getFormat().writeTo(out, this); + } + + /** {@inheritDoc} */ + @Override + public final DalvInsn withRegisterOffset(int delta) { + return withRegisters(getRegisters().withOffset(delta)); + } + + /** {@inheritDoc} */ + @Override + protected final String listingString0(boolean noteIndices) { + return getOpcode().getFormat().listingString(this, noteIndices); + } +} diff --git a/dx/src/com/android/jack/dx/dex/code/HighRegisterPrefix.java b/dx/src/com/android/jack/dx/dex/code/HighRegisterPrefix.java new file mode 100644 index 00000000..424db5eb --- /dev/null +++ b/dx/src/com/android/jack/dx/dex/code/HighRegisterPrefix.java @@ -0,0 +1,147 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.dex.code; + +import com.android.jack.dx.rop.code.RegisterSpec; +import com.android.jack.dx.rop.code.RegisterSpecList; +import com.android.jack.dx.rop.code.SourcePosition; +import com.android.jack.dx.rop.type.Type; +import com.android.jack.dx.util.AnnotatedOutput; + +/** + * Combination instruction which turns into a variable number of + * {@code move*} instructions to move a set of registers into + * registers starting at {@code 0} sequentially. This is used + * in translating an instruction whose register requirements cannot + * be met using a straightforward choice of a single opcode. + */ +public final class HighRegisterPrefix extends VariableSizeInsn { + /** {@code null-ok;} cached instructions, if constructed */ + private SimpleInsn[] insns; + + /** + * Constructs an instance. The output address of this instance is initially + * unknown ({@code -1}). + * + * @param position {@code non-null;} source position + * @param registers {@code non-null;} source registers + */ + public HighRegisterPrefix(SourcePosition position, + RegisterSpecList registers) { + super(position, registers); + + if (registers.size() == 0) { + throw new IllegalArgumentException("registers.size() == 0"); + } + + insns = null; + } + + /** {@inheritDoc} */ + @Override + public int codeSize() { + int result = 0; + + calculateInsnsIfNecessary(); + + for (SimpleInsn insn : insns) { + result += insn.codeSize(); + } + + return result; + } + + /** {@inheritDoc} */ + @Override + public void writeTo(AnnotatedOutput out) { + calculateInsnsIfNecessary(); + + for (SimpleInsn insn : insns) { + insn.writeTo(out); + } + } + + /** + * Helper for {@link #codeSize} and {@link #writeTo} which sets up + * {@link #insns} if not already done. + */ + private void calculateInsnsIfNecessary() { + if (insns != null) { + return; + } + + RegisterSpecList registers = getRegisters(); + int sz = registers.size(); + + insns = new SimpleInsn[sz]; + + for (int i = 0, outAt = 0; i < sz; i++) { + RegisterSpec src = registers.get(i); + insns[i] = moveInsnFor(src, outAt); + outAt += src.getCategory(); + } + } + + /** {@inheritDoc} */ + @Override + public DalvInsn withRegisters(RegisterSpecList registers) { + return new HighRegisterPrefix(getPosition(), registers); + } + + /** {@inheritDoc} */ + @Override + protected String argString() { + return null; + } + + /** {@inheritDoc} */ + @Override + protected String listingString0(boolean noteIndices) { + RegisterSpecList registers = getRegisters(); + int sz = registers.size(); + StringBuffer sb = new StringBuffer(100); + + for (int i = 0, outAt = 0; i < sz; i++) { + RegisterSpec src = registers.get(i); + SimpleInsn insn = moveInsnFor(src, outAt); + + if (i != 0) { + sb.append('\n'); + } + + sb.append(insn.listingString0(noteIndices)); + + outAt += src.getCategory(); + } + + return sb.toString(); + } + + /** + * Returns the proper move instruction for the given source spec + * and destination index. + * + * @param src {@code non-null;} the source register spec + * @param destIndex {@code >= 0;} the destination register index + * @return {@code non-null;} the appropriate move instruction + */ + private static SimpleInsn moveInsnFor(RegisterSpec src, int destIndex) { + return DalvInsn.makeMove(SourcePosition.NO_INFO, + RegisterSpec.make(destIndex, src.getType()), + src); + } +} diff --git a/dx/src/com/android/jack/dx/dex/code/InsnFormat.java b/dx/src/com/android/jack/dx/dex/code/InsnFormat.java new file mode 100644 index 00000000..ab09b691 --- /dev/null +++ b/dx/src/com/android/jack/dx/dex/code/InsnFormat.java @@ -0,0 +1,714 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.dex.code; + +import com.android.jack.dx.rop.code.RegisterSpec; +import com.android.jack.dx.rop.code.RegisterSpecList; +import com.android.jack.dx.rop.cst.Constant; +import com.android.jack.dx.rop.cst.CstInteger; +import com.android.jack.dx.rop.cst.CstKnownNull; +import com.android.jack.dx.rop.cst.CstLiteral64; +import com.android.jack.dx.rop.cst.CstLiteralBits; +import com.android.jack.dx.rop.cst.CstString; +import com.android.jack.dx.util.AnnotatedOutput; +import com.android.jack.dx.util.Hex; + +import java.util.BitSet; + +/** + * Base class for all instruction format handlers. Instruction format + * handlers know how to translate {@link DalvInsn} instances into + * streams of code units, as well as human-oriented listing strings + * representing such translations. + */ +public abstract class InsnFormat { + /** + * flag to enable/disable the new extended opcode formats; meant as a + * temporary measure until VM support for the salient opcodes is + * added. TODO: Remove this declaration when the VM can deal. + */ + public static boolean ALLOW_EXTENDED_OPCODES = true; + + /** + * Returns the string form, suitable for inclusion in a listing + * dump, of the given instruction. The instruction must be of this + * instance's format for proper operation. + * + * @param insn {@code non-null;} the instruction + * @param noteIndices whether to include an explicit notation of + * constant pool indices + * @return {@code non-null;} the string form + */ + public final String listingString(DalvInsn insn, boolean noteIndices) { + String op = insn.getOpcode().getName(); + String arg = insnArgString(insn); + String comment = insnCommentString(insn, noteIndices); + StringBuilder sb = new StringBuilder(100); + + sb.append(op); + + if (arg.length() != 0) { + sb.append(' '); + sb.append(arg); + } + + if (comment.length() != 0) { + sb.append(" // "); + sb.append(comment); + } + + return sb.toString(); + } + + /** + * Returns the string form of the arguments to the given instruction. + * The instruction must be of this instance's format. If the instruction + * has no arguments, then the result should be {@code ""}, not + * {@code null}. + * + *

Subclasses must override this method.

+ * + * @param insn {@code non-null;} the instruction + * @return {@code non-null;} the string form + */ + public abstract String insnArgString(DalvInsn insn); + + /** + * Returns the associated comment for the given instruction, if any. + * The instruction must be of this instance's format. If the instruction + * has no comment, then the result should be {@code ""}, not + * {@code null}. + * + *

Subclasses must override this method.

+ * + * @param insn {@code non-null;} the instruction + * @param noteIndices whether to include an explicit notation of + * constant pool indices + * @return {@code non-null;} the string form + */ + public abstract String insnCommentString(DalvInsn insn, + boolean noteIndices); + + /** + * Gets the code size of instructions that use this format. The + * size is a number of 16-bit code units, not bytes. This should + * throw an exception if this format is of variable size. + * + * @return {@code >= 0;} the instruction length in 16-bit code units + */ + public abstract int codeSize(); + + /** + * Returns whether or not the given instruction's arguments will + * fit in this instance's format. This includes such things as + * counting register arguments, checking register ranges, and + * making sure that additional arguments are of appropriate types + * and are in-range. If this format has a branch target but the + * instruction's branch offset is unknown, this method will simply + * not check the offset. + * + *

Subclasses must override this method.

+ * + * @param insn {@code non-null;} the instruction to check + * @return {@code true} iff the instruction's arguments are + * appropriate for this instance, or {@code false} if not + */ + public abstract boolean isCompatible(DalvInsn insn); + + /** + * Returns which of a given instruction's registers will fit in + * this instance's format. + * + *

The default implementation of this method always returns + * an empty BitSet. Subclasses must override this method if they + * have registers.

+ * + * @param insn {@code non-null;} the instruction to check + * @return {@code non-null;} a BitSet flagging registers in the + * register list that are compatible to this format + */ + public BitSet compatibleRegs(DalvInsn insn) { + return new BitSet(); + } + + /** + * Returns whether or not the given instruction's branch offset will + * fit in this instance's format. This always returns {@code false} + * for formats that don't include a branch offset. + * + *

The default implementation of this method always returns + * {@code false}. Subclasses must override this method if they + * include branch offsets.

+ * + * @param insn {@code non-null;} the instruction to check + * @return {@code true} iff the instruction's branch offset is + * appropriate for this instance, or {@code false} if not + */ + public boolean branchFits(TargetInsn insn) { + return false; + } + + /** + * Writes the code units for the given instruction to the given + * output destination. The instruction must be of this instance's format. + * + *

Subclasses must override this method.

+ * + * @param out {@code non-null;} the output destination to write to + * @param insn {@code non-null;} the instruction to write + */ + public abstract void writeTo(AnnotatedOutput out, DalvInsn insn); + + /** + * Helper method to return a register list string. + * + * @param list {@code non-null;} the list of registers + * @return {@code non-null;} the string form + */ + protected static String regListString(RegisterSpecList list) { + int sz = list.size(); + StringBuffer sb = new StringBuffer(sz * 5 + 2); + + sb.append('{'); + + for (int i = 0; i < sz; i++) { + if (i != 0) { + sb.append(", "); + } + sb.append(list.get(i).regString()); + } + + sb.append('}'); + + return sb.toString(); + } + + /** + * Helper method to return a register range string. + * + * @param list {@code non-null;} the list of registers (which must be + * sequential) + * @return {@code non-null;} the string form + */ + protected static String regRangeString(RegisterSpecList list) { + int size = list.size(); + StringBuilder sb = new StringBuilder(30); + + sb.append("{"); + + switch (size) { + case 0: { + // Nothing to do. + break; + } + case 1: { + sb.append(list.get(0).regString()); + break; + } + default: { + RegisterSpec lastReg = list.get(size - 1); + if (lastReg.getCategory() == 2) { + /* + * Add one to properly represent a list-final + * category-2 register. + */ + lastReg = lastReg.withOffset(1); + } + + sb.append(list.get(0).regString()); + sb.append(".."); + sb.append(lastReg.regString()); + } + } + + sb.append("}"); + + return sb.toString(); + } + + /** + * Helper method to return a literal bits argument string. + * + * @param value the value + * @return {@code non-null;} the string form + */ + protected static String literalBitsString(CstLiteralBits value) { + StringBuffer sb = new StringBuffer(100); + + sb.append('#'); + + if (value instanceof CstKnownNull) { + sb.append("null"); + } else { + sb.append(value.typeName()); + sb.append(' '); + sb.append(value.toHuman()); + } + + return sb.toString(); + } + + /** + * Helper method to return a literal bits comment string. + * + * @param value the value + * @param width the width of the constant, in bits (used for displaying + * the uninterpreted bits; one of: {@code 4 8 16 32 64} + * @return {@code non-null;} the comment + */ + protected static String literalBitsComment(CstLiteralBits value, + int width) { + StringBuffer sb = new StringBuffer(20); + + sb.append("#"); + + long bits; + + if (value instanceof CstLiteral64) { + bits = ((CstLiteral64) value).getLongBits(); + } else { + bits = value.getIntBits(); + } + + switch (width) { + case 4: sb.append(Hex.uNibble((int) bits)); break; + case 8: sb.append(Hex.u1((int) bits)); break; + case 16: sb.append(Hex.u2((int) bits)); break; + case 32: sb.append(Hex.u4((int) bits)); break; + case 64: sb.append(Hex.u8(bits)); break; + default: { + throw new RuntimeException("shouldn't happen"); + } + } + + return sb.toString(); + } + + /** + * Helper method to return a branch address string. + * + * @param insn {@code non-null;} the instruction in question + * @return {@code non-null;} the string form of the instruction's + * branch target + */ + protected static String branchString(DalvInsn insn) { + TargetInsn ti = (TargetInsn) insn; + int address = ti.getTargetAddress(); + + return (address == (char) address) ? Hex.u2(address) : Hex.u4(address); + } + + /** + * Helper method to return the comment for a branch. + * + * @param insn {@code non-null;} the instruction in question + * @return {@code non-null;} the comment + */ + protected static String branchComment(DalvInsn insn) { + TargetInsn ti = (TargetInsn) insn; + int offset = ti.getTargetOffset(); + + return (offset == (short) offset) ? Hex.s2(offset) : Hex.s4(offset); + } + + /** + * Helper method to return the constant string for a {@link CstInsn} + * in human form. + * + * @param insn {@code non-null;} a constant-bearing instruction + * @return {@code non-null;} the human string form of the contained + * constant + */ + protected static String cstString(DalvInsn insn) { + CstInsn ci = (CstInsn) insn; + Constant cst = ci.getConstant(); + + return cst instanceof CstString ? ((CstString) cst).toQuoted() : cst.toHuman(); + } + + /** + * Helper method to return an instruction comment for a constant. + * + * @param insn {@code non-null;} a constant-bearing instruction + * @return {@code non-null;} comment string representing the constant + */ + protected static String cstComment(DalvInsn insn) { + CstInsn ci = (CstInsn) insn; + + if (! ci.hasIndex()) { + return ""; + } + + StringBuilder sb = new StringBuilder(20); + int index = ci.getIndex(); + + sb.append(ci.getConstant().typeName()); + sb.append('@'); + + if (index < 65536) { + sb.append(Hex.u2(index)); + } else { + sb.append(Hex.u4(index)); + } + + return sb.toString(); + } + + /** + * Helper method to determine if a signed int value fits in a nibble. + * + * @param value the value in question + * @return {@code true} iff it's in the range -8..+7 + */ + protected static boolean signedFitsInNibble(int value) { + return (value >= -8) && (value <= 7); + } + + /** + * Helper method to determine if an unsigned int value fits in a nibble. + * + * @param value the value in question + * @return {@code true} iff it's in the range 0..0xf + */ + protected static boolean unsignedFitsInNibble(int value) { + return value == (value & 0xf); + } + + /** + * Helper method to determine if a signed int value fits in a byte. + * + * @param value the value in question + * @return {@code true} iff it's in the range -0x80..+0x7f + */ + protected static boolean signedFitsInByte(int value) { + return (byte) value == value; + } + + /** + * Helper method to determine if an unsigned int value fits in a byte. + * + * @param value the value in question + * @return {@code true} iff it's in the range 0..0xff + */ + protected static boolean unsignedFitsInByte(int value) { + return value == (value & 0xff); + } + + /** + * Helper method to determine if a signed int value fits in a short. + * + * @param value the value in question + * @return {@code true} iff it's in the range -0x8000..+0x7fff + */ + protected static boolean signedFitsInShort(int value) { + return (short) value == value; + } + + /** + * Helper method to determine if an unsigned int value fits in a short. + * + * @param value the value in question + * @return {@code true} iff it's in the range 0..0xffff + */ + protected static boolean unsignedFitsInShort(int value) { + return value == (value & 0xffff); + } + + /** + * Helper method to determine if a list of registers are sequential, + * including degenerate cases for empty or single-element lists. + * + * @param list {@code non-null;} the list of registers + * @return {@code true} iff the list is sequentially ordered + */ + protected static boolean isRegListSequential(RegisterSpecList list) { + int sz = list.size(); + + if (sz < 2) { + return true; + } + + int first = list.get(0).getReg(); + int next = first; + + for (int i = 0; i < sz; i++) { + RegisterSpec one = list.get(i); + if (one.getReg() != next) { + return false; + } + next += one.getCategory(); + } + + return true; + } + + /** + * Helper method to extract the callout-argument index from an + * appropriate instruction. + * + * @param insn {@code non-null;} the instruction + * @return {@code >= 0;} the callout argument index + */ + protected static int argIndex(DalvInsn insn) { + int arg = ((CstInteger) ((CstInsn) insn).getConstant()).getValue(); + + if (arg < 0) { + throw new IllegalArgumentException("bogus insn"); + } + + return arg; + } + + /** + * Helper method to combine an opcode and a second byte of data into + * the appropriate form for emitting into a code buffer. + * + * @param insn {@code non-null;} the instruction containing the opcode + * @param arg {@code 0..255;} arbitrary other byte value + * @return combined value + */ + protected static short opcodeUnit(DalvInsn insn, int arg) { + if ((arg & 0xff) != arg) { + throw new IllegalArgumentException("arg out of range 0..255"); + } + + int opcode = insn.getOpcode().getOpcode(); + + if ((opcode & 0xff) != opcode) { + throw new IllegalArgumentException("opcode out of range 0..255"); + } + + return (short) (opcode | (arg << 8)); + } + + /** + * Helper method to get an extended (16-bit) opcode out of an + * instruction, returning it as a code unit. The opcode + * must be an extended opcode. + * + * @param insn {@code non-null;} the instruction containing the + * extended opcode + * @return the opcode as a code unit + */ + protected static short opcodeUnit(DalvInsn insn) { + int opcode = insn.getOpcode().getOpcode(); + + if ((opcode < 0x100) || (opcode > 0xffff)) { + throw new IllegalArgumentException("opcode out of range 0..65535"); + } + + return (short) opcode; + } + + /** + * Helper method to combine two bytes into a code unit. + * + * @param low {@code 0..255;} low byte + * @param high {@code 0..255;} high byte + * @return combined value + */ + protected static short codeUnit(int low, int high) { + if ((low & 0xff) != low) { + throw new IllegalArgumentException("low out of range 0..255"); + } + + if ((high & 0xff) != high) { + throw new IllegalArgumentException("high out of range 0..255"); + } + + return (short) (low | (high << 8)); + } + + /** + * Helper method to combine four nibbles into a code unit. + * + * @param n0 {@code 0..15;} low nibble + * @param n1 {@code 0..15;} medium-low nibble + * @param n2 {@code 0..15;} medium-high nibble + * @param n3 {@code 0..15;} high nibble + * @return combined value + */ + protected static short codeUnit(int n0, int n1, int n2, int n3) { + if ((n0 & 0xf) != n0) { + throw new IllegalArgumentException("n0 out of range 0..15"); + } + + if ((n1 & 0xf) != n1) { + throw new IllegalArgumentException("n1 out of range 0..15"); + } + + if ((n2 & 0xf) != n2) { + throw new IllegalArgumentException("n2 out of range 0..15"); + } + + if ((n3 & 0xf) != n3) { + throw new IllegalArgumentException("n3 out of range 0..15"); + } + + return (short) (n0 | (n1 << 4) | (n2 << 8) | (n3 << 12)); + } + + /** + * Helper method to combine two nibbles into a byte. + * + * @param low {@code 0..15;} low nibble + * @param high {@code 0..15;} high nibble + * @return {@code 0..255;} combined value + */ + protected static int makeByte(int low, int high) { + if ((low & 0xf) != low) { + throw new IllegalArgumentException("low out of range 0..15"); + } + + if ((high & 0xf) != high) { + throw new IllegalArgumentException("high out of range 0..15"); + } + + return low | (high << 4); + } + + /** + * Writes one code unit to the given output destination. + * + * @param out {@code non-null;} where to write to + * @param c0 code unit to write + */ + protected static void write(AnnotatedOutput out, short c0) { + out.writeShort(c0); + } + + /** + * Writes two code units to the given output destination. + * + * @param out {@code non-null;} where to write to + * @param c0 code unit to write + * @param c1 code unit to write + */ + protected static void write(AnnotatedOutput out, short c0, short c1) { + out.writeShort(c0); + out.writeShort(c1); + } + + /** + * Writes three code units to the given output destination. + * + * @param out {@code non-null;} where to write to + * @param c0 code unit to write + * @param c1 code unit to write + * @param c2 code unit to write + */ + protected static void write(AnnotatedOutput out, short c0, short c1, + short c2) { + out.writeShort(c0); + out.writeShort(c1); + out.writeShort(c2); + } + + /** + * Writes four code units to the given output destination. + * + * @param out {@code non-null;} where to write to + * @param c0 code unit to write + * @param c1 code unit to write + * @param c2 code unit to write + * @param c3 code unit to write + */ + protected static void write(AnnotatedOutput out, short c0, short c1, + short c2, short c3) { + out.writeShort(c0); + out.writeShort(c1); + out.writeShort(c2); + out.writeShort(c3); + } + + /** + * Writes five code units to the given output destination. + * + * @param out {@code non-null;} where to write to + * @param c0 code unit to write + * @param c1 code unit to write + * @param c2 code unit to write + * @param c3 code unit to write + * @param c4 code unit to write + */ + protected static void write(AnnotatedOutput out, short c0, short c1, + short c2, short c3, short c4) { + out.writeShort(c0); + out.writeShort(c1); + out.writeShort(c2); + out.writeShort(c3); + out.writeShort(c4); + } + + /** + * Writes three code units to the given output destination, where the + * second and third are represented as single int and emitted + * in little-endian order. + * + * @param out {@code non-null;} where to write to + * @param c0 code unit to write + * @param c1c2 code unit pair to write + */ + protected static void write(AnnotatedOutput out, short c0, int c1c2) { + write(out, c0, (short) c1c2, (short) (c1c2 >> 16)); + } + + /** + * Writes four code units to the given output destination, where the + * second and third are represented as single int and emitted + * in little-endian order. + * + * @param out {@code non-null;} where to write to + * @param c0 code unit to write + * @param c1c2 code unit pair to write + * @param c3 code unit to write + */ + protected static void write(AnnotatedOutput out, short c0, int c1c2, + short c3) { + write(out, c0, (short) c1c2, (short) (c1c2 >> 16), c3); + } + + /** + * Writes five code units to the given output destination, where the + * second and third are represented as single int and emitted + * in little-endian order. + * + * @param out {@code non-null;} where to write to + * @param c0 code unit to write + * @param c1c2 code unit pair to write + * @param c3 code unit to write + * @param c4 code unit to write + */ + protected static void write(AnnotatedOutput out, short c0, int c1c2, + short c3, short c4) { + write(out, c0, (short) c1c2, (short) (c1c2 >> 16), c3, c4); + } + + /** + * Writes five code units to the given output destination, where the + * second through fifth are represented as single long + * and emitted in little-endian order. + * + * @param out {@code non-null;} where to write to + * @param c0 code unit to write + * @param c1c2c3c4 code unit quad to write + */ + protected static void write(AnnotatedOutput out, short c0, long c1c2c3c4) { + write(out, c0, (short) c1c2c3c4, (short) (c1c2c3c4 >> 16), + (short) (c1c2c3c4 >> 32), (short) (c1c2c3c4 >> 48)); + } +} diff --git a/dx/src/com/android/jack/dx/dex/code/LocalEnd.java b/dx/src/com/android/jack/dx/dex/code/LocalEnd.java new file mode 100644 index 00000000..a72044ae --- /dev/null +++ b/dx/src/com/android/jack/dx/dex/code/LocalEnd.java @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.dex.code; + +import com.android.jack.dx.rop.code.RegisterSpec; +import com.android.jack.dx.rop.code.RegisterSpecList; +import com.android.jack.dx.rop.code.SourcePosition; + +/** + * Pseudo-instruction which is used to explicitly end the mapping of a + * register to a named local variable. That is, an instance of this + * class in an instruction stream indicates that starting with the + * subsequent instruction, the indicated variable is no longer valid. + */ +public final class LocalEnd extends ZeroSizeInsn { + /** + * {@code non-null;} register spec representing the local variable ended + * by this instance. Note: Technically, only the register + * number needs to be recorded here as the rest of the information + * is implicit in the ambient local variable state, but other code + * will check the other info for consistency. + */ + private final RegisterSpec local; + + /** + * Constructs an instance. The output address of this instance is initially + * unknown ({@code -1}). + * + * @param position {@code non-null;} source position + * @param local {@code non-null;} register spec representing the local + * variable introduced by this instance + */ + public LocalEnd(SourcePosition position, RegisterSpec local) { + super(position); + + if (local == null) { + throw new NullPointerException("local == null"); + } + + this.local = local; + } + + /** {@inheritDoc} */ + @Override + public DalvInsn withRegisterOffset(int delta) { + return new LocalEnd(getPosition(), local.withOffset(delta)); + } + + /** {@inheritDoc} */ + @Override + public DalvInsn withRegisters(RegisterSpecList registers) { + return new LocalEnd(getPosition(), local); + } + + /** + * Gets the register spec representing the local variable ended + * by this instance. + * + * @return {@code non-null;} the register spec + */ + public RegisterSpec getLocal() { + return local; + } + + /** {@inheritDoc} */ + @Override + protected String argString() { + return local.toString(); + } + + /** {@inheritDoc} */ + @Override + protected String listingString0(boolean noteIndices) { + return "local-end " + LocalStart.localString(local); + } +} diff --git a/dx/src/com/android/jack/dx/dex/code/LocalList.java b/dx/src/com/android/jack/dx/dex/code/LocalList.java new file mode 100644 index 00000000..0a07a602 --- /dev/null +++ b/dx/src/com/android/jack/dx/dex/code/LocalList.java @@ -0,0 +1,948 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.dex.code; + +import com.android.jack.dx.rop.code.RegisterSpec; +import com.android.jack.dx.rop.code.RegisterSpecSet; +import com.android.jack.dx.rop.cst.CstString; +import com.android.jack.dx.rop.cst.CstType; +import com.android.jack.dx.rop.type.Type; +import com.android.jack.dx.util.FixedSizeList; + +import java.io.PrintStream; +import java.util.ArrayList; +import java.util.Arrays; + +/** + * List of local variables. Each local variable entry indicates a + * range of code which it is valid for, a register number, a name, + * and a type. + */ +public final class LocalList extends FixedSizeList { + /** {@code non-null;} empty instance */ + public static final LocalList EMPTY = new LocalList(0); + + /** whether to run the self-check code */ + private static final boolean DEBUG = false; + + /** + * Constructs an instance. All indices initially contain {@code null}. + * + * @param size {@code >= 0;} the size of the list + */ + public LocalList(int size) { + super(size); + } + + /** + * Gets the element at the given index. It is an error to call + * this with the index for an element which was never set; if you + * do that, this will throw {@code NullPointerException}. + * + * @param n {@code >= 0, < size();} which index + * @return {@code non-null;} element at that index + */ + public Entry get(int n) { + return (Entry) get0(n); + } + + /** + * Sets the entry at the given index. + * + * @param n {@code >= 0, < size();} which index + * @param entry {@code non-null;} the entry to set at {@code n} + */ + public void set(int n, Entry entry) { + set0(n, entry); + } + + /** + * Does a human-friendly dump of this instance. + * + * @param out {@code non-null;} where to dump + * @param prefix {@code non-null;} prefix to attach to each line of output + */ + public void debugPrint(PrintStream out, String prefix) { + int sz = size(); + + for (int i = 0; i < sz; i++) { + out.print(prefix); + out.println(get(i)); + } + } + + /** + * Disposition of a local entry. + */ + public static enum Disposition { + /** local started (introduced) */ + START, + + /** local ended without being replaced */ + END_SIMPLY, + + /** local ended because it was directly replaced */ + END_REPLACED, + + /** local ended because it was moved to a different register */ + END_MOVED, + + /** + * local ended because the previous local clobbered this one + * (because it is category-2) + */ + END_CLOBBERED_BY_PREV, + + /** + * local ended because the next local clobbered this one + * (because this one is a category-2) + */ + END_CLOBBERED_BY_NEXT; + } + + /** + * Entry in a local list. + */ + public static class Entry implements Comparable { + /** {@code >= 0;} address */ + private final int address; + + /** {@code non-null;} disposition of the local */ + private final Disposition disposition; + + /** {@code non-null;} register spec representing the variable */ + private final RegisterSpec spec; + + /** {@code non-null;} variable type (derived from {@code spec}) */ + private final CstType type; + + /** + * Constructs an instance. + * + * @param address {@code >= 0;} address + * @param disposition {@code non-null;} disposition of the local + * @param spec {@code non-null;} register spec representing + * the variable + */ + public Entry(int address, Disposition disposition, RegisterSpec spec) { + if (address < 0) { + throw new IllegalArgumentException("address < 0"); + } + + if (disposition == null) { + throw new NullPointerException("disposition == null"); + } + + try { + if (spec.getLocalItem() == null) { + throw new NullPointerException( + "spec.getLocalItem() == null"); + } + } catch (NullPointerException ex) { + // Elucidate the exception. + throw new NullPointerException("spec == null"); + } + + this.address = address; + this.disposition = disposition; + this.spec = spec; + this.type = spec.getLocalItem().getType(); + } + + /** {@inheritDoc} */ + public String toString() { + return Integer.toHexString(address) + " " + disposition + " " + + spec; + } + + /** {@inheritDoc} */ + public boolean equals(Object other) { + if (!(other instanceof Entry)) { + return false; + } + + return (compareTo((Entry) other) == 0); + } + + /** + * Compares by (in priority order) address, end then start + * disposition (variants of end are all consistered + * equivalent), and spec. + * + * @param other {@code non-null;} entry to compare to + * @return {@code -1..1;} standard result of comparison + */ + public int compareTo(Entry other) { + if (address < other.address) { + return -1; + } else if (address > other.address) { + return 1; + } + + boolean thisIsStart = isStart(); + boolean otherIsStart = other.isStart(); + + if (thisIsStart != otherIsStart) { + return thisIsStart ? 1 : -1; + } + + return spec.compareTo(other.spec); + } + + /** + * Gets the address. + * + * @return {@code >= 0;} the address + */ + public int getAddress() { + return address; + } + + /** + * Gets the disposition. + * + * @return {@code non-null;} the disposition + */ + public Disposition getDisposition() { + return disposition; + } + + /** + * Gets whether this is a local start. This is just shorthand for + * {@code getDisposition() == Disposition.START}. + * + * @return {@code true} iff this is a start + */ + public boolean isStart() { + return disposition == Disposition.START; + } + + /** + * Gets the variable name. + * + * @return {@code null-ok;} the variable name + */ + public CstString getName() { + return spec.getLocalItem().getName(); + } + + /** + * Gets the variable signature. + * + * @return {@code null-ok;} the variable signature + */ + public CstString getSignature() { + return spec.getLocalItem().getSignature(); + } + + /** + * Gets the variable's type. + * + * @return {@code non-null;} the type + */ + public CstType getType() { + return type; + } + + /** + * Gets the number of the register holding the variable. + * + * @return {@code >= 0;} the number of the register holding + * the variable + */ + public int getRegister() { + return spec.getReg(); + } + + /** + * Gets the RegisterSpec of the register holding the variable. + * + * @return {@code non-null;} RegisterSpec of the holding register. + */ + public RegisterSpec getRegisterSpec() { + return spec; + } + + /** + * Returns whether or not this instance matches the given spec. + * + * @param otherSpec {@code non-null;} the spec in question + * @return {@code true} iff this instance matches + * {@code spec} + */ + public boolean matches(RegisterSpec otherSpec) { + return spec.equalsUsingSimpleType(otherSpec); + } + + /** + * Returns whether or not this instance matches the spec in + * the given instance. + * + * @param other {@code non-null;} another entry + * @return {@code true} iff this instance's spec matches + * {@code other} + */ + public boolean matches(Entry other) { + return matches(other.spec); + } + + /** + * Returns an instance just like this one but with the disposition + * set as given. + * + * @param disposition {@code non-null;} the new disposition + * @return {@code non-null;} an appropriately-constructed instance + */ + public Entry withDisposition(Disposition disposition) { + if (disposition == this.disposition) { + return this; + } + + return new Entry(address, disposition, spec); + } + } + + /** + * Constructs an instance for the given method, based on the given + * block order and intermediate local information. + * + * @param insns {@code non-null;} instructions to convert + * @return {@code non-null;} the constructed list + */ + public static LocalList make(DalvInsnList insns) { + int sz = insns.size(); + + /* + * Go through the insn list, looking for all the local + * variable pseudoinstructions, splitting out LocalSnapshots + * into separate per-variable starts, adding explicit ends + * wherever a variable is replaced or moved, and collecting + * these and all the other local variable "activity" + * together into an output list (without the other insns). + * + * Note: As of this writing, this method won't be handed any + * insn lists that contain local ends, but I (danfuzz) expect + * that to change at some point, when we start feeding that + * info explicitly into the rop layer rather than only trying + * to infer it. So, given that expectation, this code is + * written to deal with them. + */ + + MakeState state = new MakeState(sz); + + for (int i = 0; i < sz; i++) { + DalvInsn insn = insns.get(i); + + if (insn instanceof LocalSnapshot) { + RegisterSpecSet snapshot = + ((LocalSnapshot) insn).getLocals(); + state.snapshot(insn.getAddress(), snapshot); + } else if (insn instanceof LocalStart) { + RegisterSpec local = ((LocalStart) insn).getLocal(); + state.startLocal(insn.getAddress(), local); + } else if (insn instanceof LocalEnd) { + RegisterSpec local = ((LocalEnd) insn).getLocal(); + state.endLocal(insn.getAddress(), local); + } + } + + LocalList result = state.finish(); + + if (DEBUG) { + debugVerify(result); + } + + return result; + } + + /** + * Debugging helper that verifies the constraint that a list doesn't + * contain any redundant local starts and that local ends that are + * due to replacements are properly annotated. + */ + private static void debugVerify(LocalList locals) { + try { + debugVerify0(locals); + } catch (RuntimeException ex) { + int sz = locals.size(); + for (int i = 0; i < sz; i++) { + System.err.println(locals.get(i)); + } + throw ex; + } + + } + + /** + * Helper for {@link #debugVerify} which does most of the work. + */ + private static void debugVerify0(LocalList locals) { + int sz = locals.size(); + Entry[] active = new Entry[65536]; + + for (int i = 0; i < sz; i++) { + Entry e = locals.get(i); + int reg = e.getRegister(); + + if (e.isStart()) { + Entry already = active[reg]; + + if ((already != null) && e.matches(already)) { + throw new RuntimeException("redundant start at " + + Integer.toHexString(e.getAddress()) + ": got " + + e + "; had " + already); + } + + active[reg] = e; + } else { + if (active[reg] == null) { + throw new RuntimeException("redundant end at " + + Integer.toHexString(e.getAddress())); + } + + int addr = e.getAddress(); + boolean foundStart = false; + + for (int j = i + 1; j < sz; j++) { + Entry test = locals.get(j); + if (test.getAddress() != addr) { + break; + } + if (test.getRegisterSpec().getReg() == reg) { + if (test.isStart()) { + if (e.getDisposition() + != Disposition.END_REPLACED) { + throw new RuntimeException( + "improperly marked end at " + + Integer.toHexString(addr)); + } + foundStart = true; + } else { + throw new RuntimeException( + "redundant end at " + + Integer.toHexString(addr)); + } + } + } + + if (!foundStart && + (e.getDisposition() == Disposition.END_REPLACED)) { + throw new RuntimeException( + "improper end replacement claim at " + + Integer.toHexString(addr)); + } + + active[reg] = null; + } + } + } + + /** + * Intermediate state when constructing a local list. + */ + public static class MakeState { + /** {@code non-null;} result being collected */ + private final ArrayList result; + + /** + * {@code >= 0;} running count of nulled result entries, to help with + * sizing the final list + */ + private int nullResultCount; + + /** {@code null-ok;} current register mappings */ + private RegisterSpecSet regs; + + /** {@code null-ok;} result indices where local ends are stored */ + private int[] endIndices; + + /** {@code >= 0;} last address seen */ + private int lastAddress; + + /** + * Constructs an instance. + */ + public MakeState(int initialSize) { + result = new ArrayList(initialSize); + nullResultCount = 0; + regs = null; + endIndices = null; + lastAddress = 0; + } + + /** + * Checks the address and other vitals as a prerequisite to + * further processing. + * + * @param address {@code >= 0;} address about to be processed + * @param reg {@code >= 0;} register number about to be processed + */ + private void aboutToProcess(int address, int reg) { + boolean first = (endIndices == null); + + if ((address == lastAddress) && !first) { + return; + } + + if (address < lastAddress) { + throw new RuntimeException("shouldn't happen"); + } + + if (first || (reg >= endIndices.length)) { + /* + * This is the first allocation of the state set and + * index array, or we need to grow. (The latter doesn't + * happen much; in fact, we have only ever observed + * it happening in test cases, never in "real" code.) + */ + int newSz = reg + 1; + RegisterSpecSet newRegs = new RegisterSpecSet(newSz); + int[] newEnds = new int[newSz]; + Arrays.fill(newEnds, -1); + + if (!first) { + newRegs.putAll(regs); + System.arraycopy(endIndices, 0, newEnds, 0, + endIndices.length); + } + + regs = newRegs; + endIndices = newEnds; + } + } + + /** + * Sets the local state at the given address to the given snapshot. + * The first call on this instance must be to this method, so that + * the register state can be properly sized. + * + * @param address {@code >= 0;} the address + * @param specs {@code non-null;} spec set representing the locals + */ + public void snapshot(int address, RegisterSpecSet specs) { + if (DEBUG) { + System.err.printf("%04x snapshot %s\n", address, specs); + } + + int sz = specs.getMaxSize(); + aboutToProcess(address, sz - 1); + + for (int i = 0; i < sz; i++) { + RegisterSpec oldSpec = regs.get(i); + RegisterSpec newSpec = filterSpec(specs.get(i)); + + if (oldSpec == null) { + if (newSpec != null) { + startLocal(address, newSpec); + } + } else if (newSpec == null) { + endLocal(address, oldSpec); + } else if (! newSpec.equalsUsingSimpleType(oldSpec)) { + endLocal(address, oldSpec); + startLocal(address, newSpec); + } + } + + if (DEBUG) { + System.err.printf("%04x snapshot done\n", address); + } + } + + /** + * Starts a local at the given address. + * + * @param address {@code >= 0;} the address + * @param startedLocal {@code non-null;} spec representing the + * started local + */ + public void startLocal(int address, RegisterSpec startedLocal) { + if (DEBUG) { + System.err.printf("%04x start %s\n", address, startedLocal); + } + + int regNum = startedLocal.getReg(); + + startedLocal = filterSpec(startedLocal); + aboutToProcess(address, regNum); + + RegisterSpec existingLocal = regs.get(regNum); + + if (startedLocal.equalsUsingSimpleType(existingLocal)) { + // Silently ignore a redundant start. + return; + } + + RegisterSpec movedLocal = regs.findMatchingLocal(startedLocal); + if (movedLocal != null) { + /* + * The same variable was moved from one register to another. + * So add an end for its old location. + */ + addOrUpdateEnd(address, Disposition.END_MOVED, movedLocal); + } + + int endAt = endIndices[regNum]; + + if (existingLocal != null) { + /* + * There is an existing (but non-matching) local. + * Add an explicit end for it. + */ + add(address, Disposition.END_REPLACED, existingLocal); + } else if (endAt >= 0) { + /* + * Look for an end local for the same register at the + * same address. If found, then update it or delete + * it, depending on whether or not it represents the + * same variable as the one being started. + */ + Entry endEntry = result.get(endAt); + if (endEntry.getAddress() == address) { + if (endEntry.matches(startedLocal)) { + /* + * There was already an end local for the same + * variable at the same address. This turns + * out to be superfluous, as we are starting + * up the exact same local. This situation can + * happen when a single local variable got + * somehow "split up" during intermediate + * processing. In any case, rather than represent + * the end-then-start, just remove the old end. + */ + result.set(endAt, null); + nullResultCount++; + regs.put(startedLocal); + endIndices[regNum] = -1; + return; + } else { + /* + * There was a different variable ended at the + * same address. Update it to indicate that + * it was ended due to a replacement (rather than + * ending for no particular reason). + */ + endEntry = endEntry.withDisposition( + Disposition.END_REPLACED); + result.set(endAt, endEntry); + } + } + } + + /* + * The code above didn't find and remove an unnecessary + * local end, so we now have to add one or more entries to + * the output to capture the transition. + */ + + /* + * If the local just below (in the register set at reg-1) + * is of category-2, then it is ended by this new start. + */ + if (regNum > 0) { + RegisterSpec justBelow = regs.get(regNum - 1); + if ((justBelow != null) && justBelow.isCategory2()) { + addOrUpdateEnd(address, + Disposition.END_CLOBBERED_BY_NEXT, + justBelow); + } + } + + /* + * Similarly, if this local is category-2, then the local + * just above (if any) is ended by the start now being + * emitted. + */ + if (startedLocal.isCategory2()) { + RegisterSpec justAbove = regs.get(regNum + 1); + if (justAbove != null) { + addOrUpdateEnd(address, + Disposition.END_CLOBBERED_BY_PREV, + justAbove); + } + } + + /* + * TODO: Add an end for the same local in a different reg, + * if any (that is, if the local migrates from vX to vY, + * we should note that as a local end in vX). + */ + + add(address, Disposition.START, startedLocal); + } + + /** + * Ends a local at the given address, using the disposition + * {@code END_SIMPLY}. + * + * @param address {@code >= 0;} the address + * @param endedLocal {@code non-null;} spec representing the + * local being ended + */ + public void endLocal(int address, RegisterSpec endedLocal) { + endLocal(address, endedLocal, Disposition.END_SIMPLY); + } + + /** + * Ends a local at the given address. + * + * @param address {@code >= 0;} the address + * @param endedLocal {@code non-null;} spec representing the + * local being ended + * @param disposition reason for the end + */ + public void endLocal(int address, RegisterSpec endedLocal, + Disposition disposition) { + if (DEBUG) { + System.err.printf("%04x end %s\n", address, endedLocal); + } + + int regNum = endedLocal.getReg(); + + endedLocal = filterSpec(endedLocal); + aboutToProcess(address, regNum); + + int endAt = endIndices[regNum]; + + if (endAt >= 0) { + /* + * The local in the given register is already ended. + * Silently return without adding anything to the result. + */ + return; + } + + // Check for start and end at the same address. + if (checkForEmptyRange(address, endedLocal)) { + return; + } + + add(address, disposition, endedLocal); + } + + /** + * Helper for {@link #endLocal}, which handles the cases where + * and end local is issued at the same address as a start local + * for the same register. If this case is found, then this + * method will remove the start (as the local was never actually + * active), update the {@link #endIndices} to be accurate, and + * if needed update the newly-active end to reflect an altered + * disposition. + * + * @param address {@code >= 0;} the address + * @param endedLocal {@code non-null;} spec representing the + * local being ended + * @return {@code true} iff this method found the case in question + * and adjusted things accordingly + */ + private boolean checkForEmptyRange(int address, + RegisterSpec endedLocal) { + int at = result.size() - 1; + Entry entry; + + // Look for a previous entry at the same address. + for (/*at*/; at >= 0; at--) { + entry = result.get(at); + + if (entry == null) { + continue; + } + + if (entry.getAddress() != address) { + // We didn't find any match at the same address. + return false; + } + + if (entry.matches(endedLocal)) { + break; + } + } + + /* + * In fact, we found that the endedLocal had started at the + * same address, so do all the requisite cleanup. + */ + + regs.remove(endedLocal); + result.set(at, null); + nullResultCount++; + + int regNum = endedLocal.getReg(); + boolean found = false; + entry = null; + + // Now look back further to update where the register ended. + for (at--; at >= 0; at--) { + entry = result.get(at); + + if (entry == null) { + continue; + } + + if (entry.getRegisterSpec().getReg() == regNum) { + found = true; + break; + } + } + + if (found) { + // We found an end for the same register. + endIndices[regNum] = at; + + if (entry.getAddress() == address) { + /* + * It's still the same address, so update the + * disposition. + */ + result.set(at, + entry.withDisposition(Disposition.END_SIMPLY)); + } + } + + return true; + } + + /** + * Converts a given spec into the form acceptable for use in a + * local list. This, in particular, transforms the "known + * null" type into simply {@code Object}. This method needs to + * be called for any spec that is on its way into a locals + * list. + * + *

This isn't necessarily the cleanest way to achieve the + * goal of not representing known nulls in a locals list, but + * it gets the job done.

+ * + * @param orig {@code null-ok;} the original spec + * @return {@code null-ok;} an appropriately modified spec, or the + * original if nothing needs to be done + */ + private static RegisterSpec filterSpec(RegisterSpec orig) { + if ((orig != null) && (orig.getType() == Type.KNOWN_NULL)) { + return orig.withType(Type.OBJECT); + } + + return orig; + } + + /** + * Adds an entry to the result, updating the adjunct tables + * accordingly. + * + * @param address {@code >= 0;} the address + * @param disposition {@code non-null;} the disposition + * @param spec {@code non-null;} spec representing the local + */ + private void add(int address, Disposition disposition, + RegisterSpec spec) { + int regNum = spec.getReg(); + + result.add(new Entry(address, disposition, spec)); + + if (disposition == Disposition.START) { + regs.put(spec); + endIndices[regNum] = -1; + } else { + regs.remove(spec); + endIndices[regNum] = result.size() - 1; + } + } + + /** + * Adds or updates an end local (changing its disposition). If + * this would cause an empty range for a local, this instead + * removes the local entirely. + * + * @param address {@code >= 0;} the address + * @param disposition {@code non-null;} the disposition + * @param spec {@code non-null;} spec representing the local + */ + private void addOrUpdateEnd(int address, Disposition disposition, + RegisterSpec spec) { + if (disposition == Disposition.START) { + throw new RuntimeException("shouldn't happen"); + } + + int regNum = spec.getReg(); + int endAt = endIndices[regNum]; + + if (endAt >= 0) { + // There is a previous end. + Entry endEntry = result.get(endAt); + if ((endEntry.getAddress() == address) && + endEntry.getRegisterSpec().equals(spec)) { + /* + * The end is for the right address and variable, so + * update it. + */ + result.set(endAt, endEntry.withDisposition(disposition)); + regs.remove(spec); // TODO: Is this line superfluous? + return; + } + } + + endLocal(address, spec, disposition); + } + + /** + * Finishes processing altogether and gets the result. + * + * @return {@code non-null;} the result list + */ + public LocalList finish() { + aboutToProcess(Integer.MAX_VALUE, 0); + + int resultSz = result.size(); + int finalSz = resultSz - nullResultCount; + + if (finalSz == 0) { + return EMPTY; + } + + /* + * Collect an array of only the non-null entries, and then + * sort it to get a consistent order for everything: Local + * ends and starts for a given address could come in any + * order, but we want ends before starts as well as + * registers in order (within ends or starts). + */ + + Entry[] resultArr = new Entry[finalSz]; + + if (resultSz == finalSz) { + result.toArray(resultArr); + } else { + int at = 0; + for (Entry e : result) { + if (e != null) { + resultArr[at++] = e; + } + } + } + + Arrays.sort(resultArr); + + LocalList resultList = new LocalList(finalSz); + + for (int i = 0; i < finalSz; i++) { + resultList.set(i, resultArr[i]); + } + + resultList.setImmutable(); + return resultList; + } + } +} diff --git a/dx/src/com/android/jack/dx/dex/code/LocalSnapshot.java b/dx/src/com/android/jack/dx/dex/code/LocalSnapshot.java new file mode 100644 index 00000000..01f3943f --- /dev/null +++ b/dx/src/com/android/jack/dx/dex/code/LocalSnapshot.java @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.dex.code; + +import com.android.jack.dx.rop.code.RegisterSpec; +import com.android.jack.dx.rop.code.RegisterSpecList; +import com.android.jack.dx.rop.code.RegisterSpecSet; +import com.android.jack.dx.rop.code.SourcePosition; + +/** + * Pseudo-instruction which is used to hold a snapshot of the + * state of local variable name mappings that exists immediately after + * the instance in an instruction array. + */ +public final class LocalSnapshot extends ZeroSizeInsn { + /** {@code non-null;} local state associated with this instance */ + private final RegisterSpecSet locals; + + /** + * Constructs an instance. The output address of this instance is initially + * unknown ({@code -1}). + * + * @param position {@code non-null;} source position + * @param locals {@code non-null;} associated local variable state + */ + public LocalSnapshot(SourcePosition position, RegisterSpecSet locals) { + super(position); + + if (locals == null) { + throw new NullPointerException("locals == null"); + } + + this.locals = locals; + } + + /** {@inheritDoc} */ + @Override + public DalvInsn withRegisterOffset(int delta) { + return new LocalSnapshot(getPosition(), locals.withOffset(delta)); + } + + /** {@inheritDoc} */ + @Override + public DalvInsn withRegisters(RegisterSpecList registers) { + return new LocalSnapshot(getPosition(), locals); + } + + /** + * Gets the local state associated with this instance. + * + * @return {@code non-null;} the state + */ + public RegisterSpecSet getLocals() { + return locals; + } + + /** {@inheritDoc} */ + @Override + protected String argString() { + return locals.toString(); + } + + /** {@inheritDoc} */ + @Override + protected String listingString0(boolean noteIndices) { + int sz = locals.size(); + int max = locals.getMaxSize(); + StringBuffer sb = new StringBuffer(100 + sz * 40); + + sb.append("local-snapshot"); + + for (int i = 0; i < max; i++) { + RegisterSpec spec = locals.get(i); + if (spec != null) { + sb.append("\n "); + sb.append(LocalStart.localString(spec)); + } + } + + return sb.toString(); + } +} diff --git a/dx/src/com/android/jack/dx/dex/code/LocalStart.java b/dx/src/com/android/jack/dx/dex/code/LocalStart.java new file mode 100644 index 00000000..9b8a4007 --- /dev/null +++ b/dx/src/com/android/jack/dx/dex/code/LocalStart.java @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.dex.code; + +import com.android.jack.dx.rop.code.RegisterSpec; +import com.android.jack.dx.rop.code.RegisterSpecList; +import com.android.jack.dx.rop.code.SourcePosition; + +/** + * Pseudo-instruction which is used to introduce a new local variable. That + * is, an instance of this class in an instruction stream indicates that + * starting with the subsequent instruction, the indicated variable + * is bound. + */ +public final class LocalStart extends ZeroSizeInsn { + /** + * {@code non-null;} register spec representing the local variable introduced + * by this instance + */ + private final RegisterSpec local; + + /** + * Returns the local variable listing string for a single register spec. + * + * @param spec {@code non-null;} the spec to convert + * @return {@code non-null;} the string form + */ + public static String localString(RegisterSpec spec) { + return spec.regString() + ' ' + spec.getLocalItem().toString() + ": " + + spec.getTypeBearer().toHuman(); + } + + /** + * Constructs an instance. The output address of this instance is initially + * unknown ({@code -1}). + * + * @param position {@code non-null;} source position + * @param local {@code non-null;} register spec representing the local + * variable introduced by this instance + */ + public LocalStart(SourcePosition position, RegisterSpec local) { + super(position); + + if (local == null) { + throw new NullPointerException("local == null"); + } + + this.local = local; + } + + /** {@inheritDoc} */ + @Override + public DalvInsn withRegisterOffset(int delta) { + return new LocalStart(getPosition(), local.withOffset(delta)); + } + + /** {@inheritDoc} */ + @Override + public DalvInsn withRegisters(RegisterSpecList registers) { + return new LocalStart(getPosition(), local); + } + + /** + * Gets the register spec representing the local variable introduced + * by this instance. + * + * @return {@code non-null;} the register spec + */ + public RegisterSpec getLocal() { + return local; + } + + /** {@inheritDoc} */ + @Override + protected String argString() { + return local.toString(); + } + + /** {@inheritDoc} */ + @Override + protected String listingString0(boolean noteIndices) { + return "local-start " + localString(local); + } +} diff --git a/dx/src/com/android/jack/dx/dex/code/OddSpacer.java b/dx/src/com/android/jack/dx/dex/code/OddSpacer.java new file mode 100644 index 00000000..c9166f57 --- /dev/null +++ b/dx/src/com/android/jack/dx/dex/code/OddSpacer.java @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.dex.code; + +import com.android.jack.dx.io.Opcodes; +import com.android.jack.dx.rop.code.RegisterSpecList; +import com.android.jack.dx.rop.code.SourcePosition; +import com.android.jack.dx.util.AnnotatedOutput; + +/** + * Pseudo-instruction which either turns into a {@code nop} or + * nothingness, in order to make the subsequent instruction have an + * even address. This is used to align (subsequent) instructions that + * require it. + */ +public final class OddSpacer extends VariableSizeInsn { + /** + * Constructs an instance. The output address of this instance is initially + * unknown ({@code -1}). + * + * @param position {@code non-null;} source position + */ + public OddSpacer(SourcePosition position) { + super(position, RegisterSpecList.EMPTY); + } + + /** {@inheritDoc} */ + @Override + public int codeSize() { + return (getAddress() & 1); + } + + /** {@inheritDoc} */ + @Override + public void writeTo(AnnotatedOutput out) { + if (codeSize() != 0) { + out.writeShort(InsnFormat.codeUnit(Opcodes.NOP, 0)); + } + } + + /** {@inheritDoc} */ + @Override + public DalvInsn withRegisters(RegisterSpecList registers) { + return new OddSpacer(getPosition()); + } + + /** {@inheritDoc} */ + @Override + protected String argString() { + return null; + } + + /** {@inheritDoc} */ + @Override + protected String listingString0(boolean noteIndices) { + if (codeSize() == 0) { + return null; + } + + return "nop // spacer"; + } +} diff --git a/dx/src/com/android/jack/dx/dex/code/OutputCollector.java b/dx/src/com/android/jack/dx/dex/code/OutputCollector.java new file mode 100644 index 00000000..3225b24a --- /dev/null +++ b/dx/src/com/android/jack/dx/dex/code/OutputCollector.java @@ -0,0 +1,121 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.dex.code; + +import com.android.jack.dx.dex.DexOptions; + +import java.util.ArrayList; + +/** + * Destination for {@link DalvInsn} instances being output. This class + * receives and collects instructions in two pieces — a primary + * list and a suffix (generally consisting of adjunct data referred to + * by the primary list, such as switch case tables) — which it + * merges and emits back out in the form of a {@link DalvInsnList} + * instance. + */ +public final class OutputCollector { + /** + * {@code non-null;} the associated finisher (which holds the instruction + * list in-progress) + */ + private final OutputFinisher finisher; + + /** + * {@code null-ok;} suffix for the output, or {@code null} if the suffix + * has been appended to the main output (by {@link #appendSuffixToOutput}) + */ + private ArrayList suffix; + + /** + * Constructs an instance. + * + * @param dexOptions {@code non-null;} options for dex output + * @param initialCapacity {@code >= 0;} initial capacity of the output list + * @param suffixInitialCapacity {@code >= 0;} initial capacity of the output + * suffix + * @param regCount {@code >= 0;} register count for the method + */ + public OutputCollector(DexOptions dexOptions, int initialCapacity, int suffixInitialCapacity, + int regCount) { + this.finisher = new OutputFinisher(dexOptions, initialCapacity, regCount); + this.suffix = new ArrayList(suffixInitialCapacity); + } + + /** + * Adds an instruction to the output. + * + * @param insn {@code non-null;} the instruction to add + */ + public void add(DalvInsn insn) { + finisher.add(insn); + } + + /** + * Reverses a branch which is buried a given number of instructions + * backward in the output. It is illegal to call this unless the + * indicated instruction really is a reversible branch. + * + * @param which how many instructions back to find the branch; + * {@code 0} is the most recently added instruction, + * {@code 1} is the instruction before that, etc. + * @param newTarget {@code non-null;} the new target for the reversed branch + */ + public void reverseBranch(int which, CodeAddress newTarget) { + finisher.reverseBranch(which, newTarget); + } + + /** + * Adds an instruction to the output suffix. + * + * @param insn {@code non-null;} the instruction to add + */ + public void addSuffix(DalvInsn insn) { + suffix.add(insn); + } + + /** + * Gets the results of all the calls on this instance, in the form of + * an {@link OutputFinisher}. + * + * @return {@code non-null;} the output finisher + * @throws UnsupportedOperationException if this method has + * already been called + */ + public OutputFinisher getFinisher() { + if (suffix == null) { + throw new UnsupportedOperationException("already processed"); + } + + appendSuffixToOutput(); + return finisher; + } + + /** + * Helper for {@link #getFinisher}, which appends the suffix to + * the primary output. + */ + private void appendSuffixToOutput() { + int size = suffix.size(); + + for (int i = 0; i < size; i++) { + finisher.add(suffix.get(i)); + } + + suffix = null; + } +} diff --git a/dx/src/com/android/jack/dx/dex/code/OutputFinisher.java b/dx/src/com/android/jack/dx/dex/code/OutputFinisher.java new file mode 100644 index 00000000..f004b02a --- /dev/null +++ b/dx/src/com/android/jack/dx/dex/code/OutputFinisher.java @@ -0,0 +1,784 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.dex.code; + + +import com.android.jack.dx.dex.DexOptions; +import com.android.jack.dx.io.Opcodes; +import com.android.jack.dx.rop.code.LocalItem; +import com.android.jack.dx.rop.code.RegisterSpec; +import com.android.jack.dx.rop.code.RegisterSpecList; +import com.android.jack.dx.rop.code.RegisterSpecSet; +import com.android.jack.dx.rop.code.SourcePosition; +import com.android.jack.dx.rop.cst.Constant; +import com.android.jack.dx.rop.cst.CstMemberRef; +import com.android.jack.dx.rop.cst.CstString; +import com.android.jack.dx.rop.cst.CstType; +import com.android.jack.dx.rop.type.Type; +import com.android.jack.dx.util.DexException; + +import java.util.ArrayList; +import java.util.BitSet; +import java.util.HashSet; + +/** + * Processor for instruction lists, which takes a "first cut" of + * instruction selection as a basis and produces a "final cut" in the + * form of a {@link DalvInsnList} instance. + */ +public final class OutputFinisher { + /** {@code non-null;} options for dex output */ + private final DexOptions dexOptions; + + /** + * {@code >= 0;} register count for the method, not including any extra + * "reserved" registers needed to translate "difficult" instructions + */ + private final int unreservedRegCount; + + /** {@code non-null;} the list of instructions, per se */ + private ArrayList insns; + + /** whether any instruction has position info */ + private boolean hasAnyPositionInfo; + + /** whether any instruction has local variable info */ + private boolean hasAnyLocalInfo; + + /** + * {@code >= 0;} the count of reserved registers (low-numbered + * registers used when expanding instructions that can't be + * represented simply); becomes valid after a call to {@link + * #massageInstructions} + */ + private int reservedCount; + + /** + * Constructs an instance. It initially contains no instructions. + * + * @param dexOptions {@code non-null;} options for dex output + * @param regCount {@code >= 0;} register count for the method + * @param initialCapacity {@code >= 0;} initial capacity of the + * instructions list + */ + public OutputFinisher(DexOptions dexOptions, int initialCapacity, int regCount) { + this.dexOptions = dexOptions; + this.unreservedRegCount = regCount; + this.insns = new ArrayList(initialCapacity); + this.reservedCount = -1; + this.hasAnyPositionInfo = false; + this.hasAnyLocalInfo = false; + } + + /** + * Returns whether any of the instructions added to this instance + * come with position info. + * + * @return whether any of the instructions added to this instance + * come with position info + */ + public boolean hasAnyPositionInfo() { + return hasAnyPositionInfo; + } + + /** + * Returns whether this instance has any local variable information. + * + * @return whether this instance has any local variable information + */ + public boolean hasAnyLocalInfo() { + return hasAnyLocalInfo; + } + + /** + * Helper for {@link #add} which scrutinizes a single + * instruction for local variable information. + * + * @param insn {@code non-null;} instruction to scrutinize + * @return {@code true} iff the instruction refers to any + * named locals + */ + private static boolean hasLocalInfo(DalvInsn insn) { + if (insn instanceof LocalSnapshot) { + RegisterSpecSet specs = ((LocalSnapshot) insn).getLocals(); + int size = specs.size(); + for (int i = 0; i < size; i++) { + if (hasLocalInfo(specs.get(i))) { + return true; + } + } + } else if (insn instanceof LocalStart) { + RegisterSpec spec = ((LocalStart) insn).getLocal(); + if (hasLocalInfo(spec)) { + return true; + } + } + + return false; + } + + /** + * Helper for {@link #hasAnyLocalInfo} which scrutinizes a single + * register spec. + * + * @param spec {@code non-null;} spec to scrutinize + * @return {@code true} iff the spec refers to any + * named locals + */ + private static boolean hasLocalInfo(RegisterSpec spec) { + return (spec != null) + && (spec.getLocalItem().getName() != null); + } + + /** + * Returns the set of all constants referred to by instructions added + * to this instance. + * + * @return {@code non-null;} the set of constants + */ + public HashSet getAllConstants() { + HashSet result = new HashSet(20); + + for (DalvInsn insn : insns) { + addConstants(result, insn); + } + + return result; + } + + /** + * Helper for {@link #getAllConstants} which adds all the info for + * a single instruction. + * + * @param result {@code non-null;} result set to add to + * @param insn {@code non-null;} instruction to scrutinize + */ + private static void addConstants(HashSet result, + DalvInsn insn) { + if (insn instanceof CstInsn) { + Constant cst = ((CstInsn) insn).getConstant(); + result.add(cst); + } else if (insn instanceof LocalSnapshot) { + RegisterSpecSet specs = ((LocalSnapshot) insn).getLocals(); + int size = specs.size(); + for (int i = 0; i < size; i++) { + addConstants(result, specs.get(i)); + } + } else if (insn instanceof LocalStart) { + RegisterSpec spec = ((LocalStart) insn).getLocal(); + addConstants(result, spec); + } + } + + /** + * Helper for {@link #getAllConstants} which adds all the info for + * a single {@code RegisterSpec}. + * + * @param result {@code non-null;} result set to add to + * @param spec {@code null-ok;} register spec to add + */ + private static void addConstants(HashSet result, + RegisterSpec spec) { + if (spec == null) { + return; + } + + LocalItem local = spec.getLocalItem(); + CstString name = local.getName(); + CstString signature = local.getSignature(); + Type type = spec.getType(); + CstType localType = local.getType(); + + if (type != Type.KNOWN_NULL) { + result.add(CstType.intern(type)); + } + + if (localType != null) { + result.add(localType); + } + + if (name != null) { + result.add(name); + } + + if (signature != null) { + result.add(signature); + } + } + + /** + * Adds an instruction to the output. + * + * @param insn {@code non-null;} the instruction to add + */ + public void add(DalvInsn insn) { + insns.add(insn); + updateInfo(insn); + } + + /** + * Inserts an instruction in the output at the given offset. + * + * @param at {@code >= 0;} what index to insert at + * @param insn {@code non-null;} the instruction to insert + */ + public void insert(int at, DalvInsn insn) { + insns.add(at, insn); + updateInfo(insn); + } + + /** + * Helper for {@link #add} and {@link #insert}, + * which updates the position and local info flags. + * + * @param insn {@code non-null;} an instruction that was just introduced + */ + private void updateInfo(DalvInsn insn) { + if (! hasAnyPositionInfo) { + SourcePosition pos = insn.getPosition(); + if (pos.getLine() >= 0) { + hasAnyPositionInfo = true; + } + } + + if (! hasAnyLocalInfo) { + if (hasLocalInfo(insn)) { + hasAnyLocalInfo = true; + } + } + } + + /** + * Reverses a branch which is buried a given number of instructions + * backward in the output. It is illegal to call this unless the + * indicated instruction really is a reversible branch. + * + * @param which how many instructions back to find the branch; + * {@code 0} is the most recently added instruction, + * {@code 1} is the instruction before that, etc. + * @param newTarget {@code non-null;} the new target for the + * reversed branch + */ + public void reverseBranch(int which, CodeAddress newTarget) { + int size = insns.size(); + int index = size - which - 1; + TargetInsn targetInsn; + + try { + targetInsn = (TargetInsn) insns.get(index); + } catch (IndexOutOfBoundsException ex) { + // Translate the exception. + throw new IllegalArgumentException("too few instructions"); + } catch (ClassCastException ex) { + // Translate the exception. + throw new IllegalArgumentException("non-reversible instruction"); + } + + /* + * No need to call this.set(), since the format and other info + * are the same. + */ + insns.set(index, targetInsn.withNewTargetAndReversed(newTarget)); + } + + /** + * Assigns indices in all instructions that need them, using the + * given callback to perform lookups. This should be called before + * calling {@link #finishProcessingAndGetList}. + * + * @param callback {@code non-null;} callback object + */ + public void assignIndices(DalvCode.AssignIndicesCallback callback) { + for (DalvInsn insn : insns) { + if (insn instanceof CstInsn) { + assignIndices((CstInsn) insn, callback); + } + } + } + + /** + * Helper for {@link #assignIndices} which does assignment for one + * instruction. + * + * @param insn {@code non-null;} the instruction + * @param callback {@code non-null;} the callback + */ + private static void assignIndices(CstInsn insn, + DalvCode.AssignIndicesCallback callback) { + Constant cst = insn.getConstant(); + int index = callback.getIndex(cst); + + if (index >= 0) { + insn.setIndex(index); + } + + if (cst instanceof CstMemberRef) { + CstMemberRef member = (CstMemberRef) cst; + CstType definer = member.getDefiningClass(); + index = callback.getIndex(definer); + if (index >= 0) { + insn.setClassIndex(index); + } + } + } + + /** + * Does final processing on this instance and gets the output as + * a {@link DalvInsnList}. Final processing consists of: + * + *
    + *
  • optionally renumbering registers (to make room as needed for + * expanded instructions)
  • + *
  • picking a final opcode for each instruction
  • + *
  • rewriting instructions, because of register number, + * constant pool index, or branch target size issues
  • + *
  • assigning final addresses
  • + *
+ * + *

Note: This method may only be called once per instance + * of this class.

+ * + * @return {@code non-null;} the output list + * @throws UnsupportedOperationException if this method has + * already been called + */ + public DalvInsnList finishProcessingAndGetList() { + if (reservedCount >= 0) { + throw new UnsupportedOperationException("already processed"); + } + + Dop[] opcodes = makeOpcodesArray(); + reserveRegisters(opcodes); + massageInstructions(opcodes); + assignAddressesAndFixBranches(); + + return DalvInsnList.makeImmutable(insns, + reservedCount + unreservedRegCount); + } + + /** + * Helper for {@link #finishProcessingAndGetList}, which extracts + * the opcode out of each instruction into a separate array, to be + * further manipulated as things progress. + * + * @return {@code non-null;} the array of opcodes + */ + private Dop[] makeOpcodesArray() { + int size = insns.size(); + Dop[] result = new Dop[size]; + + for (int i = 0; i < size; i++) { + result[i] = insns.get(i).getOpcode(); + } + + return result; + } + + /** + * Helper for {@link #finishProcessingAndGetList}, which figures + * out how many reserved registers are required and then reserving + * them. It also updates the given {@code opcodes} array so + * as to avoid extra work when constructing the massaged + * instruction list. + * + * @param opcodes {@code non-null;} array of per-instruction + * opcode selections + */ + private void reserveRegisters(Dop[] opcodes) { + int oldReservedCount = (reservedCount < 0) ? 0 : reservedCount; + + /* + * Call calculateReservedCount() and then perform register + * reservation, repeatedly until no new reservations happen. + */ + for (;;) { + int newReservedCount = calculateReservedCount(opcodes); + if (oldReservedCount >= newReservedCount) { + break; + } + + int reservedDifference = newReservedCount - oldReservedCount; + int size = insns.size(); + + for (int i = 0; i < size; i++) { + /* + * CodeAddress instance identity is used to link + * TargetInsns to their targets, so it is + * inappropriate to make replacements, and they don't + * have registers in any case. Hence, the instanceof + * test below. + */ + DalvInsn insn = insns.get(i); + if (!(insn instanceof CodeAddress)) { + /* + * No need to call this.set() since the format and + * other info are the same. + */ + insns.set(i, insn.withRegisterOffset(reservedDifference)); + } + } + + oldReservedCount = newReservedCount; + } + + reservedCount = oldReservedCount; + } + + /** + * Helper for {@link #reserveRegisters}, which does one + * pass over the instructions, calculating the number of + * registers that need to be reserved. It also updates the + * {@code opcodes} list to help avoid extra work in future + * register reservation passes. + * + * @param opcodes {@code non-null;} array of per-instruction + * opcode selections + * @return {@code >= 0;} the count of reserved registers + */ + private int calculateReservedCount(Dop[] opcodes) { + int size = insns.size(); + + /* + * Potential new value of reservedCount, which gets updated in the + * following loop. It starts out with the existing reservedCount + * and gets increased if it turns out that additional registers + * need to be reserved. + */ + int newReservedCount = reservedCount; + + for (int i = 0; i < size; i++) { + DalvInsn insn = insns.get(i); + Dop originalOpcode = opcodes[i]; + Dop newOpcode = findOpcodeForInsn(insn, originalOpcode); + + if (newOpcode == null) { + /* + * The instruction will need to be expanded, so find the + * expanded opcode and reserve registers for it. + */ + Dop expandedOp = findExpandedOpcodeForInsn(insn); + BitSet compatRegs = expandedOp.getFormat().compatibleRegs(insn); + int reserve = insn.getMinimumRegisterRequirement(compatRegs); + if (reserve > newReservedCount) { + newReservedCount = reserve; + } + } else if (originalOpcode == newOpcode) { + continue; + } + + opcodes[i] = newOpcode; + } + + return newReservedCount; + } + + /** + * Attempts to fit the given instruction into a specific opcode, + * returning the opcode whose format that the instruction fits + * into or {@code null} to indicate that the instruction will need + * to be expanded. This fitting process starts with the given + * opcode as a first "best guess" and then pessimizes from there + * if necessary. + * + * @param insn {@code non-null;} the instruction in question + * @param guess {@code null-ok;} the current guess as to the best + * opcode; {@code null} means that no simple opcode fits + * @return {@code null-ok;} a possibly-different opcode; either a + * {@code non-null} good fit or {@code null} to indicate that no + * simple opcode fits + */ + private Dop findOpcodeForInsn(DalvInsn insn, Dop guess) { + /* + * Note: The initial guess might be null, meaning that an + * earlier call to this method already determined that there + * was no possible simple opcode fit. + */ + + while (guess != null) { + if (guess.getFormat().isCompatible(insn)) { + /* + * Don't break out for const_string to generate jumbo version + * when option is enabled. + */ + if (!dexOptions.forceJumbo || + guess.getOpcode() != Opcodes.CONST_STRING) { + break; + } + } + + guess = Dops.getNextOrNull(guess, dexOptions); + } + + return guess; + } + + /** + * Finds the proper opcode for the given instruction, ignoring + * register constraints. + * + * @param insn {@code non-null;} the instruction in question + * @return {@code non-null;} the opcode that fits + */ + private Dop findExpandedOpcodeForInsn(DalvInsn insn) { + Dop result = findOpcodeForInsn(insn.getLowRegVersion(), insn.getOpcode()); + if (result == null) { + throw new DexException("No expanded opcode for " + insn); + } + return result; + } + + /** + * Helper for {@link #finishProcessingAndGetList}, which goes + * through each instruction in the output, making sure its opcode + * can accomodate its arguments. In cases where the opcode is + * unable to do so, this replaces the instruction with a larger + * instruction with identical semantics that will work. + * + *

This method may also reserve a number of low-numbered + * registers, renumbering the instructions' original registers, in + * order to have register space available in which to move + * very-high registers when expanding instructions into + * multi-instruction sequences. This expansion is done when no + * simple instruction format can be found for a given instruction that + * is able to accomodate that instruction's registers.

+ * + *

This method ignores issues of branch target size, since + * final addresses aren't known at the point that this method is + * called.

+ * + * @param opcodes {@code non-null;} array of per-instruction + * opcode selections + */ + private void massageInstructions(Dop[] opcodes) { + if (reservedCount == 0) { + /* + * The easy common case: No registers were reserved, so we + * merely need to replace any instructions whose format + * (and hence whose opcode) changed during the reservation + * pass, but all instructions will stay at their original + * indices, and the instruction list doesn't grow. + */ + int size = insns.size(); + + for (int i = 0; i < size; i++) { + DalvInsn insn = insns.get(i); + Dop originalOpcode = insn.getOpcode(); + Dop currentOpcode = opcodes[i]; + + if (originalOpcode != currentOpcode) { + insns.set(i, insn.withOpcode(currentOpcode)); + } + } + } else { + /* + * The difficult uncommon case: Some instructions have to be + * expanded to deal with high registers. + */ + insns = performExpansion(opcodes); + } + } + + /** + * Helper for {@link #massageInstructions}, which constructs a + * replacement list, where each {link DalvInsn} instance that + * couldn't be represented simply (due to register representation + * problems) is expanded into a series of instances that together + * perform the proper function. + * + * @param opcodes {@code non-null;} array of per-instruction + * opcode selections + * @return {@code non-null;} the replacement list + */ + private ArrayList performExpansion(Dop[] opcodes) { + int size = insns.size(); + ArrayList result = new ArrayList(size * 2); + + ArrayList closelyBoundAddresses = new ArrayList(); + + for (int i = 0; i < size; i++) { + DalvInsn insn = insns.get(i); + Dop originalOpcode = insn.getOpcode(); + Dop currentOpcode = opcodes[i]; + DalvInsn prefix; + DalvInsn suffix; + + if (currentOpcode != null) { + // No expansion is necessary. + prefix = null; + suffix = null; + } else { + // Expansion is required. + currentOpcode = findExpandedOpcodeForInsn(insn); + BitSet compatRegs = + currentOpcode.getFormat().compatibleRegs(insn); + prefix = insn.expandedPrefix(compatRegs); + suffix = insn.expandedSuffix(compatRegs); + + // Expand necessary registers to fit the new format + insn = insn.expandedVersion(compatRegs); + } + + if (insn instanceof CodeAddress) { + // If we have a closely bound address, don't add it yet, + // because we need to add it after the prefix for the + // instruction it is bound to. + if (((CodeAddress) insn).getBindsClosely()) { + closelyBoundAddresses.add((CodeAddress)insn); + continue; + } + } + + if (prefix != null) { + result.add(prefix); + } + + // Add any pending closely bound addresses + if (!(insn instanceof ZeroSizeInsn) && closelyBoundAddresses.size() > 0) { + for (CodeAddress codeAddress: closelyBoundAddresses) { + result.add(codeAddress); + } + closelyBoundAddresses.clear(); + } + + if (currentOpcode != originalOpcode) { + insn = insn.withOpcode(currentOpcode); + } + result.add(insn); + + if (suffix != null) { + result.add(suffix); + } + } + + return result; + } + + /** + * Helper for {@link #finishProcessingAndGetList}, which assigns + * addresses to each instruction, possibly rewriting branches to + * fix ones that wouldn't otherwise be able to reach their + * targets. + */ + private void assignAddressesAndFixBranches() { + for (;;) { + assignAddresses(); + if (!fixBranches()) { + break; + } + } + } + + /** + * Helper for {@link #assignAddressesAndFixBranches}, which + * assigns an address to each instruction, in order. + */ + private void assignAddresses() { + int address = 0; + int size = insns.size(); + + for (int i = 0; i < size; i++) { + DalvInsn insn = insns.get(i); + insn.setAddress(address); + address += insn.codeSize(); + } + } + + /** + * Helper for {@link #assignAddressesAndFixBranches}, which checks + * the branch target size requirement of each branch instruction + * to make sure it fits. For instructions that don't fit, this + * rewrites them to use a {@code goto} of some sort. In the + * case of a conditional branch that doesn't fit, the sense of the + * test is reversed in order to branch around a {@code goto} + * to the original target. + * + * @return whether any branches had to be fixed + */ + private boolean fixBranches() { + int size = insns.size(); + boolean anyFixed = false; + + for (int i = 0; i < size; i++) { + DalvInsn insn = insns.get(i); + if (!(insn instanceof TargetInsn)) { + // This loop only needs to inspect TargetInsns. + continue; + } + + Dop opcode = insn.getOpcode(); + TargetInsn target = (TargetInsn) insn; + + if (opcode.getFormat().branchFits(target)) { + continue; + } + + if (opcode.getFamily() == Opcodes.GOTO) { + // It is a goto; widen it if possible. + opcode = findOpcodeForInsn(insn, opcode); + if (opcode == null) { + /* + * The branch is already maximally large. This should + * only be possible if a method somehow manages to have + * more than 2^31 code units. + */ + throw new UnsupportedOperationException("method too long"); + } + insns.set(i, insn.withOpcode(opcode)); + } else { + /* + * It is a conditional: Reverse its sense, and arrange for + * it to branch around an absolute goto to the original + * branch target. + * + * Note: An invariant of the list being processed is + * that every TargetInsn is followed by a CodeAddress. + * Hence, it is always safe to get the next element + * after a TargetInsn and cast it to CodeAddress, as + * is happening a few lines down. + * + * Also note: Size gets incremented by one here, as we + * have -- in the net -- added one additional element + * to the list, so we increment i to match. The added + * and changed elements will be inspected by a repeat + * call to this method after this invocation returns. + */ + CodeAddress newTarget; + try { + newTarget = (CodeAddress) insns.get(i + 1); + } catch (IndexOutOfBoundsException ex) { + // The TargetInsn / CodeAddress invariant was violated. + throw new IllegalStateException( + "unpaired TargetInsn (dangling)"); + } catch (ClassCastException ex) { + // The TargetInsn / CodeAddress invariant was violated. + throw new IllegalStateException("unpaired TargetInsn"); + } + TargetInsn gotoInsn = + new TargetInsn(Dops.GOTO, target.getPosition(), + RegisterSpecList.EMPTY, target.getTarget()); + insns.set(i, gotoInsn); + insns.add(i, target.withNewTargetAndReversed(newTarget)); + size++; + i++; + } + + anyFixed = true; + } + + return anyFixed; + } +} diff --git a/dx/src/com/android/jack/dx/dex/code/PositionList.java b/dx/src/com/android/jack/dx/dex/code/PositionList.java new file mode 100644 index 00000000..75432d84 --- /dev/null +++ b/dx/src/com/android/jack/dx/dex/code/PositionList.java @@ -0,0 +1,192 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.dex.code; + +import com.android.jack.dx.rop.code.SourcePosition; +import com.android.jack.dx.util.FixedSizeList; + +/** + * List of source position entries. This class includes a utility + * method to extract an instance out of a {@link DalvInsnList}. + */ +public final class PositionList extends FixedSizeList { + /** {@code non-null;} empty instance */ + public static final PositionList EMPTY = new PositionList(0); + + /** + * constant for {@link #make} to indicate that no actual position + * information should be returned + */ + public static final int NONE = 1; + + /** + * constant for {@link #make} to indicate that only line number + * transitions should be returned + */ + public static final int LINES = 2; + + /** + * constant for {@link #make} to indicate that only "important" position + * information should be returned. This includes block starts and + * instructions that might throw. + */ + public static final int IMPORTANT = 3; + + /** + * Extracts and returns the source position information out of an + * instruction list. + * + * @param insns {@code non-null;} instructions to convert + * @param howMuch how much information should be included; one of the + * static constants defined by this class + * @return {@code non-null;} the positions list + */ + public static PositionList make(DalvInsnList insns, int howMuch) { + switch (howMuch) { + case NONE: { + return EMPTY; + } + case LINES: + case IMPORTANT: { + // Valid. + break; + } + default: { + throw new IllegalArgumentException("bogus howMuch"); + } + } + + SourcePosition noInfo = SourcePosition.NO_INFO; + SourcePosition cur = noInfo; + int sz = insns.size(); + PositionList.Entry[] arr = new PositionList.Entry[sz]; + boolean lastWasTarget = false; + int at = 0; + + for (int i = 0; i < sz; i++) { + DalvInsn insn = insns.get(i); + + if (insn instanceof CodeAddress) { + lastWasTarget = true;; + continue; + } + + SourcePosition pos = insn.getPosition(); + + if (pos.equals(noInfo) || pos.sameLine(cur)) { + continue; + } + + if ((howMuch == IMPORTANT) && !lastWasTarget) { + continue; + } + + cur = pos; + arr[at] = new PositionList.Entry(insn.getAddress(), pos); + at++; + + lastWasTarget = false; + } + + PositionList result = new PositionList(at); + for (int i = 0; i < at; i++) { + result.set(i, arr[i]); + } + + result.setImmutable(); + return result; + } + + /** + * Constructs an instance. All indices initially contain {@code null}. + * + * @param size {@code >= 0;} the size of the list + */ + public PositionList(int size) { + super(size); + } + + /** + * Gets the element at the given index. It is an error to call + * this with the index for an element which was never set; if you + * do that, this will throw {@code NullPointerException}. + * + * @param n {@code >= 0, < size();} which index + * @return {@code non-null;} element at that index + */ + public Entry get(int n) { + return (Entry) get0(n); + } + + /** + * Sets the entry at the given index. + * + * @param n {@code >= 0, < size();} which index + * @param entry {@code non-null;} the entry to set at {@code n} + */ + public void set(int n, Entry entry) { + set0(n, entry); + } + + /** + * Entry in a position list. + */ + public static class Entry { + /** {@code >= 0;} address of this entry */ + private final int address; + + /** {@code non-null;} corresponding source position information */ + private final SourcePosition position; + + /** + * Constructs an instance. + * + * @param address {@code >= 0;} address of this entry + * @param position {@code non-null;} corresponding source position information + */ + public Entry (int address, SourcePosition position) { + if (address < 0) { + throw new IllegalArgumentException("address < 0"); + } + + if (position == null) { + throw new NullPointerException("position == null"); + } + + this.address = address; + this.position = position; + } + + /** + * Gets the address. + * + * @return {@code >= 0;} the address + */ + public int getAddress() { + return address; + } + + /** + * Gets the source position information. + * + * @return {@code non-null;} the position information + */ + public SourcePosition getPosition() { + return position; + } + } +} diff --git a/dx/src/com/android/jack/dx/dex/code/RopToDop.java b/dx/src/com/android/jack/dx/dex/code/RopToDop.java new file mode 100644 index 00000000..6f643974 --- /dev/null +++ b/dx/src/com/android/jack/dx/dex/code/RopToDop.java @@ -0,0 +1,587 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.dex.code; + +import com.android.jack.dx.rop.code.Insn; +import com.android.jack.dx.rop.code.RegOps; +import com.android.jack.dx.rop.code.RegisterSpec; +import com.android.jack.dx.rop.code.Rop; +import com.android.jack.dx.rop.code.Rops; +import com.android.jack.dx.rop.code.ThrowingCstInsn; +import com.android.jack.dx.rop.cst.Constant; +import com.android.jack.dx.rop.cst.CstFieldRef; +import com.android.jack.dx.rop.cst.CstString; +import com.android.jack.dx.rop.cst.CstType; +import com.android.jack.dx.rop.type.Type; + +import java.util.HashMap; + +/** + * Translator from rop-level {@link Insn} instances to corresponding + * {@link Dop} instances. + */ +public final class RopToDop { + /** {@code non-null;} map from all the common rops to dalvik opcodes */ + private static final HashMap MAP; + + /** + * This class is uninstantiable. + */ + private RopToDop() { + // This space intentionally left blank. + } + + /* + * The following comment lists each opcode that should be considered + * the "head" of an opcode chain, in terms of the process of fitting + * an instruction's arguments to an actual opcode. This list is + * automatically generated and may be of use in double-checking the + * manually-generated static initialization code for this class. + * + * TODO: Make opcode-gen produce useful code in this case instead + * of just a comment. + */ + + // BEGIN(first-opcodes); GENERATED AUTOMATICALLY BY opcode-gen + // Opcodes.NOP + // Opcodes.MOVE + // Opcodes.MOVE_WIDE + // Opcodes.MOVE_OBJECT + // Opcodes.MOVE_RESULT + // Opcodes.MOVE_RESULT_WIDE + // Opcodes.MOVE_RESULT_OBJECT + // Opcodes.MOVE_EXCEPTION + // Opcodes.RETURN_VOID + // Opcodes.RETURN + // Opcodes.RETURN_WIDE + // Opcodes.RETURN_OBJECT + // Opcodes.CONST_4 + // Opcodes.CONST_WIDE_16 + // Opcodes.CONST_STRING + // Opcodes.CONST_CLASS + // Opcodes.MONITOR_ENTER + // Opcodes.MONITOR_EXIT + // Opcodes.CHECK_CAST + // Opcodes.INSTANCE_OF + // Opcodes.ARRAY_LENGTH + // Opcodes.NEW_INSTANCE + // Opcodes.NEW_ARRAY + // Opcodes.FILLED_NEW_ARRAY + // Opcodes.FILL_ARRAY_DATA + // Opcodes.THROW + // Opcodes.GOTO + // Opcodes.PACKED_SWITCH + // Opcodes.SPARSE_SWITCH + // Opcodes.CMPL_FLOAT + // Opcodes.CMPG_FLOAT + // Opcodes.CMPL_DOUBLE + // Opcodes.CMPG_DOUBLE + // Opcodes.CMP_LONG + // Opcodes.IF_EQ + // Opcodes.IF_NE + // Opcodes.IF_LT + // Opcodes.IF_GE + // Opcodes.IF_GT + // Opcodes.IF_LE + // Opcodes.IF_EQZ + // Opcodes.IF_NEZ + // Opcodes.IF_LTZ + // Opcodes.IF_GEZ + // Opcodes.IF_GTZ + // Opcodes.IF_LEZ + // Opcodes.AGET + // Opcodes.AGET_WIDE + // Opcodes.AGET_OBJECT + // Opcodes.AGET_BOOLEAN + // Opcodes.AGET_BYTE + // Opcodes.AGET_CHAR + // Opcodes.AGET_SHORT + // Opcodes.APUT + // Opcodes.APUT_WIDE + // Opcodes.APUT_OBJECT + // Opcodes.APUT_BOOLEAN + // Opcodes.APUT_BYTE + // Opcodes.APUT_CHAR + // Opcodes.APUT_SHORT + // Opcodes.IGET + // Opcodes.IGET_WIDE + // Opcodes.IGET_OBJECT + // Opcodes.IGET_BOOLEAN + // Opcodes.IGET_BYTE + // Opcodes.IGET_CHAR + // Opcodes.IGET_SHORT + // Opcodes.IPUT + // Opcodes.IPUT_WIDE + // Opcodes.IPUT_OBJECT + // Opcodes.IPUT_BOOLEAN + // Opcodes.IPUT_BYTE + // Opcodes.IPUT_CHAR + // Opcodes.IPUT_SHORT + // Opcodes.SGET + // Opcodes.SGET_WIDE + // Opcodes.SGET_OBJECT + // Opcodes.SGET_BOOLEAN + // Opcodes.SGET_BYTE + // Opcodes.SGET_CHAR + // Opcodes.SGET_SHORT + // Opcodes.SPUT + // Opcodes.SPUT_WIDE + // Opcodes.SPUT_OBJECT + // Opcodes.SPUT_BOOLEAN + // Opcodes.SPUT_BYTE + // Opcodes.SPUT_CHAR + // Opcodes.SPUT_SHORT + // Opcodes.INVOKE_VIRTUAL + // Opcodes.INVOKE_SUPER + // Opcodes.INVOKE_DIRECT + // Opcodes.INVOKE_STATIC + // Opcodes.INVOKE_INTERFACE + // Opcodes.NEG_INT + // Opcodes.NOT_INT + // Opcodes.NEG_LONG + // Opcodes.NOT_LONG + // Opcodes.NEG_FLOAT + // Opcodes.NEG_DOUBLE + // Opcodes.INT_TO_LONG + // Opcodes.INT_TO_FLOAT + // Opcodes.INT_TO_DOUBLE + // Opcodes.LONG_TO_INT + // Opcodes.LONG_TO_FLOAT + // Opcodes.LONG_TO_DOUBLE + // Opcodes.FLOAT_TO_INT + // Opcodes.FLOAT_TO_LONG + // Opcodes.FLOAT_TO_DOUBLE + // Opcodes.DOUBLE_TO_INT + // Opcodes.DOUBLE_TO_LONG + // Opcodes.DOUBLE_TO_FLOAT + // Opcodes.INT_TO_BYTE + // Opcodes.INT_TO_CHAR + // Opcodes.INT_TO_SHORT + // Opcodes.ADD_INT_2ADDR + // Opcodes.SUB_INT_2ADDR + // Opcodes.MUL_INT_2ADDR + // Opcodes.DIV_INT_2ADDR + // Opcodes.REM_INT_2ADDR + // Opcodes.AND_INT_2ADDR + // Opcodes.OR_INT_2ADDR + // Opcodes.XOR_INT_2ADDR + // Opcodes.SHL_INT_2ADDR + // Opcodes.SHR_INT_2ADDR + // Opcodes.USHR_INT_2ADDR + // Opcodes.ADD_LONG_2ADDR + // Opcodes.SUB_LONG_2ADDR + // Opcodes.MUL_LONG_2ADDR + // Opcodes.DIV_LONG_2ADDR + // Opcodes.REM_LONG_2ADDR + // Opcodes.AND_LONG_2ADDR + // Opcodes.OR_LONG_2ADDR + // Opcodes.XOR_LONG_2ADDR + // Opcodes.SHL_LONG_2ADDR + // Opcodes.SHR_LONG_2ADDR + // Opcodes.USHR_LONG_2ADDR + // Opcodes.ADD_FLOAT_2ADDR + // Opcodes.SUB_FLOAT_2ADDR + // Opcodes.MUL_FLOAT_2ADDR + // Opcodes.DIV_FLOAT_2ADDR + // Opcodes.REM_FLOAT_2ADDR + // Opcodes.ADD_DOUBLE_2ADDR + // Opcodes.SUB_DOUBLE_2ADDR + // Opcodes.MUL_DOUBLE_2ADDR + // Opcodes.DIV_DOUBLE_2ADDR + // Opcodes.REM_DOUBLE_2ADDR + // Opcodes.ADD_INT_LIT8 + // Opcodes.RSUB_INT_LIT8 + // Opcodes.MUL_INT_LIT8 + // Opcodes.DIV_INT_LIT8 + // Opcodes.REM_INT_LIT8 + // Opcodes.AND_INT_LIT8 + // Opcodes.OR_INT_LIT8 + // Opcodes.XOR_INT_LIT8 + // Opcodes.SHL_INT_LIT8 + // Opcodes.SHR_INT_LIT8 + // Opcodes.USHR_INT_LIT8 + // END(first-opcodes) + + static { + /* + * Note: The choices made here are to pick the optimistically + * smallest Dalvik opcode, and leave it to later processing to + * pessimize. See the automatically-generated comment above + * for reference. + */ + MAP = new HashMap(400); + MAP.put(Rops.NOP, Dops.NOP); + MAP.put(Rops.MOVE_INT, Dops.MOVE); + MAP.put(Rops.MOVE_LONG, Dops.MOVE_WIDE); + MAP.put(Rops.MOVE_FLOAT, Dops.MOVE); + MAP.put(Rops.MOVE_DOUBLE, Dops.MOVE_WIDE); + MAP.put(Rops.MOVE_OBJECT, Dops.MOVE_OBJECT); + MAP.put(Rops.MOVE_PARAM_INT, Dops.MOVE); + MAP.put(Rops.MOVE_PARAM_LONG, Dops.MOVE_WIDE); + MAP.put(Rops.MOVE_PARAM_FLOAT, Dops.MOVE); + MAP.put(Rops.MOVE_PARAM_DOUBLE, Dops.MOVE_WIDE); + MAP.put(Rops.MOVE_PARAM_OBJECT, Dops.MOVE_OBJECT); + + /* + * Note: No entry for MOVE_EXCEPTION, since it varies by + * exception type. (That is, there is no unique instance to + * add to the map.) + */ + + MAP.put(Rops.CONST_INT, Dops.CONST_4); + MAP.put(Rops.CONST_LONG, Dops.CONST_WIDE_16); + MAP.put(Rops.CONST_FLOAT, Dops.CONST_4); + MAP.put(Rops.CONST_DOUBLE, Dops.CONST_WIDE_16); + + /* + * Note: No entry for CONST_OBJECT, since it needs to turn + * into either CONST_STRING or CONST_CLASS. + */ + + /* + * TODO: I think the only case of this is for null, and + * const/4 should cover that. + */ + MAP.put(Rops.CONST_OBJECT_NOTHROW, Dops.CONST_4); + + MAP.put(Rops.GOTO, Dops.GOTO); + MAP.put(Rops.IF_EQZ_INT, Dops.IF_EQZ); + MAP.put(Rops.IF_NEZ_INT, Dops.IF_NEZ); + MAP.put(Rops.IF_LTZ_INT, Dops.IF_LTZ); + MAP.put(Rops.IF_GEZ_INT, Dops.IF_GEZ); + MAP.put(Rops.IF_LEZ_INT, Dops.IF_LEZ); + MAP.put(Rops.IF_GTZ_INT, Dops.IF_GTZ); + MAP.put(Rops.IF_EQZ_OBJECT, Dops.IF_EQZ); + MAP.put(Rops.IF_NEZ_OBJECT, Dops.IF_NEZ); + MAP.put(Rops.IF_EQ_INT, Dops.IF_EQ); + MAP.put(Rops.IF_NE_INT, Dops.IF_NE); + MAP.put(Rops.IF_LT_INT, Dops.IF_LT); + MAP.put(Rops.IF_GE_INT, Dops.IF_GE); + MAP.put(Rops.IF_LE_INT, Dops.IF_LE); + MAP.put(Rops.IF_GT_INT, Dops.IF_GT); + MAP.put(Rops.IF_EQ_OBJECT, Dops.IF_EQ); + MAP.put(Rops.IF_NE_OBJECT, Dops.IF_NE); + MAP.put(Rops.SWITCH, Dops.SPARSE_SWITCH); + MAP.put(Rops.ADD_INT, Dops.ADD_INT_2ADDR); + MAP.put(Rops.ADD_LONG, Dops.ADD_LONG_2ADDR); + MAP.put(Rops.ADD_FLOAT, Dops.ADD_FLOAT_2ADDR); + MAP.put(Rops.ADD_DOUBLE, Dops.ADD_DOUBLE_2ADDR); + MAP.put(Rops.SUB_INT, Dops.SUB_INT_2ADDR); + MAP.put(Rops.SUB_LONG, Dops.SUB_LONG_2ADDR); + MAP.put(Rops.SUB_FLOAT, Dops.SUB_FLOAT_2ADDR); + MAP.put(Rops.SUB_DOUBLE, Dops.SUB_DOUBLE_2ADDR); + MAP.put(Rops.MUL_INT, Dops.MUL_INT_2ADDR); + MAP.put(Rops.MUL_LONG, Dops.MUL_LONG_2ADDR); + MAP.put(Rops.MUL_FLOAT, Dops.MUL_FLOAT_2ADDR); + MAP.put(Rops.MUL_DOUBLE, Dops.MUL_DOUBLE_2ADDR); + MAP.put(Rops.DIV_INT, Dops.DIV_INT_2ADDR); + MAP.put(Rops.DIV_LONG, Dops.DIV_LONG_2ADDR); + MAP.put(Rops.DIV_FLOAT, Dops.DIV_FLOAT_2ADDR); + MAP.put(Rops.DIV_DOUBLE, Dops.DIV_DOUBLE_2ADDR); + MAP.put(Rops.REM_INT, Dops.REM_INT_2ADDR); + MAP.put(Rops.REM_LONG, Dops.REM_LONG_2ADDR); + MAP.put(Rops.REM_FLOAT, Dops.REM_FLOAT_2ADDR); + MAP.put(Rops.REM_DOUBLE, Dops.REM_DOUBLE_2ADDR); + MAP.put(Rops.NEG_INT, Dops.NEG_INT); + MAP.put(Rops.NEG_LONG, Dops.NEG_LONG); + MAP.put(Rops.NEG_FLOAT, Dops.NEG_FLOAT); + MAP.put(Rops.NEG_DOUBLE, Dops.NEG_DOUBLE); + MAP.put(Rops.AND_INT, Dops.AND_INT_2ADDR); + MAP.put(Rops.AND_LONG, Dops.AND_LONG_2ADDR); + MAP.put(Rops.OR_INT, Dops.OR_INT_2ADDR); + MAP.put(Rops.OR_LONG, Dops.OR_LONG_2ADDR); + MAP.put(Rops.XOR_INT, Dops.XOR_INT_2ADDR); + MAP.put(Rops.XOR_LONG, Dops.XOR_LONG_2ADDR); + MAP.put(Rops.SHL_INT, Dops.SHL_INT_2ADDR); + MAP.put(Rops.SHL_LONG, Dops.SHL_LONG_2ADDR); + MAP.put(Rops.SHR_INT, Dops.SHR_INT_2ADDR); + MAP.put(Rops.SHR_LONG, Dops.SHR_LONG_2ADDR); + MAP.put(Rops.USHR_INT, Dops.USHR_INT_2ADDR); + MAP.put(Rops.USHR_LONG, Dops.USHR_LONG_2ADDR); + MAP.put(Rops.NOT_INT, Dops.NOT_INT); + MAP.put(Rops.NOT_LONG, Dops.NOT_LONG); + + MAP.put(Rops.ADD_CONST_INT, Dops.ADD_INT_LIT8); + // Note: No dalvik ops for other types of add_const. + + MAP.put(Rops.SUB_CONST_INT, Dops.RSUB_INT_LIT8); + /* + * Note: No dalvik ops for any type of sub_const; instead + * there's a *reverse* sub (constant - reg) for ints only. + */ + + MAP.put(Rops.MUL_CONST_INT, Dops.MUL_INT_LIT8); + // Note: No dalvik ops for other types of mul_const. + + MAP.put(Rops.DIV_CONST_INT, Dops.DIV_INT_LIT8); + // Note: No dalvik ops for other types of div_const. + + MAP.put(Rops.REM_CONST_INT, Dops.REM_INT_LIT8); + // Note: No dalvik ops for other types of rem_const. + + MAP.put(Rops.AND_CONST_INT, Dops.AND_INT_LIT8); + // Note: No dalvik op for and_const_long. + + MAP.put(Rops.OR_CONST_INT, Dops.OR_INT_LIT8); + // Note: No dalvik op for or_const_long. + + MAP.put(Rops.XOR_CONST_INT, Dops.XOR_INT_LIT8); + // Note: No dalvik op for xor_const_long. + + MAP.put(Rops.SHL_CONST_INT, Dops.SHL_INT_LIT8); + // Note: No dalvik op for shl_const_long. + + MAP.put(Rops.SHR_CONST_INT, Dops.SHR_INT_LIT8); + // Note: No dalvik op for shr_const_long. + + MAP.put(Rops.USHR_CONST_INT, Dops.USHR_INT_LIT8); + // Note: No dalvik op for shr_const_long. + + MAP.put(Rops.CMPL_LONG, Dops.CMP_LONG); + MAP.put(Rops.CMPL_FLOAT, Dops.CMPL_FLOAT); + MAP.put(Rops.CMPL_DOUBLE, Dops.CMPL_DOUBLE); + MAP.put(Rops.CMPG_FLOAT, Dops.CMPG_FLOAT); + MAP.put(Rops.CMPG_DOUBLE, Dops.CMPG_DOUBLE); + MAP.put(Rops.CONV_L2I, Dops.LONG_TO_INT); + MAP.put(Rops.CONV_F2I, Dops.FLOAT_TO_INT); + MAP.put(Rops.CONV_D2I, Dops.DOUBLE_TO_INT); + MAP.put(Rops.CONV_I2L, Dops.INT_TO_LONG); + MAP.put(Rops.CONV_F2L, Dops.FLOAT_TO_LONG); + MAP.put(Rops.CONV_D2L, Dops.DOUBLE_TO_LONG); + MAP.put(Rops.CONV_I2F, Dops.INT_TO_FLOAT); + MAP.put(Rops.CONV_L2F, Dops.LONG_TO_FLOAT); + MAP.put(Rops.CONV_D2F, Dops.DOUBLE_TO_FLOAT); + MAP.put(Rops.CONV_I2D, Dops.INT_TO_DOUBLE); + MAP.put(Rops.CONV_L2D, Dops.LONG_TO_DOUBLE); + MAP.put(Rops.CONV_F2D, Dops.FLOAT_TO_DOUBLE); + MAP.put(Rops.TO_BYTE, Dops.INT_TO_BYTE); + MAP.put(Rops.TO_CHAR, Dops.INT_TO_CHAR); + MAP.put(Rops.TO_SHORT, Dops.INT_TO_SHORT); + MAP.put(Rops.RETURN_VOID, Dops.RETURN_VOID); + MAP.put(Rops.RETURN_INT, Dops.RETURN); + MAP.put(Rops.RETURN_LONG, Dops.RETURN_WIDE); + MAP.put(Rops.RETURN_FLOAT, Dops.RETURN); + MAP.put(Rops.RETURN_DOUBLE, Dops.RETURN_WIDE); + MAP.put(Rops.RETURN_OBJECT, Dops.RETURN_OBJECT); + MAP.put(Rops.ARRAY_LENGTH, Dops.ARRAY_LENGTH); + MAP.put(Rops.THROW, Dops.THROW); + MAP.put(Rops.MONITOR_ENTER, Dops.MONITOR_ENTER); + MAP.put(Rops.MONITOR_EXIT, Dops.MONITOR_EXIT); + MAP.put(Rops.AGET_INT, Dops.AGET); + MAP.put(Rops.AGET_LONG, Dops.AGET_WIDE); + MAP.put(Rops.AGET_FLOAT, Dops.AGET); + MAP.put(Rops.AGET_DOUBLE, Dops.AGET_WIDE); + MAP.put(Rops.AGET_OBJECT, Dops.AGET_OBJECT); + MAP.put(Rops.AGET_BOOLEAN, Dops.AGET_BOOLEAN); + MAP.put(Rops.AGET_BYTE, Dops.AGET_BYTE); + MAP.put(Rops.AGET_CHAR, Dops.AGET_CHAR); + MAP.put(Rops.AGET_SHORT, Dops.AGET_SHORT); + MAP.put(Rops.APUT_INT, Dops.APUT); + MAP.put(Rops.APUT_LONG, Dops.APUT_WIDE); + MAP.put(Rops.APUT_FLOAT, Dops.APUT); + MAP.put(Rops.APUT_DOUBLE, Dops.APUT_WIDE); + MAP.put(Rops.APUT_OBJECT, Dops.APUT_OBJECT); + MAP.put(Rops.APUT_BOOLEAN, Dops.APUT_BOOLEAN); + MAP.put(Rops.APUT_BYTE, Dops.APUT_BYTE); + MAP.put(Rops.APUT_CHAR, Dops.APUT_CHAR); + MAP.put(Rops.APUT_SHORT, Dops.APUT_SHORT); + MAP.put(Rops.NEW_INSTANCE, Dops.NEW_INSTANCE); + MAP.put(Rops.CHECK_CAST, Dops.CHECK_CAST); + MAP.put(Rops.INSTANCE_OF, Dops.INSTANCE_OF); + + MAP.put(Rops.GET_FIELD_LONG, Dops.IGET_WIDE); + MAP.put(Rops.GET_FIELD_FLOAT, Dops.IGET); + MAP.put(Rops.GET_FIELD_DOUBLE, Dops.IGET_WIDE); + MAP.put(Rops.GET_FIELD_OBJECT, Dops.IGET_OBJECT); + /* + * Note: No map entries for get_field_* for non-long integral types, + * since they need to be handled specially (see dopFor() below). + */ + + MAP.put(Rops.GET_STATIC_LONG, Dops.SGET_WIDE); + MAP.put(Rops.GET_STATIC_FLOAT, Dops.SGET); + MAP.put(Rops.GET_STATIC_DOUBLE, Dops.SGET_WIDE); + MAP.put(Rops.GET_STATIC_OBJECT, Dops.SGET_OBJECT); + /* + * Note: No map entries for get_static* for non-long integral types, + * since they need to be handled specially (see dopFor() below). + */ + + MAP.put(Rops.PUT_FIELD_LONG, Dops.IPUT_WIDE); + MAP.put(Rops.PUT_FIELD_FLOAT, Dops.IPUT); + MAP.put(Rops.PUT_FIELD_DOUBLE, Dops.IPUT_WIDE); + MAP.put(Rops.PUT_FIELD_OBJECT, Dops.IPUT_OBJECT); + /* + * Note: No map entries for put_field_* for non-long integral types, + * since they need to be handled specially (see dopFor() below). + */ + + MAP.put(Rops.PUT_STATIC_LONG, Dops.SPUT_WIDE); + MAP.put(Rops.PUT_STATIC_FLOAT, Dops.SPUT); + MAP.put(Rops.PUT_STATIC_DOUBLE, Dops.SPUT_WIDE); + MAP.put(Rops.PUT_STATIC_OBJECT, Dops.SPUT_OBJECT); + /* + * Note: No map entries for put_static* for non-long integral types, + * since they need to be handled specially (see dopFor() below). + */ + + /* + * Note: No map entries for invoke*, new_array, and + * filled_new_array, since they need to be handled specially + * (see dopFor() below). + */ + } + + /** + * Returns the dalvik opcode appropriate for the given register-based + * instruction. + * + * @param insn {@code non-null;} the original instruction + * @return the corresponding dalvik opcode; one of the constants in + * {@link Dops} + */ + public static Dop dopFor(Insn insn) { + Rop rop = insn.getOpcode(); + + /* + * First, just try looking up the rop in the MAP of easy + * cases. + */ + Dop result = MAP.get(rop); + if (result != null) { + return result; + } + + /* + * There was no easy case for the rop, so look up the opcode, and + * do something special for each: + * + * The move_exception, new_array, filled_new_array, and + * invoke* opcodes won't be found in MAP, since they'll each + * have different source and/or result register types / lists. + * + * The get* and put* opcodes for (non-long) integral types + * aren't in the map, since the type signatures aren't + * sufficient to distinguish between the types (the salient + * source or result will always be just "int"). + * + * And const instruction need to distinguish between strings and + * classes. + */ + + switch (rop.getOpcode()) { + case RegOps.MOVE_EXCEPTION: return Dops.MOVE_EXCEPTION; + case RegOps.INVOKE_STATIC: return Dops.INVOKE_STATIC; + case RegOps.INVOKE_VIRTUAL: return Dops.INVOKE_VIRTUAL; + case RegOps.INVOKE_SUPER: return Dops.INVOKE_SUPER; + case RegOps.INVOKE_DIRECT: return Dops.INVOKE_DIRECT; + case RegOps.INVOKE_INTERFACE: return Dops.INVOKE_INTERFACE; + case RegOps.NEW_ARRAY: return Dops.NEW_ARRAY; + case RegOps.FILLED_NEW_ARRAY: return Dops.FILLED_NEW_ARRAY; + case RegOps.FILL_ARRAY_DATA: return Dops.FILL_ARRAY_DATA; + case RegOps.MOVE_RESULT: { + RegisterSpec resultReg = insn.getResult(); + + if (resultReg == null) { + return Dops.NOP; + } else { + switch (resultReg.getBasicType()) { + case Type.BT_INT: + case Type.BT_FLOAT: + case Type.BT_BOOLEAN: + case Type.BT_BYTE: + case Type.BT_CHAR: + case Type.BT_SHORT: + return Dops.MOVE_RESULT; + case Type.BT_LONG: + case Type.BT_DOUBLE: + return Dops.MOVE_RESULT_WIDE; + case Type.BT_OBJECT: + return Dops.MOVE_RESULT_OBJECT; + default: { + throw new RuntimeException("Unexpected basic type"); + } + } + } + } + + case RegOps.GET_FIELD: { + CstFieldRef ref = + (CstFieldRef) ((ThrowingCstInsn) insn).getConstant(); + int basicType = ref.getBasicType(); + switch (basicType) { + case Type.BT_BOOLEAN: return Dops.IGET_BOOLEAN; + case Type.BT_BYTE: return Dops.IGET_BYTE; + case Type.BT_CHAR: return Dops.IGET_CHAR; + case Type.BT_SHORT: return Dops.IGET_SHORT; + case Type.BT_INT: return Dops.IGET; + } + break; + } + case RegOps.PUT_FIELD: { + CstFieldRef ref = + (CstFieldRef) ((ThrowingCstInsn) insn).getConstant(); + int basicType = ref.getBasicType(); + switch (basicType) { + case Type.BT_BOOLEAN: return Dops.IPUT_BOOLEAN; + case Type.BT_BYTE: return Dops.IPUT_BYTE; + case Type.BT_CHAR: return Dops.IPUT_CHAR; + case Type.BT_SHORT: return Dops.IPUT_SHORT; + case Type.BT_INT: return Dops.IPUT; + } + break; + } + case RegOps.GET_STATIC: { + CstFieldRef ref = + (CstFieldRef) ((ThrowingCstInsn) insn).getConstant(); + int basicType = ref.getBasicType(); + switch (basicType) { + case Type.BT_BOOLEAN: return Dops.SGET_BOOLEAN; + case Type.BT_BYTE: return Dops.SGET_BYTE; + case Type.BT_CHAR: return Dops.SGET_CHAR; + case Type.BT_SHORT: return Dops.SGET_SHORT; + case Type.BT_INT: return Dops.SGET; + } + break; + } + case RegOps.PUT_STATIC: { + CstFieldRef ref = + (CstFieldRef) ((ThrowingCstInsn) insn).getConstant(); + int basicType = ref.getBasicType(); + switch (basicType) { + case Type.BT_BOOLEAN: return Dops.SPUT_BOOLEAN; + case Type.BT_BYTE: return Dops.SPUT_BYTE; + case Type.BT_CHAR: return Dops.SPUT_CHAR; + case Type.BT_SHORT: return Dops.SPUT_SHORT; + case Type.BT_INT: return Dops.SPUT; + } + break; + } + case RegOps.CONST: { + Constant cst = ((ThrowingCstInsn) insn).getConstant(); + if (cst instanceof CstType) { + return Dops.CONST_CLASS; + } else if (cst instanceof CstString) { + return Dops.CONST_STRING; + } + break; + } + } + + throw new RuntimeException("unknown rop: " + rop); + } +} diff --git a/dx/src/com/android/jack/dx/dex/code/RopTranslator.java b/dx/src/com/android/jack/dx/dex/code/RopTranslator.java new file mode 100644 index 00000000..29b74003 --- /dev/null +++ b/dx/src/com/android/jack/dx/dex/code/RopTranslator.java @@ -0,0 +1,882 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.dex.code; + +import com.android.jack.dx.dex.DexOptions; +import com.android.jack.dx.io.Opcodes; +import com.android.jack.dx.rop.code.BasicBlock; +import com.android.jack.dx.rop.code.BasicBlockList; +import com.android.jack.dx.rop.code.FillArrayDataInsn; +import com.android.jack.dx.rop.code.Insn; +import com.android.jack.dx.rop.code.LocalVariableInfo; +import com.android.jack.dx.rop.code.PlainCstInsn; +import com.android.jack.dx.rop.code.PlainInsn; +import com.android.jack.dx.rop.code.RegOps; +import com.android.jack.dx.rop.code.RegisterSpec; +import com.android.jack.dx.rop.code.RegisterSpecList; +import com.android.jack.dx.rop.code.RegisterSpecSet; +import com.android.jack.dx.rop.code.Rop; +import com.android.jack.dx.rop.code.RopMethod; +import com.android.jack.dx.rop.code.SourcePosition; +import com.android.jack.dx.rop.code.SwitchInsn; +import com.android.jack.dx.rop.code.ThrowingCstInsn; +import com.android.jack.dx.rop.code.ThrowingInsn; +import com.android.jack.dx.rop.cst.Constant; +import com.android.jack.dx.rop.cst.CstInteger; +import com.android.jack.dx.util.Bits; +import com.android.jack.dx.util.IntList; + +import java.util.ArrayList; + +/** + * Translator from {@link RopMethod} to {@link DalvCode}. The {@link + * #translate} method is the thing to call on this class. + */ +public final class RopTranslator { + /** {@code non-null;} options for dex output */ + private final DexOptions dexOptions; + + /** {@code non-null;} method to translate */ + private final RopMethod method; + + /** + * how much position info to preserve; one of the static + * constants in {@link PositionList} + */ + private final int positionInfo; + + /** {@code null-ok;} local variable info to use */ + private final LocalVariableInfo locals; + + /** {@code non-null;} container for all the address objects for the method */ + private final BlockAddresses addresses; + + /** {@code non-null;} list of output instructions in-progress */ + private final OutputCollector output; + + /** {@code non-null;} visitor to use during translation */ + private final TranslationVisitor translationVisitor; + + /** {@code >= 0;} register count for the method */ + private final int regCount; + + /** {@code null-ok;} block output order; becomes non-null in {@link #pickOrder} */ + private int[] order; + + /** size, in register units, of all the parameters to this method */ + private final int paramSize; + + /** + * true if the parameters to this method happen to be in proper order + * at the end of the frame (as the optimizer emits them) + */ + private boolean paramsAreInOrder; + + /** + * Translates a {@link RopMethod}. This may modify the given + * input. + * + * @param method {@code non-null;} the original method + * @param positionInfo how much position info to preserve; one of the + * static constants in {@link PositionList} + * @param locals {@code null-ok;} local variable information to use + * @param paramSize size, in register units, of all the parameters to + * this method + * @param dexOptions {@code non-null;} options for dex output + * @return {@code non-null;} the translated version + */ + public static DalvCode translate(RopMethod method, int positionInfo, + LocalVariableInfo locals, int paramSize, DexOptions dexOptions) { + RopTranslator translator = + new RopTranslator(method, positionInfo, locals, paramSize, dexOptions); + return translator.translateAndGetResult(); + } + + /** + * Constructs an instance. This method is private. Use {@link #translate}. + * + * @param method {@code non-null;} the original method + * @param positionInfo how much position info to preserve; one of the + * static constants in {@link PositionList} + * @param locals {@code null-ok;} local variable information to use + * @param paramSize size, in register units, of all the parameters to + * this method + * @param dexOptions {@code non-null;} options for dex output + */ + private RopTranslator(RopMethod method, int positionInfo, LocalVariableInfo locals, + int paramSize, DexOptions dexOptions) { + this.dexOptions = dexOptions; + this.method = method; + this.positionInfo = positionInfo; + this.locals = locals; + this.addresses = new BlockAddresses(method); + this.paramSize = paramSize; + this.order = null; + this.paramsAreInOrder = calculateParamsAreInOrder(method, paramSize); + + BasicBlockList blocks = method.getBlocks(); + int bsz = blocks.size(); + + /* + * Max possible instructions includes three code address + * objects per basic block (to the first and last instruction, + * and just past the end of the block), and the possibility of + * an extra goto at the end of each basic block. + */ + int maxInsns = (bsz * 3) + blocks.getInstructionCount(); + + if (locals != null) { + /* + * If we're tracking locals, then there's could be another + * extra instruction per block (for the locals state at the + * start of the block) as well as one for each interblock + * local introduction. + */ + maxInsns += bsz + locals.getAssignmentCount(); + } + + /* + * If params are not in order, we will need register space + * for them before this is all over... + */ + this.regCount = blocks.getRegCount() + + (paramsAreInOrder ? 0 : this.paramSize); + + this.output = new OutputCollector(dexOptions, maxInsns, bsz * 3, regCount); + + if (locals != null) { + this.translationVisitor = + new LocalVariableAwareTranslationVisitor(output, locals); + } else { + this.translationVisitor = new TranslationVisitor(output); + } + } + + /** + * Checks to see if the move-param instructions that occur in this + * method happen to slot the params in an order at the top of the + * stack frame that matches dalvik's calling conventions. This will + * alway result in "true" for methods that have run through the + * SSA optimizer. + * + * @param paramSize size, in register units, of all the parameters + * to this method + */ + private static boolean calculateParamsAreInOrder(RopMethod method, + final int paramSize) { + final boolean[] paramsAreInOrder = { true }; + final int initialRegCount = method.getBlocks().getRegCount(); + + /* + * We almost could just check the first block here, but the + * {@code cf} layer will put in a second move-param in a + * subsequent block in the case of synchronized methods. + */ + method.getBlocks().forEachInsn(new Insn.BaseVisitor() { + @Override + public void visitPlainCstInsn(PlainCstInsn insn) { + if (insn.getOpcode().getOpcode()== RegOps.MOVE_PARAM) { + int param = + ((CstInteger) insn.getConstant()).getValue(); + + paramsAreInOrder[0] = paramsAreInOrder[0] + && ((initialRegCount - paramSize + param) + == insn.getResult().getReg()); + } + } + }); + + return paramsAreInOrder[0]; + } + + /** + * Does the translation and returns the result. + * + * @return {@code non-null;} the result + */ + private DalvCode translateAndGetResult() { + pickOrder(); + outputInstructions(); + + StdCatchBuilder catches = + new StdCatchBuilder(method, order, addresses); + + return new DalvCode(positionInfo, output.getFinisher(), catches); + } + + /** + * Performs initial creation of output instructions based on the + * original blocks. + */ + private void outputInstructions() { + BasicBlockList blocks = method.getBlocks(); + int[] order = this.order; + int len = order.length; + + // Process the blocks in output order. + for (int i = 0; i < len; i++) { + int nextI = i + 1; + int nextLabel = (nextI == order.length) ? -1 : order[nextI]; + outputBlock(blocks.labelToBlock(order[i]), nextLabel); + } + } + + /** + * Helper for {@link #outputInstructions}, which does the processing + * and output of one block. + * + * @param block {@code non-null;} the block to process and output + * @param nextLabel {@code >= -1;} the next block that will be processed, or + * {@code -1} if there is no next block + */ + private void outputBlock(BasicBlock block, int nextLabel) { + // Append the code address for this block. + CodeAddress startAddress = addresses.getStart(block); + output.add(startAddress); + + // Append the local variable state for the block. + if (locals != null) { + RegisterSpecSet starts = locals.getStarts(block); + output.add(new LocalSnapshot(startAddress.getPosition(), + starts)); + } + + /* + * Choose and append an output instruction for each original + * instruction. + */ + translationVisitor.setBlock(block, addresses.getLast(block)); + block.getInsns().forEach(translationVisitor); + + // Insert the block end code address. + output.add(addresses.getEnd(block)); + + // Set up for end-of-block activities. + + int succ = block.getPrimarySuccessor(); + Insn lastInsn = block.getLastInsn(); + + /* + * Check for (and possibly correct for) a non-optimal choice of + * which block will get output next. + */ + + if ((succ >= 0) && (succ != nextLabel)) { + /* + * The block has a "primary successor" and that primary + * successor isn't the next block to be output. + */ + Rop lastRop = lastInsn.getOpcode(); + if ((lastRop.getBranchingness() == Rop.BRANCH_IF) && + (block.getSecondarySuccessor() == nextLabel)) { + /* + * The block ends with an "if" of some sort, and its + * secondary successor (the "then") is in fact the + * next block to output. So, reverse the sense of + * the test, so that we can just emit the next block + * without an interstitial goto. + */ + output.reverseBranch(1, addresses.getStart(succ)); + } else { + /* + * Our only recourse is to add a goto here to get the + * flow to be correct. + */ + TargetInsn insn = + new TargetInsn(Dops.GOTO, lastInsn.getPosition(), + RegisterSpecList.EMPTY, + addresses.getStart(succ)); + output.add(insn); + } + } + } + + /** + * Picks an order for the blocks by doing "trace" analysis. + */ + private void pickOrder() { + BasicBlockList blocks = method.getBlocks(); + int sz = blocks.size(); + int maxLabel = blocks.getMaxLabel(); + int[] workSet = Bits.makeBitSet(maxLabel); + int[] tracebackSet = Bits.makeBitSet(maxLabel); + + for (int i = 0; i < sz; i++) { + BasicBlock one = blocks.get(i); + Bits.set(workSet, one.getLabel()); + } + + int[] order = new int[sz]; + int at = 0; + + /* + * Starting with the designated "first label" (that is, the + * first block of the method), add that label to the order, + * and then pick its first as-yet unordered successor to + * immediately follow it, giving top priority to the primary + * (aka default) successor (if any). Keep following successors + * until the trace runs out of possibilities. Then, continue + * by finding an unordered chain containing the first as-yet + * unordered block, and adding it to the order, and so on. + */ + for (int label = method.getFirstLabel(); + label != -1; + label = Bits.findFirst(workSet, 0)) { + + /* + * Attempt to trace backward from the chosen block to an + * as-yet unordered predecessor which lists the chosen + * block as its primary successor, and so on, until we + * fail to find such an unordered predecessor. Start the + * trace with that block. Note that the first block in the + * method has no predecessors, so in that case this loop + * will simply terminate with zero iterations and without + * picking a new starter block. + */ + traceBack: + for (;;) { + IntList preds = method.labelToPredecessors(label); + int psz = preds.size(); + + for (int i = 0; i < psz; i++) { + int predLabel = preds.get(i); + + if (Bits.get(tracebackSet, predLabel)) { + /* + * We found a predecessor loop; stop tracing back + * from here. + */ + break; + } + + if (!Bits.get(workSet, predLabel)) { + // This one's already ordered. + continue; + } + + BasicBlock pred = blocks.labelToBlock(predLabel); + if (pred.getPrimarySuccessor() == label) { + // Found one! + label = predLabel; + Bits.set(tracebackSet, label); + continue traceBack; + } + } + + // Failed to find a better block to start the trace. + break; + } + + /* + * Trace a path from the chosen block to one of its + * unordered successors (hopefully the primary), and so + * on, until we run out of unordered successors. + */ + while (label != -1) { + Bits.clear(workSet, label); + Bits.clear(tracebackSet, label); + order[at] = label; + at++; + + BasicBlock one = blocks.labelToBlock(label); + BasicBlock preferredBlock = blocks.preferredSuccessorOf(one); + + if (preferredBlock == null) { + break; + } + + int preferred = preferredBlock.getLabel(); + int primary = one.getPrimarySuccessor(); + + if (Bits.get(workSet, preferred)) { + /* + * Order the current block's preferred successor + * next, as it has yet to be scheduled. + */ + label = preferred; + } else if ((primary != preferred) && (primary >= 0) + && Bits.get(workSet, primary)) { + /* + * The primary is available, so use that. + */ + label = primary; + } else { + /* + * There's no obvious candidate, so pick the first + * one that's available, if any. + */ + IntList successors = one.getSuccessors(); + int ssz = successors.size(); + label = -1; + for (int i = 0; i < ssz; i++) { + int candidate = successors.get(i); + if (Bits.get(workSet, candidate)) { + label = candidate; + break; + } + } + } + } + } + + if (at != sz) { + // There was a duplicate block label. + throw new RuntimeException("shouldn't happen"); + } + + this.order = order; + } + + /** + * Gets the complete register list (result and sources) out of a + * given rop instruction. For insns that are commutative, have + * two register sources, and have a source equal to the result, + * place that source first. + * + * @param insn {@code non-null;} instruction in question + * @return {@code non-null;} the instruction's complete register list + */ + private static RegisterSpecList getRegs(Insn insn) { + return getRegs(insn, insn.getResult()); + } + + /** + * Gets the complete register list (result and sources) out of a + * given rop instruction. For insns that are commutative, have + * two register sources, and have a source equal to the result, + * place that source first. + * + * @param insn {@code non-null;} instruction in question + * @param resultReg {@code null-ok;} the real result to use (ignore the insn's) + * @return {@code non-null;} the instruction's complete register list + */ + private static RegisterSpecList getRegs(Insn insn, + RegisterSpec resultReg) { + RegisterSpecList regs = insn.getSources(); + + if (insn.getOpcode().isCommutative() + && (regs.size() == 2) + && (resultReg.getReg() == regs.get(1).getReg())) { + + /* + * For commutative ops which have two register sources, + * if the second source is the same register as the result, + * swap the sources so that an opcode of form 12x can be selected + * instead of one of form 23x + */ + + regs = RegisterSpecList.make(regs.get(1), regs.get(0)); + } + + if (resultReg == null) { + return regs; + } + + return regs.withFirst(resultReg); + } + + /** + * Instruction visitor class for doing the instruction translation per se. + */ + private class TranslationVisitor implements Insn.Visitor { + /** {@code non-null;} list of output instructions in-progress */ + private final OutputCollector output; + + /** {@code non-null;} basic block being worked on */ + private BasicBlock block; + + /** + * {@code null-ok;} code address for the salient last instruction of the + * block (used before switches and throwing instructions) + */ + private CodeAddress lastAddress; + + /** + * Constructs an instance. + * + * @param output {@code non-null;} destination for instruction output + */ + public TranslationVisitor(OutputCollector output) { + this.output = output; + } + + /** + * Sets the block currently being worked on. + * + * @param block {@code non-null;} the block + * @param lastAddress {@code non-null;} code address for the salient + * last instruction of the block + */ + public void setBlock(BasicBlock block, CodeAddress lastAddress) { + this.block = block; + this.lastAddress = lastAddress; + } + + /** {@inheritDoc} */ + public void visitPlainInsn(PlainInsn insn) { + Rop rop = insn.getOpcode(); + if (rop.getOpcode() == RegOps.MARK_LOCAL) { + /* + * Ignore these. They're dealt with by + * the LocalVariableAwareTranslationVisitor + */ + return; + } + if (rop.getOpcode() == RegOps.MOVE_RESULT_PSEUDO) { + // These get skipped + return; + } + + SourcePosition pos = insn.getPosition(); + Dop opcode = RopToDop.dopFor(insn); + DalvInsn di; + + switch (rop.getBranchingness()) { + case Rop.BRANCH_NONE: + case Rop.BRANCH_RETURN: + case Rop.BRANCH_THROW: { + di = new SimpleInsn(opcode, pos, getRegs(insn)); + break; + } + case Rop.BRANCH_GOTO: { + /* + * Code in the main translation loop will emit a + * goto if necessary (if the branch isn't to the + * immediately subsequent block). + */ + return; + } + case Rop.BRANCH_IF: { + int target = block.getSuccessors().get(1); + di = new TargetInsn(opcode, pos, getRegs(insn), + addresses.getStart(target)); + break; + } + default: { + throw new RuntimeException("shouldn't happen"); + } + } + + addOutput(di); + } + + /** {@inheritDoc} */ + public void visitPlainCstInsn(PlainCstInsn insn) { + SourcePosition pos = insn.getPosition(); + Dop opcode = RopToDop.dopFor(insn); + Rop rop = insn.getOpcode(); + int ropOpcode = rop.getOpcode(); + DalvInsn di; + + if (rop.getBranchingness() != Rop.BRANCH_NONE) { + throw new RuntimeException("shouldn't happen"); + } + + if (ropOpcode == RegOps.MOVE_PARAM) { + if (!paramsAreInOrder) { + /* + * Parameters are not in order at the top of the reg space. + * We need to add moves. + */ + + RegisterSpec dest = insn.getResult(); + int param = + ((CstInteger) insn.getConstant()).getValue(); + RegisterSpec source = + RegisterSpec.make(regCount - paramSize + param, + dest.getType()); + di = new SimpleInsn(opcode, pos, + RegisterSpecList.make(dest, source)); + addOutput(di); + } + } else { + // No moves required for the parameters + RegisterSpecList regs = getRegs(insn); + di = new CstInsn(opcode, pos, regs, insn.getConstant()); + addOutput(di); + } + } + + /** {@inheritDoc} */ + public void visitSwitchInsn(SwitchInsn insn) { + SourcePosition pos = insn.getPosition(); + IntList cases = insn.getCases(); + IntList successors = block.getSuccessors(); + int casesSz = cases.size(); + int succSz = successors.size(); + int primarySuccessor = block.getPrimarySuccessor(); + + /* + * Check the assumptions that the number of cases is one + * less than the number of successors and that the last + * successor in the list is the primary (in this case, the + * default). This test is here to guard against forgetting + * to change this code if the way switch instructions are + * constructed also gets changed. + */ + if ((casesSz != (succSz - 1)) || + (primarySuccessor != successors.get(casesSz))) { + throw new RuntimeException("shouldn't happen"); + } + + CodeAddress[] switchTargets = new CodeAddress[casesSz]; + + for (int i = 0; i < casesSz; i++) { + int label = successors.get(i); + switchTargets[i] = addresses.getStart(label); + } + + CodeAddress dataAddress = new CodeAddress(pos); + // make a new address that binds closely to the switch instruction + CodeAddress switchAddress = + new CodeAddress(lastAddress.getPosition(), true); + SwitchData dataInsn = + new SwitchData(pos, switchAddress, cases, switchTargets); + Dop opcode = dataInsn.isPacked() ? + Dops.PACKED_SWITCH : Dops.SPARSE_SWITCH; + TargetInsn switchInsn = + new TargetInsn(opcode, pos, getRegs(insn), dataAddress); + + addOutput(switchAddress); + addOutput(switchInsn); + + addOutputSuffix(new OddSpacer(pos)); + addOutputSuffix(dataAddress); + addOutputSuffix(dataInsn); + } + + /** + * Looks forward to the current block's primary successor, returning + * the RegisterSpec of the result of the move-result-pseudo at the + * top of that block or null if none. + * + * @return {@code null-ok;} result of move-result-pseudo at the beginning of + * primary successor + */ + private RegisterSpec getNextMoveResultPseudo() + { + int label = block.getPrimarySuccessor(); + + if (label < 0) { + return null; + } + + Insn insn + = method.getBlocks().labelToBlock(label).getInsns().get(0); + + if (insn.getOpcode().getOpcode() != RegOps.MOVE_RESULT_PSEUDO) { + return null; + } else { + return insn.getResult(); + } + } + + /** {@inheritDoc} */ + public void visitThrowingCstInsn(ThrowingCstInsn insn) { + SourcePosition pos = insn.getPosition(); + Dop opcode = RopToDop.dopFor(insn); + Rop rop = insn.getOpcode(); + Constant cst = insn.getConstant(); + + if (rop.getBranchingness() != Rop.BRANCH_THROW) { + throw new RuntimeException("shouldn't happen"); + } + + addOutput(lastAddress); + + if (rop.isCallLike()) { + RegisterSpecList regs = insn.getSources(); + DalvInsn di = new CstInsn(opcode, pos, regs, cst); + + addOutput(di); + } else { + RegisterSpec realResult = getNextMoveResultPseudo(); + + RegisterSpecList regs = getRegs(insn, realResult); + DalvInsn di; + + boolean hasResult = opcode.hasResult() + || (rop.getOpcode() == RegOps.CHECK_CAST); + + if (hasResult != (realResult != null)) { + throw new RuntimeException( + "Insn with result/move-result-pseudo mismatch " + + insn); + } + + if ((rop.getOpcode() == RegOps.NEW_ARRAY) && + (opcode.getOpcode() != Opcodes.NEW_ARRAY)) { + /* + * It's a type-specific new-array-, and + * so it should be turned into a SimpleInsn (no + * constant ref as it's implicit). + */ + di = new SimpleInsn(opcode, pos, regs); + } else { + /* + * This is the general case for constant-bearing + * instructions. + */ + di = new CstInsn(opcode, pos, regs, cst); + } + + addOutput(di); + } + } + + /** {@inheritDoc} */ + public void visitThrowingInsn(ThrowingInsn insn) { + SourcePosition pos = insn.getPosition(); + Dop opcode = RopToDop.dopFor(insn); + Rop rop = insn.getOpcode(); + RegisterSpec realResult; + + if (rop.getBranchingness() != Rop.BRANCH_THROW) { + throw new RuntimeException("shouldn't happen"); + } + + realResult = getNextMoveResultPseudo(); + + if (opcode.hasResult() != (realResult != null)) { + throw new RuntimeException( + "Insn with result/move-result-pseudo mismatch" + insn); + } + + addOutput(lastAddress); + + DalvInsn di = new SimpleInsn(opcode, pos, + getRegs(insn, realResult)); + + addOutput(di); + } + + /** {@inheritDoc} */ + public void visitFillArrayDataInsn(FillArrayDataInsn insn) { + SourcePosition pos = insn.getPosition(); + Constant cst = insn.getConstant(); + ArrayList values = insn.getInitValues(); + Rop rop = insn.getOpcode(); + + if (rop.getBranchingness() != Rop.BRANCH_NONE) { + throw new RuntimeException("shouldn't happen"); + } + CodeAddress dataAddress = new CodeAddress(pos); + ArrayData dataInsn = + new ArrayData(pos, lastAddress, values, cst); + + TargetInsn fillArrayDataInsn = + new TargetInsn(Dops.FILL_ARRAY_DATA, pos, getRegs(insn), + dataAddress); + + addOutput(lastAddress); + addOutput(fillArrayDataInsn); + + addOutputSuffix(new OddSpacer(pos)); + addOutputSuffix(dataAddress); + addOutputSuffix(dataInsn); + } + + /** + * Adds to the output. + * + * @param insn {@code non-null;} instruction to add + */ + protected void addOutput(DalvInsn insn) { + output.add(insn); + } + + /** + * Adds to the output suffix. + * + * @param insn {@code non-null;} instruction to add + */ + protected void addOutputSuffix(DalvInsn insn) { + output.addSuffix(insn); + } + } + + /** + * Instruction visitor class for doing instruction translation with + * local variable tracking + */ + private class LocalVariableAwareTranslationVisitor + extends TranslationVisitor { + /** {@code non-null;} local variable info */ + private LocalVariableInfo locals; + + /** + * Constructs an instance. + * + * @param output {@code non-null;} destination for instruction output + * @param locals {@code non-null;} the local variable info + */ + public LocalVariableAwareTranslationVisitor(OutputCollector output, + LocalVariableInfo locals) { + super(output); + this.locals = locals; + } + + /** {@inheritDoc} */ + @Override + public void visitPlainInsn(PlainInsn insn) { + super.visitPlainInsn(insn); + addIntroductionIfNecessary(insn); + } + + /** {@inheritDoc} */ + @Override + public void visitPlainCstInsn(PlainCstInsn insn) { + super.visitPlainCstInsn(insn); + addIntroductionIfNecessary(insn); + } + + /** {@inheritDoc} */ + @Override + public void visitSwitchInsn(SwitchInsn insn) { + super.visitSwitchInsn(insn); + addIntroductionIfNecessary(insn); + } + + /** {@inheritDoc} */ + @Override + public void visitThrowingCstInsn(ThrowingCstInsn insn) { + super.visitThrowingCstInsn(insn); + addIntroductionIfNecessary(insn); + } + + /** {@inheritDoc} */ + @Override + public void visitThrowingInsn(ThrowingInsn insn) { + super.visitThrowingInsn(insn); + addIntroductionIfNecessary(insn); + } + + /** + * Adds a {@link LocalStart} to the output if the given + * instruction in fact introduces a local variable. + * + * @param insn {@code non-null;} instruction in question + */ + public void addIntroductionIfNecessary(Insn insn) { + RegisterSpec spec = locals.getAssignment(insn); + + if (spec != null) { + addOutput(new LocalStart(insn.getPosition(), spec)); + } + } + } +} diff --git a/dx/src/com/android/jack/dx/dex/code/SimpleInsn.java b/dx/src/com/android/jack/dx/dex/code/SimpleInsn.java new file mode 100644 index 00000000..ca233e49 --- /dev/null +++ b/dx/src/com/android/jack/dx/dex/code/SimpleInsn.java @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.dex.code; + +import com.android.jack.dx.rop.code.RegisterSpecList; +import com.android.jack.dx.rop.code.SourcePosition; + +/** + * Instruction which has no extra info beyond the basics provided for in + * the base class. + */ +public final class SimpleInsn extends FixedSizeInsn { + /** + * Constructs an instance. The output address of this instance is initially + * unknown ({@code -1}). + * + * @param opcode the opcode; one of the constants from {@link Dops} + * @param position {@code non-null;} source position + * @param registers {@code non-null;} register list, including a + * result register if appropriate (that is, registers may be either + * ins or outs) + */ + public SimpleInsn(Dop opcode, SourcePosition position, + RegisterSpecList registers) { + super(opcode, position, registers); + } + + /** {@inheritDoc} */ + @Override + public DalvInsn withOpcode(Dop opcode) { + return new SimpleInsn(opcode, getPosition(), getRegisters()); + } + + /** {@inheritDoc} */ + @Override + public DalvInsn withRegisters(RegisterSpecList registers) { + return new SimpleInsn(getOpcode(), getPosition(), registers); + } + + /** {@inheritDoc} */ + @Override + protected String argString() { + return null; + } +} diff --git a/dx/src/com/android/jack/dx/dex/code/StdCatchBuilder.java b/dx/src/com/android/jack/dx/dex/code/StdCatchBuilder.java new file mode 100644 index 00000000..e7f75eb0 --- /dev/null +++ b/dx/src/com/android/jack/dx/dex/code/StdCatchBuilder.java @@ -0,0 +1,316 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.dex.code; + +import com.android.jack.dx.rop.code.BasicBlock; +import com.android.jack.dx.rop.code.BasicBlockList; +import com.android.jack.dx.rop.code.RopMethod; +import com.android.jack.dx.rop.cst.CstType; +import com.android.jack.dx.rop.type.Type; +import com.android.jack.dx.rop.type.TypeList; +import com.android.jack.dx.util.IntList; + +import java.util.ArrayList; +import java.util.HashSet; + +/** + * Constructor of {@link CatchTable} instances from {@link RopMethod} + * and associated data. + */ +public final class StdCatchBuilder implements CatchBuilder { + /** the maximum range of a single catch handler, in code units */ + private static final int MAX_CATCH_RANGE = 65535; + + /** {@code non-null;} method to build the list for */ + private final RopMethod method; + + /** {@code non-null;} block output order */ + private final int[] order; + + /** {@code non-null;} address objects for each block */ + private final BlockAddresses addresses; + + /** + * Constructs an instance. It merely holds onto its parameters for + * a subsequent call to {@link #build}. + * + * @param method {@code non-null;} method to build the list for + * @param order {@code non-null;} block output order + * @param addresses {@code non-null;} address objects for each block + */ + public StdCatchBuilder(RopMethod method, int[] order, + BlockAddresses addresses) { + if (method == null) { + throw new NullPointerException("method == null"); + } + + if (order == null) { + throw new NullPointerException("order == null"); + } + + if (addresses == null) { + throw new NullPointerException("addresses == null"); + } + + this.method = method; + this.order = order; + this.addresses = addresses; + } + + /** {@inheritDoc} */ + public CatchTable build() { + return build(method, order, addresses); + } + + /** {@inheritDoc} */ + public boolean hasAnyCatches() { + BasicBlockList blocks = method.getBlocks(); + int size = blocks.size(); + + for (int i = 0; i < size; i++) { + BasicBlock block = blocks.get(i); + TypeList catches = block.getLastInsn().getCatches(); + if (catches.size() != 0) { + return true; + } + } + + return false; + } + + /** {@inheritDoc} */ + public HashSet getCatchTypes() { + HashSet result = new HashSet(20); + BasicBlockList blocks = method.getBlocks(); + int size = blocks.size(); + + for (int i = 0; i < size; i++) { + BasicBlock block = blocks.get(i); + TypeList catches = block.getLastInsn().getCatches(); + int catchSize = catches.size(); + + for (int j = 0; j < catchSize; j++) { + result.add(catches.getType(j)); + } + } + + return result; + } + + /** + * Builds and returns the catch table for a given method. + * + * @param method {@code non-null;} method to build the list for + * @param order {@code non-null;} block output order + * @param addresses {@code non-null;} address objects for each block + * @return {@code non-null;} the constructed table + */ + public static CatchTable build(RopMethod method, int[] order, + BlockAddresses addresses) { + int len = order.length; + BasicBlockList blocks = method.getBlocks(); + ArrayList resultList = + new ArrayList(len); + CatchHandlerList currentHandlers = CatchHandlerList.EMPTY; + BasicBlock currentStartBlock = null; + BasicBlock currentEndBlock = null; + + for (int i = 0; i < len; i++) { + BasicBlock block = blocks.labelToBlock(order[i]); + + if (!block.canThrow()) { + /* + * There is no need to concern ourselves with the + * placement of blocks that can't throw with respect + * to the blocks that *can* throw. + */ + continue; + } + + CatchHandlerList handlers = handlersFor(block, addresses); + + if (currentHandlers.size() == 0) { + // This is the start of a new catch range. + currentStartBlock = block; + currentEndBlock = block; + currentHandlers = handlers; + continue; + } + + if (currentHandlers.equals(handlers) + && rangeIsValid(currentStartBlock, block, addresses)) { + /* + * The block we are looking at now has the same handlers + * as the block that started the currently open catch + * range, and adding it to the currently open range won't + * cause it to be too long. + */ + currentEndBlock = block; + continue; + } + + /* + * The block we are looking at now has incompatible handlers, + * so we need to finish off the last entry and start a new + * one. Note: We only emit an entry if it has associated handlers. + */ + if (currentHandlers.size() != 0) { + CatchTable.Entry entry = + makeEntry(currentStartBlock, currentEndBlock, + currentHandlers, addresses); + resultList.add(entry); + } + + currentStartBlock = block; + currentEndBlock = block; + currentHandlers = handlers; + } + + if (currentHandlers.size() != 0) { + // Emit an entry for the range that was left hanging. + CatchTable.Entry entry = + makeEntry(currentStartBlock, currentEndBlock, + currentHandlers, addresses); + resultList.add(entry); + } + + // Construct the final result. + + int resultSz = resultList.size(); + + if (resultSz == 0) { + return CatchTable.EMPTY; + } + + CatchTable result = new CatchTable(resultSz); + + for (int i = 0; i < resultSz; i++) { + result.set(i, resultList.get(i)); + } + + result.setImmutable(); + return result; + } + + /** + * Makes the {@link CatchHandlerList} for the given basic block. + * + * @param block {@code non-null;} block to get entries for + * @param addresses {@code non-null;} address objects for each block + * @return {@code non-null;} array of entries + */ + private static CatchHandlerList handlersFor(BasicBlock block, + BlockAddresses addresses) { + IntList successors = block.getSuccessors(); + int succSize = successors.size(); + int primary = block.getPrimarySuccessor(); + TypeList catches = block.getLastInsn().getCatches(); + int catchSize = catches.size(); + + if (catchSize == 0) { + return CatchHandlerList.EMPTY; + } + + if (((primary == -1) && (succSize != catchSize)) + || ((primary != -1) && + ((succSize != (catchSize + 1)) + || (primary != successors.get(catchSize))))) { + /* + * Blocks that throw are supposed to list their primary + * successor -- if any -- last in the successors list, but + * that constraint appears to be violated here. + */ + throw new RuntimeException( + "shouldn't happen: weird successors list"); + } + + /* + * Reduce the effective catchSize if we spot a catch-all that + * isn't at the end. + */ + for (int i = 0; i < catchSize; i++) { + Type type = catches.getType(i); + if (type.equals(Type.OBJECT)) { + catchSize = i + 1; + break; + } + } + + CatchHandlerList result = new CatchHandlerList(catchSize); + + for (int i = 0; i < catchSize; i++) { + CstType oneType = new CstType(catches.getType(i)); + CodeAddress oneHandler = addresses.getStart(successors.get(i)); + result.set(i, oneType, oneHandler.getAddress()); + } + + result.setImmutable(); + return result; + } + + /** + * Makes a {@link CatchTable#Entry} for the given block range and + * handlers. + * + * @param start {@code non-null;} the start block for the range (inclusive) + * @param end {@code non-null;} the start block for the range (also inclusive) + * @param handlers {@code non-null;} the handlers for the range + * @param addresses {@code non-null;} address objects for each block + */ + private static CatchTable.Entry makeEntry(BasicBlock start, + BasicBlock end, CatchHandlerList handlers, + BlockAddresses addresses) { + /* + * We start at the *last* instruction of the start block, since + * that's the instruction that can throw... + */ + CodeAddress startAddress = addresses.getLast(start); + + // ...And we end *after* the last instruction of the end block. + CodeAddress endAddress = addresses.getEnd(end); + + return new CatchTable.Entry(startAddress.getAddress(), + endAddress.getAddress(), handlers); + } + + /** + * Gets whether the address range for the given two blocks is valid + * for a catch handler. This is true as long as the covered range is + * under 65536 code units. + * + * @param start {@code non-null;} the start block for the range (inclusive) + * @param end {@code non-null;} the start block for the range (also inclusive) + * @param addresses {@code non-null;} address objects for each block + * @return {@code true} if the range is valid as a catch range + */ + private static boolean rangeIsValid(BasicBlock start, BasicBlock end, + BlockAddresses addresses) { + if (start == null) { + throw new NullPointerException("start == null"); + } + + if (end == null) { + throw new NullPointerException("end == null"); + } + + // See above about selection of instructions. + int startAddress = addresses.getLast(start).getAddress(); + int endAddress = addresses.getEnd(end).getAddress(); + + return (endAddress - startAddress) <= MAX_CATCH_RANGE; + } +} diff --git a/dx/src/com/android/jack/dx/dex/code/SwitchData.java b/dx/src/com/android/jack/dx/dex/code/SwitchData.java new file mode 100644 index 00000000..0785fba1 --- /dev/null +++ b/dx/src/com/android/jack/dx/dex/code/SwitchData.java @@ -0,0 +1,258 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.dex.code; + +import com.android.jack.dx.io.Opcodes; +import com.android.jack.dx.rop.code.RegisterSpecList; +import com.android.jack.dx.rop.code.SourcePosition; +import com.android.jack.dx.util.AnnotatedOutput; +import com.android.jack.dx.util.Hex; +import com.android.jack.dx.util.IntList; + +/** + * Pseudo-instruction which holds switch data. The switch data is + * a map of values to target addresses, and this class writes the data + * in either a "packed" or "sparse" form. + */ +public final class SwitchData extends VariableSizeInsn { + /** + * {@code non-null;} address representing the instruction that uses this + * instance + */ + private final CodeAddress user; + + /** {@code non-null;} sorted list of switch cases (keys) */ + private final IntList cases; + + /** + * {@code non-null;} corresponding list of code addresses; the branch + * target for each case + */ + private final CodeAddress[] targets; + + /** whether the output table will be packed (vs. sparse) */ + private final boolean packed; + + /** + * Constructs an instance. The output address of this instance is initially + * unknown ({@code -1}). + * + * @param position {@code non-null;} source position + * @param user {@code non-null;} address representing the instruction that + * uses this instance + * @param cases {@code non-null;} sorted list of switch cases (keys) + * @param targets {@code non-null;} corresponding list of code addresses; the + * branch target for each case + */ + public SwitchData(SourcePosition position, CodeAddress user, + IntList cases, CodeAddress[] targets) { + super(position, RegisterSpecList.EMPTY); + + if (user == null) { + throw new NullPointerException("user == null"); + } + + if (cases == null) { + throw new NullPointerException("cases == null"); + } + + if (targets == null) { + throw new NullPointerException("targets == null"); + } + + int sz = cases.size(); + + if (sz != targets.length) { + throw new IllegalArgumentException("cases / targets mismatch"); + } + + if (sz > 65535) { + throw new IllegalArgumentException("too many cases"); + } + + this.user = user; + this.cases = cases; + this.targets = targets; + this.packed = shouldPack(cases); + } + + /** {@inheritDoc} */ + @Override + public int codeSize() { + return packed ? (int) packedCodeSize(cases) : + (int) sparseCodeSize(cases); + } + + /** {@inheritDoc} */ + @Override + public void writeTo(AnnotatedOutput out) { + int baseAddress = user.getAddress(); + int defaultTarget = Dops.PACKED_SWITCH.getFormat().codeSize(); + int sz = targets.length; + + if (packed) { + int firstCase = (sz == 0) ? 0 : cases.get(0); + int lastCase = (sz == 0) ? 0 : cases.get(sz - 1); + int outSz = lastCase - firstCase + 1; + + out.writeShort(Opcodes.PACKED_SWITCH_PAYLOAD); + out.writeShort(outSz); + out.writeInt(firstCase); + + int caseAt = 0; + for (int i = 0; i < outSz; i++) { + int outCase = firstCase + i; + int oneCase = cases.get(caseAt); + int relTarget; + + if (oneCase > outCase) { + relTarget = defaultTarget; + } else { + relTarget = targets[caseAt].getAddress() - baseAddress; + caseAt++; + } + + out.writeInt(relTarget); + } + } else { + out.writeShort(Opcodes.SPARSE_SWITCH_PAYLOAD); + out.writeShort(sz); + + for (int i = 0; i < sz; i++) { + out.writeInt(cases.get(i)); + } + + for (int i = 0; i < sz; i++) { + int relTarget = targets[i].getAddress() - baseAddress; + out.writeInt(relTarget); + } + } + } + + /** {@inheritDoc} */ + @Override + public DalvInsn withRegisters(RegisterSpecList registers) { + return new SwitchData(getPosition(), user, cases, targets); + } + + /** + * Returns whether or not this instance's data will be output as packed. + * + * @return {@code true} iff the data is to be packed + */ + public boolean isPacked() { + return packed; + } + + /** {@inheritDoc} */ + @Override + protected String argString() { + StringBuffer sb = new StringBuffer(100); + + int sz = targets.length; + for (int i = 0; i < sz; i++) { + sb.append("\n "); + sb.append(cases.get(i)); + sb.append(": "); + sb.append(targets[i]); + } + + return sb.toString(); + } + + /** {@inheritDoc} */ + @Override + protected String listingString0(boolean noteIndices) { + int baseAddress = user.getAddress(); + StringBuffer sb = new StringBuffer(100); + int sz = targets.length; + + sb.append(packed ? "packed" : "sparse"); + sb.append("-switch-payload // for switch @ "); + sb.append(Hex.u2(baseAddress)); + + for (int i = 0; i < sz; i++) { + int absTarget = targets[i].getAddress(); + int relTarget = absTarget - baseAddress; + sb.append("\n "); + sb.append(cases.get(i)); + sb.append(": "); + sb.append(Hex.u4(absTarget)); + sb.append(" // "); + sb.append(Hex.s4(relTarget)); + } + + return sb.toString(); + } + + /** + * Gets the size of a packed table for the given cases, in 16-bit code + * units. + * + * @param cases {@code non-null;} sorted list of cases + * @return {@code >= -1;} the packed table size or {@code -1} if the + * cases couldn't possibly be represented as a packed table + */ + private static long packedCodeSize(IntList cases) { + int sz = cases.size(); + long low = cases.get(0); + long high = cases.get(sz - 1); + long result = ((high - low + 1)) * 2 + 4; + + return (result <= 0x7fffffff) ? result : -1; + } + + /** + * Gets the size of a sparse table for the given cases, in 16-bit code + * units. + * + * @param cases {@code non-null;} sorted list of cases + * @return {@code > 0;} the sparse table size + */ + private static long sparseCodeSize(IntList cases) { + int sz = cases.size(); + + return (sz * 4L) + 2; + } + + /** + * Determines whether the given list of cases warrant being packed. + * + * @param cases {@code non-null;} sorted list of cases + * @return {@code true} iff the table encoding the cases + * should be packed + */ + private static boolean shouldPack(IntList cases) { + int sz = cases.size(); + + if (sz < 2) { + return true; + } + + long packedSize = packedCodeSize(cases); + long sparseSize = sparseCodeSize(cases); + + /* + * We pick the packed representation if it is possible and + * would be as small or smaller than 5/4 of the sparse + * representation. That is, we accept some size overhead on + * the packed representation, since that format is faster to + * execute at runtime. + */ + return (packedSize >= 0) && (packedSize <= ((sparseSize * 5) / 4)); + } +} diff --git a/dx/src/com/android/jack/dx/dex/code/TargetInsn.java b/dx/src/com/android/jack/dx/dex/code/TargetInsn.java new file mode 100644 index 00000000..c957867a --- /dev/null +++ b/dx/src/com/android/jack/dx/dex/code/TargetInsn.java @@ -0,0 +1,132 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.dex.code; + +import com.android.jack.dx.rop.code.RegisterSpecList; +import com.android.jack.dx.rop.code.SourcePosition; + +/** + * Instruction which has a single branch target. + */ +public final class TargetInsn extends FixedSizeInsn { + /** {@code non-null;} the branch target */ + private CodeAddress target; + + /** + * Constructs an instance. The output address of this instance is initially + * unknown ({@code -1}), and the target is initially + * {@code null}. + * + * @param opcode the opcode; one of the constants from {@link Dops} + * @param position {@code non-null;} source position + * @param registers {@code non-null;} register list, including a + * result register if appropriate (that is, registers may be either + * ins or outs) + * @param target {@code non-null;} the branch target + */ + public TargetInsn(Dop opcode, SourcePosition position, + RegisterSpecList registers, CodeAddress target) { + super(opcode, position, registers); + + if (target == null) { + throw new NullPointerException("target == null"); + } + + this.target = target; + } + + /** {@inheritDoc} */ + @Override + public DalvInsn withOpcode(Dop opcode) { + return new TargetInsn(opcode, getPosition(), getRegisters(), target); + } + + /** {@inheritDoc} */ + @Override + public DalvInsn withRegisters(RegisterSpecList registers) { + return new TargetInsn(getOpcode(), getPosition(), registers, target); + } + + /** + * Returns an instance that is just like this one, except that its + * opcode has the opposite sense (as a test; e.g. a + * {@code lt} test becomes a {@code ge}), and its branch + * target is replaced by the one given, and all set-once values + * associated with the class (such as its address) are reset. + * + * @param target {@code non-null;} the new branch target + * @return {@code non-null;} an appropriately-constructed instance + */ + public TargetInsn withNewTargetAndReversed(CodeAddress target) { + Dop opcode = getOpcode().getOppositeTest(); + + return new TargetInsn(opcode, getPosition(), getRegisters(), target); + } + + /** + * Gets the unique branch target of this instruction. + * + * @return {@code non-null;} the branch target + */ + public CodeAddress getTarget() { + return target; + } + + /** + * Gets the target address of this instruction. This is only valid + * to call if the target instruction has been assigned an address, + * and it is merely a convenient shorthand for + * {@code getTarget().getAddress()}. + * + * @return {@code >= 0;} the target address + */ + public int getTargetAddress() { + return target.getAddress(); + } + + /** + * Gets the branch offset of this instruction. This is only valid to + * call if both this and the target instruction each has been assigned + * an address, and it is merely a convenient shorthand for + * {@code getTargetAddress() - getAddress()}. + * + * @return the branch offset + */ + public int getTargetOffset() { + return target.getAddress() - getAddress(); + } + + /** + * Returns whether the target offset is known. + * + * @return {@code true} if the target offset is known or + * {@code false} if not + */ + public boolean hasTargetOffset() { + return hasAddress() && target.hasAddress(); + } + + /** {@inheritDoc} */ + @Override + protected String argString() { + if (target == null) { + return "????"; + } + + return target.identifierString(); + } +} diff --git a/dx/src/com/android/jack/dx/dex/code/VariableSizeInsn.java b/dx/src/com/android/jack/dx/dex/code/VariableSizeInsn.java new file mode 100644 index 00000000..31106294 --- /dev/null +++ b/dx/src/com/android/jack/dx/dex/code/VariableSizeInsn.java @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.dex.code; + +import com.android.jack.dx.rop.code.RegisterSpecList; +import com.android.jack.dx.rop.code.SourcePosition; + +/** + * Pseudo-instruction base class for variable-sized instructions. + */ +public abstract class VariableSizeInsn extends DalvInsn { + /** + * Constructs an instance. The output address of this instance is initially + * unknown ({@code -1}). + * + * @param position {@code non-null;} source position + * @param registers {@code non-null;} source registers + */ + public VariableSizeInsn(SourcePosition position, + RegisterSpecList registers) { + super(Dops.SPECIAL_FORMAT, position, registers); + } + + /** {@inheritDoc} */ + @Override + public final DalvInsn withOpcode(Dop opcode) { + throw new RuntimeException("unsupported"); + } + + /** {@inheritDoc} */ + @Override + public final DalvInsn withRegisterOffset(int delta) { + return withRegisters(getRegisters().withOffset(delta)); + } +} diff --git a/dx/src/com/android/jack/dx/dex/code/ZeroSizeInsn.java b/dx/src/com/android/jack/dx/dex/code/ZeroSizeInsn.java new file mode 100644 index 00000000..d9a7f1b5 --- /dev/null +++ b/dx/src/com/android/jack/dx/dex/code/ZeroSizeInsn.java @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.dex.code; + +import com.android.jack.dx.rop.code.RegisterSpecList; +import com.android.jack.dx.rop.code.SourcePosition; +import com.android.jack.dx.util.AnnotatedOutput; + +/** + * Pseudo-instruction base class for zero-size (no code emitted) + * instructions, which are generally used for tracking metainformation + * about the code they are adjacent to. + */ +public abstract class ZeroSizeInsn extends DalvInsn { + /** + * Constructs an instance. The output address of this instance is initially + * unknown ({@code -1}). + * + * @param position {@code non-null;} source position + */ + public ZeroSizeInsn(SourcePosition position) { + super(Dops.SPECIAL_FORMAT, position, RegisterSpecList.EMPTY); + } + + /** {@inheritDoc} */ + @Override + public final int codeSize() { + return 0; + } + + /** {@inheritDoc} */ + @Override + public final void writeTo(AnnotatedOutput out) { + // Nothing to do here, for this class. + } + + /** {@inheritDoc} */ + @Override + public final DalvInsn withOpcode(Dop opcode) { + throw new RuntimeException("unsupported"); + } + + /** {@inheritDoc} */ + @Override + public DalvInsn withRegisterOffset(int delta) { + return withRegisters(getRegisters().withOffset(delta)); + } +} diff --git a/dx/src/com/android/jack/dx/dex/code/form/Form10t.java b/dx/src/com/android/jack/dx/dex/code/form/Form10t.java new file mode 100644 index 00000000..6f279862 --- /dev/null +++ b/dx/src/com/android/jack/dx/dex/code/form/Form10t.java @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.dex.code.form; + +import com.android.jack.dx.dex.code.DalvInsn; +import com.android.jack.dx.dex.code.InsnFormat; +import com.android.jack.dx.dex.code.TargetInsn; +import com.android.jack.dx.util.AnnotatedOutput; + +/** + * Instruction format {@code 10t}. See the instruction format spec + * for details. + */ +public final class Form10t extends InsnFormat { + /** {@code non-null;} unique instance of this class */ + public static final InsnFormat THE_ONE = new Form10t(); + + /** + * Constructs an instance. This class is not publicly + * instantiable. Use {@link #THE_ONE}. + */ + private Form10t() { + // This space intentionally left blank. + } + + /** {@inheritDoc} */ + @Override + public String insnArgString(DalvInsn insn) { + return branchString(insn); + } + + /** {@inheritDoc} */ + @Override + public String insnCommentString(DalvInsn insn, boolean noteIndices) { + return branchComment(insn); + } + + /** {@inheritDoc} */ + @Override + public int codeSize() { + return 1; + } + + /** {@inheritDoc} */ + @Override + public boolean isCompatible(DalvInsn insn) { + if (!((insn instanceof TargetInsn) && + (insn.getRegisters().size() == 0))) { + return false; + } + + TargetInsn ti = (TargetInsn) insn; + return ti.hasTargetOffset() ? branchFits(ti) : true; + } + + /** {@inheritDoc} */ + @Override + public boolean branchFits(TargetInsn insn) { + int offset = insn.getTargetOffset(); + + // Note: A zero offset would fit, but it is prohibited by the spec. + return (offset != 0) && signedFitsInByte(offset); + } + + /** {@inheritDoc} */ + @Override + public void writeTo(AnnotatedOutput out, DalvInsn insn) { + int offset = ((TargetInsn) insn).getTargetOffset(); + + write(out, opcodeUnit(insn, (offset & 0xff))); + } +} diff --git a/dx/src/com/android/jack/dx/dex/code/form/Form10x.java b/dx/src/com/android/jack/dx/dex/code/form/Form10x.java new file mode 100644 index 00000000..785f0fa5 --- /dev/null +++ b/dx/src/com/android/jack/dx/dex/code/form/Form10x.java @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.dex.code.form; + +import com.android.jack.dx.dex.code.DalvInsn; +import com.android.jack.dx.dex.code.InsnFormat; +import com.android.jack.dx.dex.code.SimpleInsn; +import com.android.jack.dx.util.AnnotatedOutput; + +/** + * Instruction format {@code 10x}. See the instruction format spec + * for details. + */ +public final class Form10x extends InsnFormat { + /** {@code non-null;} unique instance of this class */ + public static final InsnFormat THE_ONE = new Form10x(); + + /** + * Constructs an instance. This class is not publicly + * instantiable. Use {@link #THE_ONE}. + */ + private Form10x() { + // This space intentionally left blank. + } + + /** {@inheritDoc} */ + @Override + public String insnArgString(DalvInsn insn) { + // This format has no arguments. + return ""; + } + + /** {@inheritDoc} */ + @Override + public String insnCommentString(DalvInsn insn, boolean noteIndices) { + // This format has no comment. + return ""; + } + + /** {@inheritDoc} */ + @Override + public int codeSize() { + return 1; + } + + /** {@inheritDoc} */ + @Override + public boolean isCompatible(DalvInsn insn) { + return (insn instanceof SimpleInsn) && + (insn.getRegisters().size() == 0); + } + + /** {@inheritDoc} */ + @Override + public void writeTo(AnnotatedOutput out, DalvInsn insn) { + write(out, opcodeUnit(insn, 0)); + } +} diff --git a/dx/src/com/android/jack/dx/dex/code/form/Form11n.java b/dx/src/com/android/jack/dx/dex/code/form/Form11n.java new file mode 100644 index 00000000..c30357e9 --- /dev/null +++ b/dx/src/com/android/jack/dx/dex/code/form/Form11n.java @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.dex.code.form; + +import com.android.jack.dx.dex.code.CstInsn; +import com.android.jack.dx.dex.code.DalvInsn; +import com.android.jack.dx.dex.code.InsnFormat; +import com.android.jack.dx.rop.code.RegisterSpecList; +import com.android.jack.dx.rop.cst.Constant; +import com.android.jack.dx.rop.cst.CstLiteralBits; +import com.android.jack.dx.util.AnnotatedOutput; + +import java.util.BitSet; + +/** + * Instruction format {@code 11n}. See the instruction format spec + * for details. + */ +public final class Form11n extends InsnFormat { + /** {@code non-null;} unique instance of this class */ + public static final InsnFormat THE_ONE = new Form11n(); + + /** + * Constructs an instance. This class is not publicly + * instantiable. Use {@link #THE_ONE}. + */ + private Form11n() { + // This space intentionally left blank. + } + + /** {@inheritDoc} */ + @Override + public String insnArgString(DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + CstLiteralBits value = (CstLiteralBits) ((CstInsn) insn).getConstant(); + + return regs.get(0).regString() + ", " + literalBitsString(value); + } + + /** {@inheritDoc} */ + @Override + public String insnCommentString(DalvInsn insn, boolean noteIndices) { + CstLiteralBits value = (CstLiteralBits) ((CstInsn) insn).getConstant(); + return literalBitsComment(value, 4); + } + + /** {@inheritDoc} */ + @Override + public int codeSize() { + return 1; + } + + /** {@inheritDoc} */ + @Override + public boolean isCompatible(DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + + if (!((insn instanceof CstInsn) && + (regs.size() == 1) && + unsignedFitsInNibble(regs.get(0).getReg()))) { + return false; + } + + CstInsn ci = (CstInsn) insn; + Constant cst = ci.getConstant(); + + if (!(cst instanceof CstLiteralBits)) { + return false; + } + + CstLiteralBits cb = (CstLiteralBits) cst; + + return cb.fitsInInt() && signedFitsInNibble(cb.getIntBits()); + } + + /** {@inheritDoc} */ + @Override + public BitSet compatibleRegs(DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + BitSet bits = new BitSet(1); + + bits.set(0, unsignedFitsInNibble(regs.get(0).getReg())); + return bits; + } + + /** {@inheritDoc} */ + @Override + public void writeTo(AnnotatedOutput out, DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + int value = + ((CstLiteralBits) ((CstInsn) insn).getConstant()).getIntBits(); + + write(out, + opcodeUnit(insn, makeByte(regs.get(0).getReg(), value & 0xf))); + } +} diff --git a/dx/src/com/android/jack/dx/dex/code/form/Form11x.java b/dx/src/com/android/jack/dx/dex/code/form/Form11x.java new file mode 100644 index 00000000..a0002024 --- /dev/null +++ b/dx/src/com/android/jack/dx/dex/code/form/Form11x.java @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.dex.code.form; + +import com.android.jack.dx.dex.code.DalvInsn; +import com.android.jack.dx.dex.code.InsnFormat; +import com.android.jack.dx.dex.code.SimpleInsn; +import com.android.jack.dx.rop.code.RegisterSpecList; +import com.android.jack.dx.util.AnnotatedOutput; + +import java.util.BitSet; + +/** + * Instruction format {@code 11x}. See the instruction format spec + * for details. + */ +public final class Form11x extends InsnFormat { + /** {@code non-null;} unique instance of this class */ + public static final InsnFormat THE_ONE = new Form11x(); + + /** + * Constructs an instance. This class is not publicly + * instantiable. Use {@link #THE_ONE}. + */ + private Form11x() { + // This space intentionally left blank. + } + + /** {@inheritDoc} */ + @Override + public String insnArgString(DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + return regs.get(0).regString(); + } + + /** {@inheritDoc} */ + @Override + public String insnCommentString(DalvInsn insn, boolean noteIndices) { + // This format has no comment. + return ""; + } + + /** {@inheritDoc} */ + @Override + public int codeSize() { + return 1; + } + + /** {@inheritDoc} */ + @Override + public boolean isCompatible(DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + return (insn instanceof SimpleInsn) && + (regs.size() == 1) && + unsignedFitsInByte(regs.get(0).getReg()); + } + + /** {@inheritDoc} */ + @Override + public BitSet compatibleRegs(DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + BitSet bits = new BitSet(1); + + bits.set(0, unsignedFitsInByte(regs.get(0).getReg())); + return bits; + } + + /** {@inheritDoc} */ + @Override + public void writeTo(AnnotatedOutput out, DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + write(out, opcodeUnit(insn, regs.get(0).getReg())); + } +} diff --git a/dx/src/com/android/jack/dx/dex/code/form/Form12x.java b/dx/src/com/android/jack/dx/dex/code/form/Form12x.java new file mode 100644 index 00000000..4c93d274 --- /dev/null +++ b/dx/src/com/android/jack/dx/dex/code/form/Form12x.java @@ -0,0 +1,162 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.dex.code.form; + +import com.android.jack.dx.dex.code.DalvInsn; +import com.android.jack.dx.dex.code.InsnFormat; +import com.android.jack.dx.dex.code.SimpleInsn; +import com.android.jack.dx.rop.code.RegisterSpec; +import com.android.jack.dx.rop.code.RegisterSpecList; +import com.android.jack.dx.util.AnnotatedOutput; + +import java.util.BitSet; + +/** + * Instruction format {@code 12x}. See the instruction format spec + * for details. + */ +public final class Form12x extends InsnFormat { + /** {@code non-null;} unique instance of this class */ + public static final InsnFormat THE_ONE = new Form12x(); + + /** + * Constructs an instance. This class is not publicly + * instantiable. Use {@link #THE_ONE}. + */ + private Form12x() { + // This space intentionally left blank. + } + + /** {@inheritDoc} */ + @Override + public String insnArgString(DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + int sz = regs.size(); + + /* + * The (sz - 2) and (sz - 1) below makes this code work for + * both the two- and three-register ops. (See "case 3" in + * isCompatible(), below.) + */ + + return regs.get(sz - 2).regString() + ", " + + regs.get(sz - 1).regString(); + } + + /** {@inheritDoc} */ + @Override + public String insnCommentString(DalvInsn insn, boolean noteIndices) { + // This format has no comment. + return ""; + } + + /** {@inheritDoc} */ + @Override + public int codeSize() { + return 1; + } + + /** {@inheritDoc} */ + @Override + public boolean isCompatible(DalvInsn insn) { + if (!(insn instanceof SimpleInsn)) { + return false; + } + + RegisterSpecList regs = insn.getRegisters(); + RegisterSpec rs1; + RegisterSpec rs2; + + switch (regs.size()) { + case 2: { + rs1 = regs.get(0); + rs2 = regs.get(1); + break; + } + case 3: { + /* + * This format is allowed for ops that are effectively + * 3-arg but where the first two args are identical. + */ + rs1 = regs.get(1); + rs2 = regs.get(2); + if (rs1.getReg() != regs.get(0).getReg()) { + return false; + } + break; + } + default: { + return false; + } + } + + return unsignedFitsInNibble(rs1.getReg()) && + unsignedFitsInNibble(rs2.getReg()); + } + + /** {@inheritDoc} */ + @Override + public BitSet compatibleRegs(DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + BitSet bits = new BitSet(2); + int r0 = regs.get(0).getReg(); + int r1 = regs.get(1).getReg(); + + switch (regs.size()) { + case 2: { + bits.set(0, unsignedFitsInNibble(r0)); + bits.set(1, unsignedFitsInNibble(r1)); + break; + } + case 3: { + if (r0 != r1) { + bits.set(0, false); + bits.set(1, false); + } else { + boolean dstRegComp = unsignedFitsInNibble(r1); + bits.set(0, dstRegComp); + bits.set(1, dstRegComp); + } + + bits.set(2, unsignedFitsInNibble(regs.get(2).getReg())); + break; + } + default: { + throw new AssertionError(); + } + } + + return bits; + } + + /** {@inheritDoc} */ + @Override + public void writeTo(AnnotatedOutput out, DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + int sz = regs.size(); + + /* + * The (sz - 2) and (sz - 1) below makes this code work for + * both the two- and three-register ops. (See "case 3" in + * isCompatible(), above.) + */ + + write(out, opcodeUnit(insn, + makeByte(regs.get(sz - 2).getReg(), + regs.get(sz - 1).getReg()))); + } +} diff --git a/dx/src/com/android/jack/dx/dex/code/form/Form20t.java b/dx/src/com/android/jack/dx/dex/code/form/Form20t.java new file mode 100644 index 00000000..e05042a3 --- /dev/null +++ b/dx/src/com/android/jack/dx/dex/code/form/Form20t.java @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.dex.code.form; + +import com.android.jack.dx.dex.code.DalvInsn; +import com.android.jack.dx.dex.code.InsnFormat; +import com.android.jack.dx.dex.code.TargetInsn; +import com.android.jack.dx.util.AnnotatedOutput; + +/** + * Instruction format {@code 20t}. See the instruction format spec + * for details. + */ +public final class Form20t extends InsnFormat { + /** {@code non-null;} unique instance of this class */ + public static final InsnFormat THE_ONE = new Form20t(); + + /** + * Constructs an instance. This class is not publicly + * instantiable. Use {@link #THE_ONE}. + */ + private Form20t() { + // This space intentionally left blank. + } + + /** {@inheritDoc} */ + @Override + public String insnArgString(DalvInsn insn) { + return branchString(insn); + } + + /** {@inheritDoc} */ + @Override + public String insnCommentString(DalvInsn insn, boolean noteIndices) { + return branchComment(insn); + } + + /** {@inheritDoc} */ + @Override + public int codeSize() { + return 2; + } + + /** {@inheritDoc} */ + @Override + public boolean isCompatible(DalvInsn insn) { + if (!((insn instanceof TargetInsn) && + (insn.getRegisters().size() == 0))) { + return false; + } + + TargetInsn ti = (TargetInsn) insn; + return ti.hasTargetOffset() ? branchFits(ti) : true; + } + + /** {@inheritDoc} */ + @Override + public boolean branchFits(TargetInsn insn) { + int offset = insn.getTargetOffset(); + + // Note: A zero offset would fit, but it is prohibited by the spec. + return (offset != 0) && signedFitsInShort(offset); + } + + /** {@inheritDoc} */ + @Override + public void writeTo(AnnotatedOutput out, DalvInsn insn) { + int offset = ((TargetInsn) insn).getTargetOffset(); + + write(out, opcodeUnit(insn, 0), (short) offset); + } +} diff --git a/dx/src/com/android/jack/dx/dex/code/form/Form21c.java b/dx/src/com/android/jack/dx/dex/code/form/Form21c.java new file mode 100644 index 00000000..90415f1e --- /dev/null +++ b/dx/src/com/android/jack/dx/dex/code/form/Form21c.java @@ -0,0 +1,149 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.dex.code.form; + +import com.android.jack.dx.dex.code.CstInsn; +import com.android.jack.dx.dex.code.DalvInsn; +import com.android.jack.dx.dex.code.InsnFormat; +import com.android.jack.dx.rop.code.RegisterSpec; +import com.android.jack.dx.rop.code.RegisterSpecList; +import com.android.jack.dx.rop.cst.Constant; +import com.android.jack.dx.rop.cst.CstFieldRef; +import com.android.jack.dx.rop.cst.CstString; +import com.android.jack.dx.rop.cst.CstType; +import com.android.jack.dx.util.AnnotatedOutput; + +import java.util.BitSet; + +/** + * Instruction format {@code 21c}. See the instruction format spec + * for details. + */ +public final class Form21c extends InsnFormat { + /** {@code non-null;} unique instance of this class */ + public static final InsnFormat THE_ONE = new Form21c(); + + /** + * Constructs an instance. This class is not publicly + * instantiable. Use {@link #THE_ONE}. + */ + private Form21c() { + // This space intentionally left blank. + } + + /** {@inheritDoc} */ + @Override + public String insnArgString(DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + return regs.get(0).regString() + ", " + cstString(insn); + } + + /** {@inheritDoc} */ + @Override + public String insnCommentString(DalvInsn insn, boolean noteIndices) { + if (noteIndices) { + return cstComment(insn); + } else { + return ""; + } + } + + /** {@inheritDoc} */ + @Override + public int codeSize() { + return 2; + } + + /** {@inheritDoc} */ + @Override + public boolean isCompatible(DalvInsn insn) { + if (!(insn instanceof CstInsn)) { + return false; + } + + RegisterSpecList regs = insn.getRegisters(); + RegisterSpec reg; + + switch (regs.size()) { + case 1: { + reg = regs.get(0); + break; + } + case 2: { + /* + * This format is allowed for ops that are effectively + * 2-arg but where the two args are identical. + */ + reg = regs.get(0); + if (reg.getReg() != regs.get(1).getReg()) { + return false; + } + break; + } + default: { + return false; + } + } + + if (!unsignedFitsInByte(reg.getReg())) { + return false; + } + + CstInsn ci = (CstInsn) insn; + int cpi = ci.getIndex(); + Constant cst = ci.getConstant(); + + if (! unsignedFitsInShort(cpi)) { + return false; + } + + return (cst instanceof CstType) || + (cst instanceof CstFieldRef) || + (cst instanceof CstString); + } + + /** {@inheritDoc} */ + @Override + public BitSet compatibleRegs(DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + int sz = regs.size(); + BitSet bits = new BitSet(sz); + boolean compat = unsignedFitsInByte(regs.get(0).getReg()); + + if (sz == 1) { + bits.set(0, compat); + } else { + if (regs.get(0).getReg() == regs.get(1).getReg()) { + bits.set(0, compat); + bits.set(1, compat); + } + } + + return bits; + } + + /** {@inheritDoc} */ + @Override + public void writeTo(AnnotatedOutput out, DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + int cpi = ((CstInsn) insn).getIndex(); + + write(out, + opcodeUnit(insn, regs.get(0).getReg()), + (short) cpi); + } +} diff --git a/dx/src/com/android/jack/dx/dex/code/form/Form21h.java b/dx/src/com/android/jack/dx/dex/code/form/Form21h.java new file mode 100644 index 00000000..4a57f0ad --- /dev/null +++ b/dx/src/com/android/jack/dx/dex/code/form/Form21h.java @@ -0,0 +1,126 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.dex.code.form; + +import com.android.jack.dx.dex.code.CstInsn; +import com.android.jack.dx.dex.code.DalvInsn; +import com.android.jack.dx.dex.code.InsnFormat; +import com.android.jack.dx.rop.code.RegisterSpecList; +import com.android.jack.dx.rop.cst.Constant; +import com.android.jack.dx.rop.cst.CstLiteralBits; +import com.android.jack.dx.util.AnnotatedOutput; + +import java.util.BitSet; + +/** + * Instruction format {@code 21h}. See the instruction format spec + * for details. + */ +public final class Form21h extends InsnFormat { + /** {@code non-null;} unique instance of this class */ + public static final InsnFormat THE_ONE = new Form21h(); + + /** + * Constructs an instance. This class is not publicly + * instantiable. Use {@link #THE_ONE}. + */ + private Form21h() { + // This space intentionally left blank. + } + + /** {@inheritDoc} */ + @Override + public String insnArgString(DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + CstLiteralBits value = (CstLiteralBits) ((CstInsn) insn).getConstant(); + + return regs.get(0).regString() + ", " + literalBitsString(value); + } + + /** {@inheritDoc} */ + @Override + public String insnCommentString(DalvInsn insn, boolean noteIndices) { + RegisterSpecList regs = insn.getRegisters(); + CstLiteralBits value = (CstLiteralBits) ((CstInsn) insn).getConstant(); + + return + literalBitsComment(value, + (regs.get(0).getCategory() == 1) ? 32 : 64); + } + + /** {@inheritDoc} */ + @Override + public int codeSize() { + return 2; + } + + /** {@inheritDoc} */ + @Override + public boolean isCompatible(DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + if (!((insn instanceof CstInsn) && + (regs.size() == 1) && + unsignedFitsInByte(regs.get(0).getReg()))) { + return false; + } + + CstInsn ci = (CstInsn) insn; + Constant cst = ci.getConstant(); + + if (!(cst instanceof CstLiteralBits)) { + return false; + } + + CstLiteralBits cb = (CstLiteralBits) cst; + + // Where the high bits are depends on the category of the target. + if (regs.get(0).getCategory() == 1) { + int bits = cb.getIntBits(); + return ((bits & 0xffff) == 0); + } else { + long bits = cb.getLongBits(); + return ((bits & 0xffffffffffffL) == 0); + } + } + + /** {@inheritDoc} */ + @Override + public BitSet compatibleRegs(DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + BitSet bits = new BitSet(1); + + bits.set(0, unsignedFitsInByte(regs.get(0).getReg())); + return bits; + } + + /** {@inheritDoc} */ + @Override + public void writeTo(AnnotatedOutput out, DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + CstLiteralBits cb = (CstLiteralBits) ((CstInsn) insn).getConstant(); + short bits; + + // Where the high bits are depends on the category of the target. + if (regs.get(0).getCategory() == 1) { + bits = (short) (cb.getIntBits() >>> 16); + } else { + bits = (short) (cb.getLongBits() >>> 48); + } + + write(out, opcodeUnit(insn, regs.get(0).getReg()), bits); + } +} diff --git a/dx/src/com/android/jack/dx/dex/code/form/Form21s.java b/dx/src/com/android/jack/dx/dex/code/form/Form21s.java new file mode 100644 index 00000000..57dd12af --- /dev/null +++ b/dx/src/com/android/jack/dx/dex/code/form/Form21s.java @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.dex.code.form; + +import com.android.jack.dx.dex.code.CstInsn; +import com.android.jack.dx.dex.code.DalvInsn; +import com.android.jack.dx.dex.code.InsnFormat; +import com.android.jack.dx.rop.code.RegisterSpecList; +import com.android.jack.dx.rop.cst.Constant; +import com.android.jack.dx.rop.cst.CstLiteralBits; +import com.android.jack.dx.util.AnnotatedOutput; + +import java.util.BitSet; + +/** + * Instruction format {@code 21s}. See the instruction format spec + * for details. + */ +public final class Form21s extends InsnFormat { + /** {@code non-null;} unique instance of this class */ + public static final InsnFormat THE_ONE = new Form21s(); + + /** + * Constructs an instance. This class is not publicly + * instantiable. Use {@link #THE_ONE}. + */ + private Form21s() { + // This space intentionally left blank. + } + + /** {@inheritDoc} */ + @Override + public String insnArgString(DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + CstLiteralBits value = (CstLiteralBits) ((CstInsn) insn).getConstant(); + + return regs.get(0).regString() + ", " + literalBitsString(value); + } + + /** {@inheritDoc} */ + @Override + public String insnCommentString(DalvInsn insn, boolean noteIndices) { + CstLiteralBits value = (CstLiteralBits) ((CstInsn) insn).getConstant(); + return literalBitsComment(value, 16); + } + + /** {@inheritDoc} */ + @Override + public int codeSize() { + return 2; + } + + /** {@inheritDoc} */ + @Override + public boolean isCompatible(DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + if (!((insn instanceof CstInsn) && + (regs.size() == 1) && + unsignedFitsInByte(regs.get(0).getReg()))) { + return false; + } + + CstInsn ci = (CstInsn) insn; + Constant cst = ci.getConstant(); + + if (!(cst instanceof CstLiteralBits)) { + return false; + } + + CstLiteralBits cb = (CstLiteralBits) cst; + + return cb.fitsInInt() && signedFitsInShort(cb.getIntBits()); + } + + /** {@inheritDoc} */ + @Override + public BitSet compatibleRegs(DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + BitSet bits = new BitSet(1); + + bits.set(0, unsignedFitsInByte(regs.get(0).getReg())); + return bits; + } + + /** {@inheritDoc} */ + @Override + public void writeTo(AnnotatedOutput out, DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + int value = + ((CstLiteralBits) ((CstInsn) insn).getConstant()).getIntBits(); + + write(out, + opcodeUnit(insn, regs.get(0).getReg()), + (short) value); + } +} diff --git a/dx/src/com/android/jack/dx/dex/code/form/Form21t.java b/dx/src/com/android/jack/dx/dex/code/form/Form21t.java new file mode 100644 index 00000000..5836a5e0 --- /dev/null +++ b/dx/src/com/android/jack/dx/dex/code/form/Form21t.java @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.dex.code.form; + +import com.android.jack.dx.dex.code.DalvInsn; +import com.android.jack.dx.dex.code.InsnFormat; +import com.android.jack.dx.dex.code.TargetInsn; +import com.android.jack.dx.rop.code.RegisterSpecList; +import com.android.jack.dx.util.AnnotatedOutput; + +import java.util.BitSet; + +/** + * Instruction format {@code 21t}. See the instruction format spec + * for details. + */ +public final class Form21t extends InsnFormat { + /** {@code non-null;} unique instance of this class */ + public static final InsnFormat THE_ONE = new Form21t(); + + /** + * Constructs an instance. This class is not publicly + * instantiable. Use {@link #THE_ONE}. + */ + private Form21t() { + // This space intentionally left blank. + } + + /** {@inheritDoc} */ + @Override + public String insnArgString(DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + return regs.get(0).regString() + ", " + branchString(insn); + } + + /** {@inheritDoc} */ + @Override + public String insnCommentString(DalvInsn insn, boolean noteIndices) { + return branchComment(insn); + } + + /** {@inheritDoc} */ + @Override + public int codeSize() { + return 2; + } + + /** {@inheritDoc} */ + @Override + public boolean isCompatible(DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + + if (!((insn instanceof TargetInsn) && + (regs.size() == 1) && + unsignedFitsInByte(regs.get(0).getReg()))) { + return false; + } + + TargetInsn ti = (TargetInsn) insn; + return ti.hasTargetOffset() ? branchFits(ti) : true; + } + + /** {@inheritDoc} */ + @Override + public BitSet compatibleRegs(DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + BitSet bits = new BitSet(1); + + bits.set(0, unsignedFitsInByte(regs.get(0).getReg())); + return bits; + } + + /** {@inheritDoc} */ + @Override + public boolean branchFits(TargetInsn insn) { + int offset = insn.getTargetOffset(); + + // Note: A zero offset would fit, but it is prohibited by the spec. + return (offset != 0) && signedFitsInShort(offset); + } + + /** {@inheritDoc} */ + @Override + public void writeTo(AnnotatedOutput out, DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + int offset = ((TargetInsn) insn).getTargetOffset(); + + write(out, + opcodeUnit(insn, regs.get(0).getReg()), + (short) offset); + } +} diff --git a/dx/src/com/android/jack/dx/dex/code/form/Form22b.java b/dx/src/com/android/jack/dx/dex/code/form/Form22b.java new file mode 100644 index 00000000..a09b6c6b --- /dev/null +++ b/dx/src/com/android/jack/dx/dex/code/form/Form22b.java @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.dex.code.form; + +import com.android.jack.dx.dex.code.CstInsn; +import com.android.jack.dx.dex.code.DalvInsn; +import com.android.jack.dx.dex.code.InsnFormat; +import com.android.jack.dx.rop.code.RegisterSpecList; +import com.android.jack.dx.rop.cst.Constant; +import com.android.jack.dx.rop.cst.CstLiteralBits; +import com.android.jack.dx.util.AnnotatedOutput; + +import java.util.BitSet; + +/** + * Instruction format {@code 22b}. See the instruction format spec + * for details. + */ +public final class Form22b extends InsnFormat { + /** {@code non-null;} unique instance of this class */ + public static final InsnFormat THE_ONE = new Form22b(); + + /** + * Constructs an instance. This class is not publicly + * instantiable. Use {@link #THE_ONE}. + */ + private Form22b() { + // This space intentionally left blank. + } + + /** {@inheritDoc} */ + @Override + public String insnArgString(DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + CstLiteralBits value = (CstLiteralBits) ((CstInsn) insn).getConstant(); + + return regs.get(0).regString() + ", " + regs.get(1).regString() + + ", " + literalBitsString(value); + } + + /** {@inheritDoc} */ + @Override + public String insnCommentString(DalvInsn insn, boolean noteIndices) { + CstLiteralBits value = (CstLiteralBits) ((CstInsn) insn).getConstant(); + return literalBitsComment(value, 8); + } + + /** {@inheritDoc} */ + @Override + public int codeSize() { + return 2; + } + + /** {@inheritDoc} */ + @Override + public boolean isCompatible(DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + if (!((insn instanceof CstInsn) && + (regs.size() == 2) && + unsignedFitsInByte(regs.get(0).getReg()) && + unsignedFitsInByte(regs.get(1).getReg()))) { + return false; + } + + CstInsn ci = (CstInsn) insn; + Constant cst = ci.getConstant(); + + if (!(cst instanceof CstLiteralBits)) { + return false; + } + + CstLiteralBits cb = (CstLiteralBits) cst; + + return cb.fitsInInt() && signedFitsInByte(cb.getIntBits()); + } + + /** {@inheritDoc} */ + @Override + public BitSet compatibleRegs(DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + BitSet bits = new BitSet(2); + + bits.set(0, unsignedFitsInByte(regs.get(0).getReg())); + bits.set(1, unsignedFitsInByte(regs.get(1).getReg())); + return bits; + } + + /** {@inheritDoc} */ + @Override + public void writeTo(AnnotatedOutput out, DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + int value = + ((CstLiteralBits) ((CstInsn) insn).getConstant()).getIntBits(); + + write(out, + opcodeUnit(insn, regs.get(0).getReg()), + codeUnit(regs.get(1).getReg(), value & 0xff)); + } +} diff --git a/dx/src/com/android/jack/dx/dex/code/form/Form22c.java b/dx/src/com/android/jack/dx/dex/code/form/Form22c.java new file mode 100644 index 00000000..d170e06e --- /dev/null +++ b/dx/src/com/android/jack/dx/dex/code/form/Form22c.java @@ -0,0 +1,115 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.dex.code.form; + +import com.android.jack.dx.dex.code.CstInsn; +import com.android.jack.dx.dex.code.DalvInsn; +import com.android.jack.dx.dex.code.InsnFormat; +import com.android.jack.dx.rop.code.RegisterSpecList; +import com.android.jack.dx.rop.cst.Constant; +import com.android.jack.dx.rop.cst.CstFieldRef; +import com.android.jack.dx.rop.cst.CstType; +import com.android.jack.dx.util.AnnotatedOutput; + +import java.util.BitSet; + +/** + * Instruction format {@code 22c}. See the instruction format spec + * for details. + */ +public final class Form22c extends InsnFormat { + /** {@code non-null;} unique instance of this class */ + public static final InsnFormat THE_ONE = new Form22c(); + + /** + * Constructs an instance. This class is not publicly + * instantiable. Use {@link #THE_ONE}. + */ + private Form22c() { + // This space intentionally left blank. + } + + /** {@inheritDoc} */ + @Override + public String insnArgString(DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + return regs.get(0).regString() + ", " + regs.get(1).regString() + + ", " + cstString(insn); + } + + /** {@inheritDoc} */ + @Override + public String insnCommentString(DalvInsn insn, boolean noteIndices) { + if (noteIndices) { + return cstComment(insn); + } else { + return ""; + } + } + + /** {@inheritDoc} */ + @Override + public int codeSize() { + return 2; + } + + /** {@inheritDoc} */ + @Override + public boolean isCompatible(DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + if (!((insn instanceof CstInsn) && + (regs.size() == 2) && + unsignedFitsInNibble(regs.get(0).getReg()) && + unsignedFitsInNibble(regs.get(1).getReg()))) { + return false; + } + + CstInsn ci = (CstInsn) insn; + int cpi = ci.getIndex(); + + if (! unsignedFitsInShort(cpi)) { + return false; + } + + Constant cst = ci.getConstant(); + return (cst instanceof CstType) || + (cst instanceof CstFieldRef); + } + + /** {@inheritDoc} */ + @Override + public BitSet compatibleRegs(DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + BitSet bits = new BitSet(2); + + bits.set(0, unsignedFitsInNibble(regs.get(0).getReg())); + bits.set(1, unsignedFitsInNibble(regs.get(1).getReg())); + return bits; + } + + /** {@inheritDoc} */ + @Override + public void writeTo(AnnotatedOutput out, DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + int cpi = ((CstInsn) insn).getIndex(); + + write(out, + opcodeUnit(insn, + makeByte(regs.get(0).getReg(), regs.get(1).getReg())), + (short) cpi); + } +} diff --git a/dx/src/com/android/jack/dx/dex/code/form/Form22s.java b/dx/src/com/android/jack/dx/dex/code/form/Form22s.java new file mode 100644 index 00000000..bbd66a3b --- /dev/null +++ b/dx/src/com/android/jack/dx/dex/code/form/Form22s.java @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.dex.code.form; + +import com.android.jack.dx.dex.code.CstInsn; +import com.android.jack.dx.dex.code.DalvInsn; +import com.android.jack.dx.dex.code.InsnFormat; +import com.android.jack.dx.rop.code.RegisterSpecList; +import com.android.jack.dx.rop.cst.Constant; +import com.android.jack.dx.rop.cst.CstLiteralBits; +import com.android.jack.dx.util.AnnotatedOutput; + +import java.util.BitSet; + +/** + * Instruction format {@code 22s}. See the instruction format spec + * for details. + */ +public final class Form22s extends InsnFormat { + /** {@code non-null;} unique instance of this class */ + public static final InsnFormat THE_ONE = new Form22s(); + + /** + * Constructs an instance. This class is not publicly + * instantiable. Use {@link #THE_ONE}. + */ + private Form22s() { + // This space intentionally left blank. + } + + /** {@inheritDoc} */ + @Override + public String insnArgString(DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + CstLiteralBits value = (CstLiteralBits) ((CstInsn) insn).getConstant(); + + return regs.get(0).regString() + ", " + regs.get(1).regString() + + ", " + literalBitsString(value); + } + + /** {@inheritDoc} */ + @Override + public String insnCommentString(DalvInsn insn, boolean noteIndices) { + CstLiteralBits value = (CstLiteralBits) ((CstInsn) insn).getConstant(); + return literalBitsComment(value, 16); + } + + /** {@inheritDoc} */ + @Override + public int codeSize() { + return 2; + } + + /** {@inheritDoc} */ + @Override + public boolean isCompatible(DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + if (!((insn instanceof CstInsn) && + (regs.size() == 2) && + unsignedFitsInNibble(regs.get(0).getReg()) && + unsignedFitsInNibble(regs.get(1).getReg()))) { + return false; + } + + CstInsn ci = (CstInsn) insn; + Constant cst = ci.getConstant(); + + if (!(cst instanceof CstLiteralBits)) { + return false; + } + + CstLiteralBits cb = (CstLiteralBits) cst; + + return cb.fitsInInt() && signedFitsInShort(cb.getIntBits()); + } + + /** {@inheritDoc} */ + @Override + public BitSet compatibleRegs(DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + BitSet bits = new BitSet(2); + + bits.set(0, unsignedFitsInNibble(regs.get(0).getReg())); + bits.set(1, unsignedFitsInNibble(regs.get(1).getReg())); + return bits; + } + + /** {@inheritDoc} */ + @Override + public void writeTo(AnnotatedOutput out, DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + int value = + ((CstLiteralBits) ((CstInsn) insn).getConstant()).getIntBits(); + + write(out, + opcodeUnit(insn, + makeByte(regs.get(0).getReg(), regs.get(1).getReg())), + (short) value); + } +} diff --git a/dx/src/com/android/jack/dx/dex/code/form/Form22t.java b/dx/src/com/android/jack/dx/dex/code/form/Form22t.java new file mode 100644 index 00000000..3e9cfdb8 --- /dev/null +++ b/dx/src/com/android/jack/dx/dex/code/form/Form22t.java @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.dex.code.form; + +import com.android.jack.dx.dex.code.DalvInsn; +import com.android.jack.dx.dex.code.InsnFormat; +import com.android.jack.dx.dex.code.TargetInsn; +import com.android.jack.dx.rop.code.RegisterSpecList; +import com.android.jack.dx.util.AnnotatedOutput; + +import java.util.BitSet; + +/** + * Instruction format {@code 22t}. See the instruction format spec + * for details. + */ +public final class Form22t extends InsnFormat { + /** {@code non-null;} unique instance of this class */ + public static final InsnFormat THE_ONE = new Form22t(); + + /** + * Constructs an instance. This class is not publicly + * instantiable. Use {@link #THE_ONE}. + */ + private Form22t() { + // This space intentionally left blank. + } + + /** {@inheritDoc} */ + @Override + public String insnArgString(DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + return regs.get(0).regString() + ", " + regs.get(1).regString() + + ", " + branchString(insn); + } + + /** {@inheritDoc} */ + @Override + public String insnCommentString(DalvInsn insn, boolean noteIndices) { + return branchComment(insn); + } + + /** {@inheritDoc} */ + @Override + public int codeSize() { + return 2; + } + + /** {@inheritDoc} */ + @Override + public boolean isCompatible(DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + + if (!((insn instanceof TargetInsn) && + (regs.size() == 2) && + unsignedFitsInNibble(regs.get(0).getReg()) && + unsignedFitsInNibble(regs.get(1).getReg()))) { + return false; + } + + TargetInsn ti = (TargetInsn) insn; + return ti.hasTargetOffset() ? branchFits(ti) : true; + } + + /** {@inheritDoc} */ + @Override + public BitSet compatibleRegs(DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + BitSet bits = new BitSet(2); + + bits.set(0, unsignedFitsInNibble(regs.get(0).getReg())); + bits.set(1, unsignedFitsInNibble(regs.get(1).getReg())); + return bits; + } + + /** {@inheritDoc} */ + @Override + public boolean branchFits(TargetInsn insn) { + int offset = insn.getTargetOffset(); + + // Note: A zero offset would fit, but it is prohibited by the spec. + return (offset != 0) && signedFitsInShort(offset); + } + + /** {@inheritDoc} */ + @Override + public void writeTo(AnnotatedOutput out, DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + int offset = ((TargetInsn) insn).getTargetOffset(); + + write(out, + opcodeUnit(insn, + makeByte(regs.get(0).getReg(), regs.get(1).getReg())), + (short) offset); + } +} diff --git a/dx/src/com/android/jack/dx/dex/code/form/Form22x.java b/dx/src/com/android/jack/dx/dex/code/form/Form22x.java new file mode 100644 index 00000000..f9e4d9c9 --- /dev/null +++ b/dx/src/com/android/jack/dx/dex/code/form/Form22x.java @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.dex.code.form; + +import com.android.jack.dx.dex.code.DalvInsn; +import com.android.jack.dx.dex.code.InsnFormat; +import com.android.jack.dx.dex.code.SimpleInsn; +import com.android.jack.dx.rop.code.RegisterSpecList; +import com.android.jack.dx.util.AnnotatedOutput; + +import java.util.BitSet; + +/** + * Instruction format {@code 22x}. See the instruction format spec + * for details. + */ +public final class Form22x extends InsnFormat { + /** {@code non-null;} unique instance of this class */ + public static final InsnFormat THE_ONE = new Form22x(); + + /** + * Constructs an instance. This class is not publicly + * instantiable. Use {@link #THE_ONE}. + */ + private Form22x() { + // This space intentionally left blank. + } + + /** {@inheritDoc} */ + @Override + public String insnArgString(DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + return regs.get(0).regString() + ", " + regs.get(1).regString(); + } + + /** {@inheritDoc} */ + @Override + public String insnCommentString(DalvInsn insn, boolean noteIndices) { + // This format has no comment. + return ""; + } + + /** {@inheritDoc} */ + @Override + public int codeSize() { + return 2; + } + + /** {@inheritDoc} */ + @Override + public boolean isCompatible(DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + + return (insn instanceof SimpleInsn) && + (regs.size() == 2) && + unsignedFitsInByte(regs.get(0).getReg()) && + unsignedFitsInShort(regs.get(1).getReg()); + } + + /** {@inheritDoc} */ + @Override + public BitSet compatibleRegs(DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + BitSet bits = new BitSet(2); + + bits.set(0, unsignedFitsInByte(regs.get(0).getReg())); + bits.set(1, unsignedFitsInShort(regs.get(1).getReg())); + return bits; + } + + /** {@inheritDoc} */ + @Override + public void writeTo(AnnotatedOutput out, DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + write(out, + opcodeUnit(insn, regs.get(0).getReg()), + (short) regs.get(1).getReg()); + } +} diff --git a/dx/src/com/android/jack/dx/dex/code/form/Form23x.java b/dx/src/com/android/jack/dx/dex/code/form/Form23x.java new file mode 100644 index 00000000..32617883 --- /dev/null +++ b/dx/src/com/android/jack/dx/dex/code/form/Form23x.java @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.dex.code.form; + +import com.android.jack.dx.dex.code.DalvInsn; +import com.android.jack.dx.dex.code.InsnFormat; +import com.android.jack.dx.dex.code.SimpleInsn; +import com.android.jack.dx.rop.code.RegisterSpecList; +import com.android.jack.dx.util.AnnotatedOutput; + +import java.util.BitSet; + +/** + * Instruction format {@code 23x}. See the instruction format spec + * for details. + */ +public final class Form23x extends InsnFormat { + /** {@code non-null;} unique instance of this class */ + public static final InsnFormat THE_ONE = new Form23x(); + + /** + * Constructs an instance. This class is not publicly + * instantiable. Use {@link #THE_ONE}. + */ + private Form23x() { + // This space intentionally left blank. + } + + /** {@inheritDoc} */ + @Override + public String insnArgString(DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + return regs.get(0).regString() + ", " + regs.get(1).regString() + + ", " + regs.get(2).regString(); + } + + /** {@inheritDoc} */ + @Override + public String insnCommentString(DalvInsn insn, boolean noteIndices) { + // This format has no comment. + return ""; + } + + /** {@inheritDoc} */ + @Override + public int codeSize() { + return 2; + } + + /** {@inheritDoc} */ + @Override + public boolean isCompatible(DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + + return (insn instanceof SimpleInsn) && + (regs.size() == 3) && + unsignedFitsInByte(regs.get(0).getReg()) && + unsignedFitsInByte(regs.get(1).getReg()) && + unsignedFitsInByte(regs.get(2).getReg()); + } + + /** {@inheritDoc} */ + @Override + public BitSet compatibleRegs(DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + BitSet bits = new BitSet(3); + + bits.set(0, unsignedFitsInByte(regs.get(0).getReg())); + bits.set(1, unsignedFitsInByte(regs.get(1).getReg())); + bits.set(2, unsignedFitsInByte(regs.get(2).getReg())); + return bits; + } + + /** {@inheritDoc} */ + @Override + public void writeTo(AnnotatedOutput out, DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + write(out, + opcodeUnit(insn, regs.get(0).getReg()), + codeUnit(regs.get(1).getReg(), regs.get(2).getReg())); + } +} diff --git a/dx/src/com/android/jack/dx/dex/code/form/Form30t.java b/dx/src/com/android/jack/dx/dex/code/form/Form30t.java new file mode 100644 index 00000000..743f8066 --- /dev/null +++ b/dx/src/com/android/jack/dx/dex/code/form/Form30t.java @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.dex.code.form; + +import com.android.jack.dx.dex.code.DalvInsn; +import com.android.jack.dx.dex.code.InsnFormat; +import com.android.jack.dx.dex.code.TargetInsn; +import com.android.jack.dx.util.AnnotatedOutput; + +/** + * Instruction format {@code 30t}. See the instruction format spec + * for details. + */ +public final class Form30t extends InsnFormat { + /** {@code non-null;} unique instance of this class */ + public static final InsnFormat THE_ONE = new Form30t(); + + /** + * Constructs an instance. This class is not publicly + * instantiable. Use {@link #THE_ONE}. + */ + private Form30t() { + // This space intentionally left blank. + } + + /** {@inheritDoc} */ + @Override + public String insnArgString(DalvInsn insn) { + return branchString(insn); + } + + /** {@inheritDoc} */ + @Override + public String insnCommentString(DalvInsn insn, boolean noteIndices) { + return branchComment(insn); + } + + /** {@inheritDoc} */ + @Override + public int codeSize() { + return 3; + } + + /** {@inheritDoc} */ + @Override + public boolean isCompatible(DalvInsn insn) { + if (!((insn instanceof TargetInsn) && + (insn.getRegisters().size() == 0))) { + return false; + } + + return true; + } + + /** {@inheritDoc} */ + @Override + public boolean branchFits(TargetInsn insn) { + return true; + } + + /** {@inheritDoc} */ + @Override + public void writeTo(AnnotatedOutput out, DalvInsn insn) { + int offset = ((TargetInsn) insn).getTargetOffset(); + + write(out, opcodeUnit(insn, 0), offset); + } +} diff --git a/dx/src/com/android/jack/dx/dex/code/form/Form31c.java b/dx/src/com/android/jack/dx/dex/code/form/Form31c.java new file mode 100644 index 00000000..12e5f14d --- /dev/null +++ b/dx/src/com/android/jack/dx/dex/code/form/Form31c.java @@ -0,0 +1,142 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.dex.code.form; + +import com.android.jack.dx.dex.code.CstInsn; +import com.android.jack.dx.dex.code.DalvInsn; +import com.android.jack.dx.dex.code.InsnFormat; +import com.android.jack.dx.rop.code.RegisterSpec; +import com.android.jack.dx.rop.code.RegisterSpecList; +import com.android.jack.dx.rop.cst.Constant; +import com.android.jack.dx.rop.cst.CstFieldRef; +import com.android.jack.dx.rop.cst.CstString; +import com.android.jack.dx.rop.cst.CstType; +import com.android.jack.dx.util.AnnotatedOutput; + +import java.util.BitSet; + +/** + * Instruction format {@code 31c}. See the instruction format spec + * for details. + */ +public final class Form31c extends InsnFormat { + /** {@code non-null;} unique instance of this class */ + public static final InsnFormat THE_ONE = new Form31c(); + + /** + * Constructs an instance. This class is not publicly + * instantiable. Use {@link #THE_ONE}. + */ + private Form31c() { + // This space intentionally left blank. + } + + /** {@inheritDoc} */ + @Override + public String insnArgString(DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + return regs.get(0).regString() + ", " + cstString(insn); + } + + /** {@inheritDoc} */ + @Override + public String insnCommentString(DalvInsn insn, boolean noteIndices) { + if (noteIndices) { + return cstComment(insn); + } else { + return ""; + } + } + + /** {@inheritDoc} */ + @Override + public int codeSize() { + return 3; + } + + /** {@inheritDoc} */ + @Override + public boolean isCompatible(DalvInsn insn) { + if (!(insn instanceof CstInsn)) { + return false; + } + + RegisterSpecList regs = insn.getRegisters(); + RegisterSpec reg; + + switch (regs.size()) { + case 1: { + reg = regs.get(0); + break; + } + case 2: { + /* + * This format is allowed for ops that are effectively + * 2-arg but where the two args are identical. + */ + reg = regs.get(0); + if (reg.getReg() != regs.get(1).getReg()) { + return false; + } + break; + } + default: { + return false; + } + } + + if (!unsignedFitsInByte(reg.getReg())) { + return false; + } + + CstInsn ci = (CstInsn) insn; + Constant cst = ci.getConstant(); + + return (cst instanceof CstType) || + (cst instanceof CstFieldRef) || + (cst instanceof CstString); + } + + /** {@inheritDoc} */ + @Override + public BitSet compatibleRegs(DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + int sz = regs.size(); + BitSet bits = new BitSet(sz); + boolean compat = unsignedFitsInByte(regs.get(0).getReg()); + + if (sz == 1) { + bits.set(0, compat); + } else { + if (regs.get(0).getReg() == regs.get(1).getReg()) { + bits.set(0, compat); + bits.set(1, compat); + } + } + + return bits; + } + + /** {@inheritDoc} */ + @Override + public void writeTo(AnnotatedOutput out, DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + int cpi = ((CstInsn) insn).getIndex(); + + write(out, opcodeUnit(insn, regs.get(0).getReg()), cpi); + } +} diff --git a/dx/src/com/android/jack/dx/dex/code/form/Form31i.java b/dx/src/com/android/jack/dx/dex/code/form/Form31i.java new file mode 100644 index 00000000..8e45f18c --- /dev/null +++ b/dx/src/com/android/jack/dx/dex/code/form/Form31i.java @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.dex.code.form; + +import com.android.jack.dx.dex.code.CstInsn; +import com.android.jack.dx.dex.code.DalvInsn; +import com.android.jack.dx.dex.code.InsnFormat; +import com.android.jack.dx.rop.code.RegisterSpecList; +import com.android.jack.dx.rop.cst.Constant; +import com.android.jack.dx.rop.cst.CstLiteralBits; +import com.android.jack.dx.util.AnnotatedOutput; + +import java.util.BitSet; + +/** + * Instruction format {@code 31i}. See the instruction format spec + * for details. + */ +public final class Form31i extends InsnFormat { + /** {@code non-null;} unique instance of this class */ + public static final InsnFormat THE_ONE = new Form31i(); + + /** + * Constructs an instance. This class is not publicly + * instantiable. Use {@link #THE_ONE}. + */ + private Form31i() { + // This space intentionally left blank. + } + + /** {@inheritDoc} */ + @Override + public String insnArgString(DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + CstLiteralBits value = (CstLiteralBits) ((CstInsn) insn).getConstant(); + + return regs.get(0).regString() + ", " + literalBitsString(value); + } + + /** {@inheritDoc} */ + @Override + public String insnCommentString(DalvInsn insn, boolean noteIndices) { + CstLiteralBits value = (CstLiteralBits) ((CstInsn) insn).getConstant(); + return literalBitsComment(value, 32); + } + + /** {@inheritDoc} */ + @Override + public int codeSize() { + return 3; + } + + /** {@inheritDoc} */ + @Override + public boolean isCompatible(DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + if (!((insn instanceof CstInsn) && + (regs.size() == 1) && + unsignedFitsInByte(regs.get(0).getReg()))) { + return false; + } + + CstInsn ci = (CstInsn) insn; + Constant cst = ci.getConstant(); + + if (!(cst instanceof CstLiteralBits)) { + return false; + } + + return ((CstLiteralBits) cst).fitsInInt(); + } + + /** {@inheritDoc} */ + @Override + public BitSet compatibleRegs(DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + BitSet bits = new BitSet(1); + + bits.set(0, unsignedFitsInByte(regs.get(0).getReg())); + return bits; + } + + /** {@inheritDoc} */ + @Override + public void writeTo(AnnotatedOutput out, DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + int value = + ((CstLiteralBits) ((CstInsn) insn).getConstant()).getIntBits(); + + write(out, opcodeUnit(insn, regs.get(0).getReg()), value); + } +} diff --git a/dx/src/com/android/jack/dx/dex/code/form/Form31t.java b/dx/src/com/android/jack/dx/dex/code/form/Form31t.java new file mode 100644 index 00000000..8573615e --- /dev/null +++ b/dx/src/com/android/jack/dx/dex/code/form/Form31t.java @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.dex.code.form; + +import com.android.jack.dx.dex.code.DalvInsn; +import com.android.jack.dx.dex.code.InsnFormat; +import com.android.jack.dx.dex.code.TargetInsn; +import com.android.jack.dx.rop.code.RegisterSpecList; +import com.android.jack.dx.util.AnnotatedOutput; + +import java.util.BitSet; + +/** + * Instruction format {@code 31t}. See the instruction format spec + * for details. + */ +public final class Form31t extends InsnFormat { + /** {@code non-null;} unique instance of this class */ + public static final InsnFormat THE_ONE = new Form31t(); + + /** + * Constructs an instance. This class is not publicly + * instantiable. Use {@link #THE_ONE}. + */ + private Form31t() { + // This space intentionally left blank. + } + + /** {@inheritDoc} */ + @Override + public String insnArgString(DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + return regs.get(0).regString() + ", " + branchString(insn); + } + + /** {@inheritDoc} */ + @Override + public String insnCommentString(DalvInsn insn, boolean noteIndices) { + return branchComment(insn); + } + + /** {@inheritDoc} */ + @Override + public int codeSize() { + return 3; + } + + /** {@inheritDoc} */ + @Override + public boolean isCompatible(DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + + if (!((insn instanceof TargetInsn) && + (regs.size() == 1) && + unsignedFitsInByte(regs.get(0).getReg()))) { + return false; + } + + return true; + } + + /** {@inheritDoc} */ + @Override + public BitSet compatibleRegs(DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + BitSet bits = new BitSet(1); + + bits.set(0, unsignedFitsInByte(regs.get(0).getReg())); + return bits; + } + + /** {@inheritDoc} */ + @Override + public boolean branchFits(TargetInsn insn) { + return true; + } + + /** {@inheritDoc} */ + @Override + public void writeTo(AnnotatedOutput out, DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + int offset = ((TargetInsn) insn).getTargetOffset(); + + write(out, opcodeUnit(insn, regs.get(0).getReg()), offset); + } +} diff --git a/dx/src/com/android/jack/dx/dex/code/form/Form32x.java b/dx/src/com/android/jack/dx/dex/code/form/Form32x.java new file mode 100644 index 00000000..2f856ce7 --- /dev/null +++ b/dx/src/com/android/jack/dx/dex/code/form/Form32x.java @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.dex.code.form; + +import com.android.jack.dx.dex.code.DalvInsn; +import com.android.jack.dx.dex.code.InsnFormat; +import com.android.jack.dx.dex.code.SimpleInsn; +import com.android.jack.dx.rop.code.RegisterSpecList; +import com.android.jack.dx.util.AnnotatedOutput; + +import java.util.BitSet; + +/** + * Instruction format {@code 32x}. See the instruction format spec + * for details. + */ +public final class Form32x extends InsnFormat { + /** {@code non-null;} unique instance of this class */ + public static final InsnFormat THE_ONE = new Form32x(); + + /** + * Constructs an instance. This class is not publicly + * instantiable. Use {@link #THE_ONE}. + */ + private Form32x() { + // This space intentionally left blank. + } + + /** {@inheritDoc} */ + @Override + public String insnArgString(DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + return regs.get(0).regString() + ", " + regs.get(1).regString(); + } + + /** {@inheritDoc} */ + @Override + public String insnCommentString(DalvInsn insn, boolean noteIndices) { + // This format has no comment. + return ""; + } + + /** {@inheritDoc} */ + @Override + public int codeSize() { + return 3; + } + + /** {@inheritDoc} */ + @Override + public boolean isCompatible(DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + return (insn instanceof SimpleInsn) && + (regs.size() == 2) && + unsignedFitsInShort(regs.get(0).getReg()) && + unsignedFitsInShort(regs.get(1).getReg()); + } + + /** {@inheritDoc} */ + @Override + public BitSet compatibleRegs(DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + BitSet bits = new BitSet(2); + + bits.set(0, unsignedFitsInShort(regs.get(0).getReg())); + bits.set(1, unsignedFitsInShort(regs.get(1).getReg())); + return bits; + } + + /** {@inheritDoc} */ + @Override + public void writeTo(AnnotatedOutput out, DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + + write(out, + opcodeUnit(insn, 0), + (short) regs.get(0).getReg(), + (short) regs.get(1).getReg()); + } +} diff --git a/dx/src/com/android/jack/dx/dex/code/form/Form35c.java b/dx/src/com/android/jack/dx/dex/code/form/Form35c.java new file mode 100644 index 00000000..df277ef7 --- /dev/null +++ b/dx/src/com/android/jack/dx/dex/code/form/Form35c.java @@ -0,0 +1,211 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.dex.code.form; + +import com.android.jack.dx.dex.code.CstInsn; +import com.android.jack.dx.dex.code.DalvInsn; +import com.android.jack.dx.dex.code.InsnFormat; +import com.android.jack.dx.rop.code.RegisterSpec; +import com.android.jack.dx.rop.code.RegisterSpecList; +import com.android.jack.dx.rop.cst.Constant; +import com.android.jack.dx.rop.cst.CstMethodRef; +import com.android.jack.dx.rop.cst.CstType; +import com.android.jack.dx.rop.type.Type; +import com.android.jack.dx.util.AnnotatedOutput; + +import java.util.BitSet; + +/** + * Instruction format {@code 35c}. See the instruction format spec + * for details. + */ +public final class Form35c extends InsnFormat { + /** {@code non-null;} unique instance of this class */ + public static final InsnFormat THE_ONE = new Form35c(); + + /** Maximal number of operands */ + private static final int MAX_NUM_OPS = 5; + + /** + * Constructs an instance. This class is not publicly + * instantiable. Use {@link #THE_ONE}. + */ + private Form35c() { + // This space intentionally left blank. + } + + /** {@inheritDoc} */ + @Override + public String insnArgString(DalvInsn insn) { + RegisterSpecList regs = explicitize(insn.getRegisters()); + return regListString(regs) + ", " + cstString(insn); + } + + /** {@inheritDoc} */ + @Override + public String insnCommentString(DalvInsn insn, boolean noteIndices) { + if (noteIndices) { + return cstComment(insn); + } else { + return ""; + } + } + + /** {@inheritDoc} */ + @Override + public int codeSize() { + return 3; + } + + /** {@inheritDoc} */ + @Override + public boolean isCompatible(DalvInsn insn) { + if (!(insn instanceof CstInsn)) { + return false; + } + + CstInsn ci = (CstInsn) insn; + int cpi = ci.getIndex(); + + if (! unsignedFitsInShort(cpi)) { + return false; + } + + Constant cst = ci.getConstant(); + if (!((cst instanceof CstMethodRef) || + (cst instanceof CstType))) { + return false; + } + + RegisterSpecList regs = ci.getRegisters(); + return (wordCount(regs) >= 0); + } + + /** {@inheritDoc} */ + @Override + public BitSet compatibleRegs(DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + int sz = regs.size(); + BitSet bits = new BitSet(sz); + + for (int i = 0; i < sz; i++) { + RegisterSpec reg = regs.get(i); + /* + * The check below adds (category - 1) to the register, to + * account for the fact that the second half of a + * category-2 register has to be represented explicitly in + * the result. + */ + bits.set(i, unsignedFitsInNibble(reg.getReg() + + reg.getCategory() - 1)); + } + + return bits; + } + + /** {@inheritDoc} */ + @Override + public void writeTo(AnnotatedOutput out, DalvInsn insn) { + int cpi = ((CstInsn) insn).getIndex(); + RegisterSpecList regs = explicitize(insn.getRegisters()); + int sz = regs.size(); + int r0 = (sz > 0) ? regs.get(0).getReg() : 0; + int r1 = (sz > 1) ? regs.get(1).getReg() : 0; + int r2 = (sz > 2) ? regs.get(2).getReg() : 0; + int r3 = (sz > 3) ? regs.get(3).getReg() : 0; + int r4 = (sz > 4) ? regs.get(4).getReg() : 0; + + write(out, + opcodeUnit(insn, + makeByte(r4, sz)), // encode the fifth operand here + (short) cpi, + codeUnit(r0, r1, r2, r3)); + } + + /** + * Gets the number of words required for the given register list, where + * category-2 values count as two words. Return {@code -1} if the + * list requires more than five words or contains registers that need + * more than a nibble to identify them. + * + * @param regs {@code non-null;} the register list in question + * @return {@code >= -1;} the number of words required, or {@code -1} + * if the list couldn't possibly fit in this format + */ + private static int wordCount(RegisterSpecList regs) { + int sz = regs.size(); + + if (sz > MAX_NUM_OPS) { + // It can't possibly fit. + return -1; + } + + int result = 0; + + for (int i = 0; i < sz; i++) { + RegisterSpec one = regs.get(i); + result += one.getCategory(); + /* + * The check below adds (category - 1) to the register, to + * account for the fact that the second half of a + * category-2 register has to be represented explicitly in + * the result. + */ + if (!unsignedFitsInNibble(one.getReg() + one.getCategory() - 1)) { + return -1; + } + } + + return (result <= MAX_NUM_OPS) ? result : -1; + } + + /** + * Returns a register list which is equivalent to the given one, + * except that it splits category-2 registers into two explicit + * entries. This returns the original list if no modification is + * required + * + * @param orig {@code non-null;} the original list + * @return {@code non-null;} the list with the described transformation + */ + private static RegisterSpecList explicitize(RegisterSpecList orig) { + int wordCount = wordCount(orig); + int sz = orig.size(); + + if (wordCount == sz) { + return orig; + } + + RegisterSpecList result = new RegisterSpecList(wordCount); + int wordAt = 0; + + for (int i = 0; i < sz; i++) { + RegisterSpec one = orig.get(i); + result.set(wordAt, one); + if (one.getCategory() == 2) { + result.set(wordAt + 1, + RegisterSpec.make(one.getReg() + 1, Type.VOID)); + wordAt += 2; + } else { + wordAt++; + } + } + + result.setImmutable(); + return result; + } +} diff --git a/dx/src/com/android/jack/dx/dex/code/form/Form3rc.java b/dx/src/com/android/jack/dx/dex/code/form/Form3rc.java new file mode 100644 index 00000000..35adc7a7 --- /dev/null +++ b/dx/src/com/android/jack/dx/dex/code/form/Form3rc.java @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.dex.code.form; + +import com.android.jack.dx.dex.code.CstInsn; +import com.android.jack.dx.dex.code.DalvInsn; +import com.android.jack.dx.dex.code.InsnFormat; +import com.android.jack.dx.rop.code.RegisterSpecList; +import com.android.jack.dx.rop.cst.Constant; +import com.android.jack.dx.rop.cst.CstMethodRef; +import com.android.jack.dx.rop.cst.CstType; +import com.android.jack.dx.util.AnnotatedOutput; + +/** + * Instruction format {@code 3rc}. See the instruction format spec + * for details. + */ +public final class Form3rc extends InsnFormat { + /** {@code non-null;} unique instance of this class */ + public static final InsnFormat THE_ONE = new Form3rc(); + + /** + * Constructs an instance. This class is not publicly + * instantiable. Use {@link #THE_ONE}. + */ + private Form3rc() { + // This space intentionally left blank. + } + + /** {@inheritDoc} */ + @Override + public String insnArgString(DalvInsn insn) { + return regRangeString(insn.getRegisters()) + ", " + + cstString(insn); + } + + /** {@inheritDoc} */ + @Override + public String insnCommentString(DalvInsn insn, boolean noteIndices) { + if (noteIndices) { + return cstComment(insn); + } else { + return ""; + } + } + + /** {@inheritDoc} */ + @Override + public int codeSize() { + return 3; + } + + /** {@inheritDoc} */ + @Override + public boolean isCompatible(DalvInsn insn) { + if (!(insn instanceof CstInsn)) { + return false; + } + + CstInsn ci = (CstInsn) insn; + int cpi = ci.getIndex(); + Constant cst = ci.getConstant(); + + if (! unsignedFitsInShort(cpi)) { + return false; + } + + if (!((cst instanceof CstMethodRef) || + (cst instanceof CstType))) { + return false; + } + + RegisterSpecList regs = ci.getRegisters(); + int sz = regs.size(); + + return (regs.size() == 0) || + (isRegListSequential(regs) && + unsignedFitsInShort(regs.get(0).getReg()) && + unsignedFitsInByte(regs.getWordCount())); + } + + /** {@inheritDoc} */ + @Override + public void writeTo(AnnotatedOutput out, DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + int cpi = ((CstInsn) insn).getIndex(); + int firstReg = (regs.size() == 0) ? 0 : regs.get(0).getReg(); + int count = regs.getWordCount(); + + write(out, opcodeUnit(insn, count), (short) cpi, (short) firstReg); + } +} diff --git a/dx/src/com/android/jack/dx/dex/code/form/Form51l.java b/dx/src/com/android/jack/dx/dex/code/form/Form51l.java new file mode 100644 index 00000000..9dd086fa --- /dev/null +++ b/dx/src/com/android/jack/dx/dex/code/form/Form51l.java @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.dex.code.form; + +import com.android.jack.dx.dex.code.CstInsn; +import com.android.jack.dx.dex.code.DalvInsn; +import com.android.jack.dx.dex.code.InsnFormat; +import com.android.jack.dx.rop.code.RegisterSpecList; +import com.android.jack.dx.rop.cst.Constant; +import com.android.jack.dx.rop.cst.CstLiteral64; +import com.android.jack.dx.rop.cst.CstLiteralBits; +import com.android.jack.dx.util.AnnotatedOutput; + +import java.util.BitSet; + +/** + * Instruction format {@code 51l}. See the instruction format spec + * for details. + */ +public final class Form51l extends InsnFormat { + /** {@code non-null;} unique instance of this class */ + public static final InsnFormat THE_ONE = new Form51l(); + + /** + * Constructs an instance. This class is not publicly + * instantiable. Use {@link #THE_ONE}. + */ + private Form51l() { + // This space intentionally left blank. + } + + /** {@inheritDoc} */ + @Override + public String insnArgString(DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + CstLiteralBits value = (CstLiteralBits) ((CstInsn) insn).getConstant(); + + return regs.get(0).regString() + ", " + literalBitsString(value); + } + + /** {@inheritDoc} */ + @Override + public String insnCommentString(DalvInsn insn, boolean noteIndices) { + CstLiteralBits value = (CstLiteralBits) ((CstInsn) insn).getConstant(); + return literalBitsComment(value, 64); + } + + /** {@inheritDoc} */ + @Override + public int codeSize() { + return 5; + } + + /** {@inheritDoc} */ + @Override + public boolean isCompatible(DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + if (!((insn instanceof CstInsn) && + (regs.size() == 1) && + unsignedFitsInByte(regs.get(0).getReg()))) { + return false; + } + + CstInsn ci = (CstInsn) insn; + Constant cst = ci.getConstant(); + + return (cst instanceof CstLiteral64); + } + + /** {@inheritDoc} */ + @Override + public BitSet compatibleRegs(DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + BitSet bits = new BitSet(1); + + bits.set(0, unsignedFitsInByte(regs.get(0).getReg())); + return bits; + } + + /** {@inheritDoc} */ + @Override + public void writeTo(AnnotatedOutput out, DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + long value = + ((CstLiteral64) ((CstInsn) insn).getConstant()).getLongBits(); + + write(out, opcodeUnit(insn, regs.get(0).getReg()), value); + } +} diff --git a/dx/src/com/android/jack/dx/dex/code/form/SpecialFormat.java b/dx/src/com/android/jack/dx/dex/code/form/SpecialFormat.java new file mode 100644 index 00000000..bce8a678 --- /dev/null +++ b/dx/src/com/android/jack/dx/dex/code/form/SpecialFormat.java @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.dex.code.form; + +import com.android.jack.dx.dex.code.DalvInsn; +import com.android.jack.dx.dex.code.InsnFormat; +import com.android.jack.dx.util.AnnotatedOutput; + +/** + * Instruction format for nonstandard format instructions, which aren't + * generally real instructions but do end up appearing in instruction + * lists. Most of the overridden methods on this class end up throwing + * exceptions, as code should know (implicitly or explicitly) to avoid + * using this class. The one exception is {@link #isCompatible}, which + * always returns {@code true}. + */ +public final class SpecialFormat extends InsnFormat { + /** {@code non-null;} unique instance of this class */ + public static final InsnFormat THE_ONE = new SpecialFormat(); + + /** + * Constructs an instance. This class is not publicly + * instantiable. Use {@link #THE_ONE}. + */ + private SpecialFormat() { + // This space intentionally left blank. + } + + /** {@inheritDoc} */ + @Override + public String insnArgString(DalvInsn insn) { + throw new RuntimeException("unsupported"); + } + + /** {@inheritDoc} */ + @Override + public String insnCommentString(DalvInsn insn, boolean noteIndices) { + throw new RuntimeException("unsupported"); + } + + /** {@inheritDoc} */ + @Override + public int codeSize() { + throw new RuntimeException("unsupported"); + } + + /** {@inheritDoc} */ + @Override + public boolean isCompatible(DalvInsn insn) { + return true; + } + + /** {@inheritDoc} */ + @Override + public void writeTo(AnnotatedOutput out, DalvInsn insn) { + throw new RuntimeException("unsupported"); + } +} diff --git a/dx/src/com/android/jack/dx/dex/file/AnnotationItem.java b/dx/src/com/android/jack/dx/dex/file/AnnotationItem.java new file mode 100644 index 00000000..c48c3d84 --- /dev/null +++ b/dx/src/com/android/jack/dx/dex/file/AnnotationItem.java @@ -0,0 +1,218 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.dex.file; + +import com.android.jack.dx.rop.annotation.Annotation; +import com.android.jack.dx.rop.annotation.AnnotationVisibility; +import com.android.jack.dx.rop.annotation.NameValuePair; +import com.android.jack.dx.rop.cst.Constant; +import com.android.jack.dx.rop.cst.CstString; +import com.android.jack.dx.util.AnnotatedOutput; +import com.android.jack.dx.util.ByteArrayAnnotatedOutput; + +import java.util.Arrays; +import java.util.Comparator; + +/** + * Single annotation, which consists of a type and a set of name-value + * element pairs. + */ +public final class AnnotationItem extends OffsettedItem { + /** annotation visibility constant: visible at build time only */ + private static final int VISIBILITY_BUILD = 0; + + /** annotation visibility constant: visible at runtime */ + private static final int VISIBILITY_RUNTIME = 1; + + /** annotation visibility constant: visible at runtime only to system */ + private static final int VISIBILITY_SYSTEM = 2; + + /** the required alignment for instances of this class */ + private static final int ALIGNMENT = 1; + + /** {@code non-null;} unique instance of {@link #TypeIdSorter} */ + private static final TypeIdSorter TYPE_ID_SORTER = new TypeIdSorter(); + + /** {@code non-null;} the annotation to represent */ + private final Annotation annotation; + + /** + * {@code null-ok;} type reference for the annotation type; set during + * {@link #addContents} + */ + private TypeIdItem type; + + /** + * {@code null-ok;} encoded form, ready for writing to a file; set during + * {@link #place0} + */ + private byte[] encodedForm; + + /** + * Comparator that sorts (outer) instances by type id index. + */ + private static class TypeIdSorter implements Comparator { + /** {@inheritDoc} */ + public int compare(AnnotationItem item1, AnnotationItem item2) { + int index1 = item1.type.getIndex(); + int index2 = item2.type.getIndex(); + + if (index1 < index2) { + return -1; + } else if (index1 > index2) { + return 1; + } + + return 0; + } + } + + /** + * Sorts an array of instances, in place, by type id index, + * ignoring all other aspects of the elements. This is only valid + * to use after type id indices are known. + * + * @param array {@code non-null;} array to sort + */ + public static void sortByTypeIdIndex(AnnotationItem[] array) { + Arrays.sort(array, TYPE_ID_SORTER); + } + + /** + * Constructs an instance. + * + * @param annotation {@code non-null;} annotation to represent + */ + public AnnotationItem(Annotation annotation) { + /* + * The write size isn't known up-front because (the variable-lengthed) + * leb128 type is used to represent some things. + */ + super(ALIGNMENT, -1); + + if (annotation == null) { + throw new NullPointerException("annotation == null"); + } + + this.annotation = annotation; + this.type = null; + this.encodedForm = null; + } + + /** {@inheritDoc} */ + @Override + public ItemType itemType() { + return ItemType.TYPE_ANNOTATION_ITEM; + } + + /** {@inheritDoc} */ + @Override + public int hashCode() { + return annotation.hashCode(); + } + + /** {@inheritDoc} */ + @Override + protected int compareTo0(OffsettedItem other) { + AnnotationItem otherAnnotation = (AnnotationItem) other; + + return annotation.compareTo(otherAnnotation.annotation); + } + + /** {@inheritDoc} */ + @Override + public String toHuman() { + return annotation.toHuman(); + } + + /** {@inheritDoc} */ + public void addContents(DexFile file) { + type = file.getTypeIds().intern(annotation.getType()); + ValueEncoder.addContents(file, annotation); + } + + /** {@inheritDoc} */ + @Override + protected void place0(Section addedTo, int offset) { + // Encode the data and note the size. + + ByteArrayAnnotatedOutput out = new ByteArrayAnnotatedOutput(); + ValueEncoder encoder = new ValueEncoder(addedTo.getFile(), out); + + encoder.writeAnnotation(annotation, false); + encodedForm = out.toByteArray(); + + // Add one for the visibility byte in front of the encoded annotation. + setWriteSize(encodedForm.length + 1); + } + + /** + * Write a (listing file) annotation for this instance to the given + * output, that consumes no bytes of output. This is for annotating + * a reference to this instance at the point of the reference. + * + * @param out {@code non-null;} where to output to + * @param prefix {@code non-null;} prefix for each line of output + */ + public void annotateTo(AnnotatedOutput out, String prefix) { + out.annotate(0, prefix + "visibility: " + + annotation.getVisibility().toHuman()); + out.annotate(0, prefix + "type: " + annotation.getType().toHuman()); + + for (NameValuePair pair : annotation.getNameValuePairs()) { + CstString name = pair.getName(); + Constant value = pair.getValue(); + + out.annotate(0, prefix + name.toHuman() + ": " + + ValueEncoder.constantToHuman(value)); + } + } + + /** {@inheritDoc} */ + @Override + protected void writeTo0(DexFile file, AnnotatedOutput out) { + boolean annotates = out.annotates(); + AnnotationVisibility visibility = annotation.getVisibility(); + + if (annotates) { + out.annotate(0, offsetString() + " annotation"); + out.annotate(1, " visibility: VISBILITY_" + visibility); + } + + switch (visibility) { + case BUILD: out.writeByte(VISIBILITY_BUILD); break; + case RUNTIME: out.writeByte(VISIBILITY_RUNTIME); break; + case SYSTEM: out.writeByte(VISIBILITY_SYSTEM); break; + default: { + // EMBEDDED shouldn't appear at the top level. + throw new RuntimeException("shouldn't happen"); + } + } + + if (annotates) { + /* + * The output is to be annotated, so redo the work previously + * done by place0(), except this time annotations will actually + * get emitted. + */ + ValueEncoder encoder = new ValueEncoder(file, out); + encoder.writeAnnotation(annotation, true); + } else { + out.write(encodedForm); + } + } +} diff --git a/dx/src/com/android/jack/dx/dex/file/AnnotationSetItem.java b/dx/src/com/android/jack/dx/dex/file/AnnotationSetItem.java new file mode 100644 index 00000000..9672023e --- /dev/null +++ b/dx/src/com/android/jack/dx/dex/file/AnnotationSetItem.java @@ -0,0 +1,157 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.dex.file; + +import com.android.jack.dx.rop.annotation.Annotation; +import com.android.jack.dx.rop.annotation.Annotations; +import com.android.jack.dx.util.AnnotatedOutput; +import com.android.jack.dx.util.Hex; + +/** + * Set of annotations, where no annotation type appears more than once. + */ +public final class AnnotationSetItem extends OffsettedItem { + /** the required alignment for instances of this class */ + private static final int ALIGNMENT = 4; + + /** the size of an entry int the set: one {@code uint} */ + private static final int ENTRY_WRITE_SIZE = 4; + + /** {@code non-null;} the set of annotations */ + private final Annotations annotations; + + /** + * {@code non-null;} set of annotations as individual items in an array. + * Note: The contents have to get sorted by type id before + * writing. + */ + private final AnnotationItem[] items; + + /** + * Constructs an instance. + * + * @param annotations {@code non-null;} set of annotations + */ + public AnnotationSetItem(Annotations annotations) { + super(ALIGNMENT, writeSize(annotations)); + + this.annotations = annotations; + this.items = new AnnotationItem[annotations.size()]; + + int at = 0; + for (Annotation a : annotations.getAnnotations()) { + items[at] = new AnnotationItem(a); + at++; + } + } + + /** + * Gets the write size for the given set. + * + * @param annotations {@code non-null;} the set + * @return {@code > 0;} the write size + */ + private static int writeSize(Annotations annotations) { + // This includes an int size at the start of the list. + + try { + return (annotations.size() * ENTRY_WRITE_SIZE) + 4; + } catch (NullPointerException ex) { + // Elucidate the exception. + throw new NullPointerException("list == null"); + } + } + + /** + * Gets the underlying annotations of this instance + * + * @return {@code non-null;} the annotations + */ + public Annotations getAnnotations() { + return annotations; + } + + /** {@inheritDoc} */ + @Override + public int hashCode() { + return annotations.hashCode(); + } + + /** {@inheritDoc} */ + @Override + protected int compareTo0(OffsettedItem other) { + AnnotationSetItem otherSet = (AnnotationSetItem) other; + + return annotations.compareTo(otherSet.annotations); + } + + /** {@inheritDoc} */ + @Override + public ItemType itemType() { + return ItemType.TYPE_ANNOTATION_SET_ITEM; + } + + /** {@inheritDoc} */ + @Override + public String toHuman() { + return annotations.toString(); + } + + /** {@inheritDoc} */ + public void addContents(DexFile file) { + MixedItemSection byteData = file.getByteData(); + int size = items.length; + + for (int i = 0; i < size; i++) { + items[i] = byteData.intern(items[i]); + } + } + + /** {@inheritDoc} */ + @Override + protected void place0(Section addedTo, int offset) { + // Sort the array to be in type id index order. + AnnotationItem.sortByTypeIdIndex(items); + } + + /** {@inheritDoc} */ + @Override + protected void writeTo0(DexFile file, AnnotatedOutput out) { + boolean annotates = out.annotates(); + int size = items.length; + + if (annotates) { + out.annotate(0, offsetString() + " annotation set"); + out.annotate(4, " size: " + Hex.u4(size)); + } + + out.writeInt(size); + + for (int i = 0; i < size; i++) { + AnnotationItem item = items[i]; + int offset = item.getAbsoluteOffset(); + + if (annotates) { + out.annotate(4, " entries[" + Integer.toHexString(i) + "]: " + + Hex.u4(offset)); + items[i].annotateTo(out, " "); + } + + out.writeInt(offset); + } + } +} diff --git a/dx/src/com/android/jack/dx/dex/file/AnnotationSetRefItem.java b/dx/src/com/android/jack/dx/dex/file/AnnotationSetRefItem.java new file mode 100644 index 00000000..a78dafc2 --- /dev/null +++ b/dx/src/com/android/jack/dx/dex/file/AnnotationSetRefItem.java @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.dex.file; + +import com.android.jack.dx.util.AnnotatedOutput; +import com.android.jack.dx.util.Hex; + +/** + * Indirect reference to an {@link AnnotationSetItem}. + */ +public final class AnnotationSetRefItem extends OffsettedItem { + /** the required alignment for instances of this class */ + private static final int ALIGNMENT = 4; + + /** write size of this class, in bytes */ + private static final int WRITE_SIZE = 4; + + /** {@code non-null;} the annotation set to refer to */ + private AnnotationSetItem annotations; + + /** + * Constructs an instance. + * + * @param annotations {@code non-null;} the annotation set to refer to + */ + public AnnotationSetRefItem(AnnotationSetItem annotations) { + super(ALIGNMENT, WRITE_SIZE); + + if (annotations == null) { + throw new NullPointerException("annotations == null"); + } + + this.annotations = annotations; + } + + /** {@inheritDoc} */ + @Override + public ItemType itemType() { + return ItemType.TYPE_ANNOTATION_SET_REF_ITEM; + } + + /** {@inheritDoc} */ + public void addContents(DexFile file) { + MixedItemSection wordData = file.getWordData(); + + annotations = wordData.intern(annotations); + } + + /** {@inheritDoc} */ + @Override + public String toHuman() { + return annotations.toHuman(); + } + + /** {@inheritDoc} */ + @Override + protected void writeTo0(DexFile file, AnnotatedOutput out) { + int annotationsOff = annotations.getAbsoluteOffset(); + + if (out.annotates()) { + out.annotate(4, " annotations_off: " + Hex.u4(annotationsOff)); + } + + out.writeInt(annotationsOff); + } +} diff --git a/dx/src/com/android/jack/dx/dex/file/AnnotationUtils.java b/dx/src/com/android/jack/dx/dex/file/AnnotationUtils.java new file mode 100644 index 00000000..868f82b0 --- /dev/null +++ b/dx/src/com/android/jack/dx/dex/file/AnnotationUtils.java @@ -0,0 +1,252 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.dex.file; + +import com.android.jack.dx.rop.annotation.Annotation; +import com.android.jack.dx.rop.annotation.NameValuePair; +import com.android.jack.dx.rop.cst.Constant; +import com.android.jack.dx.rop.cst.CstAnnotation; +import com.android.jack.dx.rop.cst.CstArray; +import com.android.jack.dx.rop.cst.CstInteger; +import com.android.jack.dx.rop.cst.CstKnownNull; +import com.android.jack.dx.rop.cst.CstMethodRef; +import com.android.jack.dx.rop.cst.CstString; +import com.android.jack.dx.rop.cst.CstType; +import com.android.jack.dx.rop.type.Type; +import com.android.jack.dx.rop.type.TypeList; + +import java.util.ArrayList; + +import static com.android.jack.dx.rop.annotation.AnnotationVisibility.*; + +/** + * Utility class for dealing with annotations. + */ +public final class AnnotationUtils { + /** {@code non-null;} type for {@code AnnotationDefault} annotations */ + private static final CstType ANNOTATION_DEFAULT_TYPE = + CstType.intern(Type.intern("Ldalvik/annotation/AnnotationDefault;")); + + /** {@code non-null;} type for {@code EnclosingClass} annotations */ + private static final CstType ENCLOSING_CLASS_TYPE = + CstType.intern(Type.intern("Ldalvik/annotation/EnclosingClass;")); + + /** {@code non-null;} type for {@code EnclosingMethod} annotations */ + private static final CstType ENCLOSING_METHOD_TYPE = + CstType.intern(Type.intern("Ldalvik/annotation/EnclosingMethod;")); + + /** {@code non-null;} type for {@code InnerClass} annotations */ + private static final CstType INNER_CLASS_TYPE = + CstType.intern(Type.intern("Ldalvik/annotation/InnerClass;")); + + /** {@code non-null;} type for {@code MemberClasses} annotations */ + private static final CstType MEMBER_CLASSES_TYPE = + CstType.intern(Type.intern("Ldalvik/annotation/MemberClasses;")); + + /** {@code non-null;} type for {@code Signature} annotations */ + private static final CstType SIGNATURE_TYPE = + CstType.intern(Type.intern("Ldalvik/annotation/Signature;")); + + /** {@code non-null;} type for {@code Throws} annotations */ + private static final CstType THROWS_TYPE = + CstType.intern(Type.intern("Ldalvik/annotation/Throws;")); + + /** {@code non-null;} the UTF-8 constant {@code "accessFlags"} */ + private static final CstString ACCESS_FLAGS_STRING = new CstString("accessFlags"); + + /** {@code non-null;} the UTF-8 constant {@code "name"} */ + private static final CstString NAME_STRING = new CstString("name"); + + /** {@code non-null;} the UTF-8 constant {@code "value"} */ + private static final CstString VALUE_STRING = new CstString("value"); + + /** + * This class is uninstantiable. + */ + private AnnotationUtils() { + // This space intentionally left blank. + } + + /** + * Constructs a standard {@code AnnotationDefault} annotation. + * + * @param defaults {@code non-null;} the defaults, itself as an annotation + * @return {@code non-null;} the constructed annotation + */ + public static Annotation makeAnnotationDefault(Annotation defaults) { + Annotation result = new Annotation(ANNOTATION_DEFAULT_TYPE, SYSTEM); + + result.put(new NameValuePair(VALUE_STRING, new CstAnnotation(defaults))); + result.setImmutable(); + return result; + } + + /** + * Constructs a standard {@code EnclosingClass} annotation. + * + * @param clazz {@code non-null;} the enclosing class + * @return {@code non-null;} the annotation + */ + public static Annotation makeEnclosingClass(CstType clazz) { + Annotation result = new Annotation(ENCLOSING_CLASS_TYPE, SYSTEM); + + result.put(new NameValuePair(VALUE_STRING, clazz)); + result.setImmutable(); + return result; + } + + /** + * Constructs a standard {@code EnclosingMethod} annotation. + * + * @param method {@code non-null;} the enclosing method + * @return {@code non-null;} the annotation + */ + public static Annotation makeEnclosingMethod(CstMethodRef method) { + Annotation result = new Annotation(ENCLOSING_METHOD_TYPE, SYSTEM); + + result.put(new NameValuePair(VALUE_STRING, method)); + result.setImmutable(); + return result; + } + + /** + * Constructs a standard {@code InnerClass} annotation. + * + * @param name {@code null-ok;} the original name of the class, or + * {@code null} to represent an anonymous class + * @param accessFlags the original access flags + * @return {@code non-null;} the annotation + */ + public static Annotation makeInnerClass(CstString name, int accessFlags) { + Annotation result = new Annotation(INNER_CLASS_TYPE, SYSTEM); + Constant nameCst = (name != null) ? name : CstKnownNull.THE_ONE; + + result.put(new NameValuePair(NAME_STRING, nameCst)); + result.put(new NameValuePair(ACCESS_FLAGS_STRING, + CstInteger.make(accessFlags))); + result.setImmutable(); + return result; + } + + /** + * Constructs a standard {@code MemberClasses} annotation. + * + * @param types {@code non-null;} the list of (the types of) the member classes + * @return {@code non-null;} the annotation + */ + public static Annotation makeMemberClasses(TypeList types) { + CstArray array = makeCstArray(types); + Annotation result = new Annotation(MEMBER_CLASSES_TYPE, SYSTEM); + result.put(new NameValuePair(VALUE_STRING, array)); + result.setImmutable(); + return result; + } + + /** + * Constructs a standard {@code Signature} annotation. + * + * @param signature {@code non-null;} the signature string + * @return {@code non-null;} the annotation + */ + public static Annotation makeSignature(CstString signature) { + Annotation result = new Annotation(SIGNATURE_TYPE, SYSTEM); + + /* + * Split the string into pieces that are likely to be common + * across many signatures and the rest of the file. + */ + + String raw = signature.getString(); + int rawLength = raw.length(); + ArrayList pieces = new ArrayList(20); + + for (int at = 0; at < rawLength; /*at*/) { + char c = raw.charAt(at); + int endAt = at + 1; + if (c == 'L') { + // Scan to ';' or '<'. Consume ';' but not '<'. + while (endAt < rawLength) { + c = raw.charAt(endAt); + if (c == ';') { + endAt++; + break; + } else if (c == '<') { + break; + } + endAt++; + } + } else { + // Scan to 'L' without consuming it. + while (endAt < rawLength) { + c = raw.charAt(endAt); + if (c == 'L') { + break; + } + endAt++; + } + } + + pieces.add(raw.substring(at, endAt)); + at = endAt; + } + + int size = pieces.size(); + CstArray.List list = new CstArray.List(size); + + for (int i = 0; i < size; i++) { + list.set(i, new CstString(pieces.get(i))); + } + + list.setImmutable(); + + result.put(new NameValuePair(VALUE_STRING, new CstArray(list))); + result.setImmutable(); + return result; + } + + /** + * Constructs a standard {@code Throws} annotation. + * + * @param types {@code non-null;} the list of thrown types + * @return {@code non-null;} the annotation + */ + public static Annotation makeThrows(TypeList types) { + CstArray array = makeCstArray(types); + Annotation result = new Annotation(THROWS_TYPE, SYSTEM); + result.put(new NameValuePair(VALUE_STRING, array)); + result.setImmutable(); + return result; + } + + /** + * Converts a {@link TypeList} to a {@link CstArray}. + * + * @param types {@code non-null;} the type list + * @return {@code non-null;} the corresponding array constant + */ + private static CstArray makeCstArray(TypeList types) { + int size = types.size(); + CstArray.List list = new CstArray.List(size); + + for (int i = 0; i < size; i++) { + list.set(i, CstType.intern(types.getType(i))); + } + + list.setImmutable(); + return new CstArray(list); + } +} diff --git a/dx/src/com/android/jack/dx/dex/file/AnnotationsDirectoryItem.java b/dx/src/com/android/jack/dx/dex/file/AnnotationsDirectoryItem.java new file mode 100644 index 00000000..acde7fa9 --- /dev/null +++ b/dx/src/com/android/jack/dx/dex/file/AnnotationsDirectoryItem.java @@ -0,0 +1,385 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.dex.file; + +import com.android.jack.dx.rop.annotation.Annotations; +import com.android.jack.dx.rop.annotation.AnnotationsList; +import com.android.jack.dx.rop.cst.CstFieldRef; +import com.android.jack.dx.rop.cst.CstMethodRef; +import com.android.jack.dx.util.AnnotatedOutput; +import com.android.jack.dx.util.Hex; + +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.Collections; + +/** + * Per-class directory of annotations. + */ +public final class AnnotationsDirectoryItem extends OffsettedItem { + /** the required alignment for instances of this class */ + private static final int ALIGNMENT = 4; + + /** write size of this class's header, in bytes */ + private static final int HEADER_SIZE = 16; + + /** write size of a list element, in bytes */ + private static final int ELEMENT_SIZE = 8; + + /** {@code null-ok;} the class-level annotations, if any */ + private AnnotationSetItem classAnnotations; + + /** {@code null-ok;} the annotated fields, if any */ + private ArrayList fieldAnnotations; + + /** {@code null-ok;} the annotated methods, if any */ + private ArrayList methodAnnotations; + + /** {@code null-ok;} the annotated parameters, if any */ + private ArrayList parameterAnnotations; + + /** + * Constructs an empty instance. + */ + public AnnotationsDirectoryItem() { + super(ALIGNMENT, -1); + + classAnnotations = null; + fieldAnnotations = null; + methodAnnotations = null; + parameterAnnotations = null; + } + + /** {@inheritDoc} */ + @Override + public ItemType itemType() { + return ItemType.TYPE_ANNOTATIONS_DIRECTORY_ITEM; + } + + /** + * Returns whether this item is empty (has no contents). + * + * @return {@code true} if this item is empty, or {@code false} + * if not + */ + public boolean isEmpty() { + return (classAnnotations == null) && + (fieldAnnotations == null) && + (methodAnnotations == null) && + (parameterAnnotations == null); + } + + /** + * Returns whether this item is a candidate for interning. The only + * interning candidates are ones that only have a non-null + * set of class annotations, with no other lists. + * + * @return {@code true} if this is an interning candidate, or + * {@code false} if not + */ + public boolean isInternable() { + return (classAnnotations != null) && + (fieldAnnotations == null) && + (methodAnnotations == null) && + (parameterAnnotations == null); + } + + /** {@inheritDoc} */ + @Override + public int hashCode() { + if (classAnnotations == null) { + return 0; + } + + return classAnnotations.hashCode(); + } + + /** + * {@inheritDoc} + * + *

Note:: This throws an exception if this item is not + * internable.

+ * + * @see #isInternable + */ + @Override + public int compareTo0(OffsettedItem other) { + if (! isInternable()) { + throw new UnsupportedOperationException("uninternable instance"); + } + + AnnotationsDirectoryItem otherDirectory = + (AnnotationsDirectoryItem) other; + return classAnnotations.compareTo(otherDirectory.classAnnotations); + } + + /** + * Sets the direct annotations on this instance. These are annotations + * made on the class, per se, as opposed to on one of its members. + * It is only valid to call this method at most once per instance. + * + * @param annotations {@code non-null;} annotations to set for this class + */ + public void setClassAnnotations(Annotations annotations) { + if (annotations == null) { + throw new NullPointerException("annotations == null"); + } + + if (classAnnotations != null) { + throw new UnsupportedOperationException( + "class annotations already set"); + } + + classAnnotations = new AnnotationSetItem(annotations); + } + + /** + * Adds a field annotations item to this instance. + * + * @param field {@code non-null;} field in question + * @param annotations {@code non-null;} associated annotations to add + */ + public void addFieldAnnotations(CstFieldRef field, + Annotations annotations) { + if (fieldAnnotations == null) { + fieldAnnotations = new ArrayList(); + } + + fieldAnnotations.add(new FieldAnnotationStruct(field, + new AnnotationSetItem(annotations))); + } + + /** + * Adds a method annotations item to this instance. + * + * @param method {@code non-null;} method in question + * @param annotations {@code non-null;} associated annotations to add + */ + public void addMethodAnnotations(CstMethodRef method, + Annotations annotations) { + if (methodAnnotations == null) { + methodAnnotations = new ArrayList(); + } + + methodAnnotations.add(new MethodAnnotationStruct(method, + new AnnotationSetItem(annotations))); + } + + /** + * Adds a parameter annotations item to this instance. + * + * @param method {@code non-null;} method in question + * @param list {@code non-null;} associated list of annotation sets to add + */ + public void addParameterAnnotations(CstMethodRef method, + AnnotationsList list) { + if (parameterAnnotations == null) { + parameterAnnotations = new ArrayList(); + } + + parameterAnnotations.add(new ParameterAnnotationStruct(method, list)); + } + + /** + * Gets the method annotations for a given method, if any. This is + * meant for use by debugging / dumping code. + * + * @param method {@code non-null;} the method + * @return {@code null-ok;} the method annotations, if any + */ + public Annotations getMethodAnnotations(CstMethodRef method) { + if (methodAnnotations == null) { + return null; + } + + for (MethodAnnotationStruct item : methodAnnotations) { + if (item.getMethod().equals(method)) { + return item.getAnnotations(); + } + } + + return null; + } + + /** + * Gets the parameter annotations for a given method, if any. This is + * meant for use by debugging / dumping code. + * + * @param method {@code non-null;} the method + * @return {@code null-ok;} the parameter annotations, if any + */ + public AnnotationsList getParameterAnnotations(CstMethodRef method) { + if (parameterAnnotations == null) { + return null; + } + + for (ParameterAnnotationStruct item : parameterAnnotations) { + if (item.getMethod().equals(method)) { + return item.getAnnotationsList(); + } + } + + return null; + } + + /** {@inheritDoc} */ + public void addContents(DexFile file) { + MixedItemSection wordData = file.getWordData(); + + if (classAnnotations != null) { + classAnnotations = wordData.intern(classAnnotations); + } + + if (fieldAnnotations != null) { + for (FieldAnnotationStruct item : fieldAnnotations) { + item.addContents(file); + } + } + + if (methodAnnotations != null) { + for (MethodAnnotationStruct item : methodAnnotations) { + item.addContents(file); + } + } + + if (parameterAnnotations != null) { + for (ParameterAnnotationStruct item : parameterAnnotations) { + item.addContents(file); + } + } + } + + /** {@inheritDoc} */ + @Override + public String toHuman() { + throw new RuntimeException("unsupported"); + } + + /** {@inheritDoc} */ + @Override + protected void place0(Section addedTo, int offset) { + // We just need to set the write size here. + + int elementCount = listSize(fieldAnnotations) + + listSize(methodAnnotations) + listSize(parameterAnnotations); + setWriteSize(HEADER_SIZE + (elementCount * ELEMENT_SIZE)); + } + + /** {@inheritDoc} */ + @Override + protected void writeTo0(DexFile file, AnnotatedOutput out) { + boolean annotates = out.annotates(); + int classOff = OffsettedItem.getAbsoluteOffsetOr0(classAnnotations); + int fieldsSize = listSize(fieldAnnotations); + int methodsSize = listSize(methodAnnotations); + int parametersSize = listSize(parameterAnnotations); + + if (annotates) { + out.annotate(0, offsetString() + " annotations directory"); + out.annotate(4, " class_annotations_off: " + Hex.u4(classOff)); + out.annotate(4, " fields_size: " + + Hex.u4(fieldsSize)); + out.annotate(4, " methods_size: " + + Hex.u4(methodsSize)); + out.annotate(4, " parameters_size: " + + Hex.u4(parametersSize)); + } + + out.writeInt(classOff); + out.writeInt(fieldsSize); + out.writeInt(methodsSize); + out.writeInt(parametersSize); + + if (fieldsSize != 0) { + Collections.sort(fieldAnnotations); + if (annotates) { + out.annotate(0, " fields:"); + } + for (FieldAnnotationStruct item : fieldAnnotations) { + item.writeTo(file, out); + } + } + + if (methodsSize != 0) { + Collections.sort(methodAnnotations); + if (annotates) { + out.annotate(0, " methods:"); + } + for (MethodAnnotationStruct item : methodAnnotations) { + item.writeTo(file, out); + } + } + + if (parametersSize != 0) { + Collections.sort(parameterAnnotations); + if (annotates) { + out.annotate(0, " parameters:"); + } + for (ParameterAnnotationStruct item : parameterAnnotations) { + item.writeTo(file, out); + } + } + } + + /** + * Gets the list size of the given list, or {@code 0} if given + * {@code null}. + * + * @param list {@code null-ok;} the list in question + * @return {@code >= 0;} its size + */ + private static int listSize(ArrayList list) { + if (list == null) { + return 0; + } + + return list.size(); + } + + /** + * Prints out the contents of this instance, in a debugging-friendly + * way. This is meant to be called from {@link ClassDefItem#debugPrint}. + * + * @param out {@code non-null;} where to output to + */ + /*package*/ void debugPrint(PrintWriter out) { + if (classAnnotations != null) { + out.println(" class annotations: " + classAnnotations); + } + + if (fieldAnnotations != null) { + out.println(" field annotations:"); + for (FieldAnnotationStruct item : fieldAnnotations) { + out.println(" " + item.toHuman()); + } + } + + if (methodAnnotations != null) { + out.println(" method annotations:"); + for (MethodAnnotationStruct item : methodAnnotations) { + out.println(" " + item.toHuman()); + } + } + + if (parameterAnnotations != null) { + out.println(" parameter annotations:"); + for (ParameterAnnotationStruct item : parameterAnnotations) { + out.println(" " + item.toHuman()); + } + } + } +} diff --git a/dx/src/com/android/jack/dx/dex/file/CatchStructs.java b/dx/src/com/android/jack/dx/dex/file/CatchStructs.java new file mode 100644 index 00000000..686ef820 --- /dev/null +++ b/dx/src/com/android/jack/dx/dex/file/CatchStructs.java @@ -0,0 +1,315 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.dex.file; + +import com.android.jack.dx.dex.code.CatchHandlerList; +import com.android.jack.dx.dex.code.CatchTable; +import com.android.jack.dx.dex.code.DalvCode; +import com.android.jack.dx.util.AnnotatedOutput; +import com.android.jack.dx.util.ByteArrayAnnotatedOutput; +import com.android.jack.dx.util.Hex; + +import java.io.PrintWriter; +import java.util.Map; +import java.util.TreeMap; + +/** + * List of exception handlers (tuples of covered range, catch type, + * handler address) for a particular piece of code. Instances of this + * class correspond to a {@code try_item[]} and a + * {@code catch_handler_item[]}. + */ +public final class CatchStructs { + /** + * the size of a {@code try_item}: a {@code uint} + * and two {@code ushort}s + */ + public static final int TRY_ITEM_WRITE_SIZE = 4 + (2 * 2); + + /** {@code non-null;} code that contains the catches */ + private final DalvCode code; + + /** + * {@code null-ok;} the underlying table; set in + * {@link #finishProcessingIfNecessary} + */ + private CatchTable table; + + /** + * {@code null-ok;} the encoded handler list, if calculated; set in + * {@link #encode} + */ + private byte[] encodedHandlers; + + /** + * length of the handlers header (encoded size), if known; used for + * annotation + */ + private int encodedHandlerHeaderSize; + + /** + * {@code null-ok;} map from handler lists to byte offsets, if calculated; set in + * {@link #encode} + */ + private TreeMap handlerOffsets; + + /** + * Constructs an instance. + * + * @param code {@code non-null;} code that contains the catches + */ + public CatchStructs(DalvCode code) { + this.code = code; + this.table = null; + this.encodedHandlers = null; + this.encodedHandlerHeaderSize = 0; + this.handlerOffsets = null; + } + + /** + * Finish processing the catches, if necessary. + */ + private void finishProcessingIfNecessary() { + if (table == null) { + table = code.getCatches(); + } + } + + /** + * Gets the size of the tries list, in entries. + * + * @return {@code >= 0;} the tries list size + */ + public int triesSize() { + finishProcessingIfNecessary(); + return table.size(); + } + + /** + * Does a human-friendly dump of this instance. + * + * @param out {@code non-null;} where to dump + * @param prefix {@code non-null;} prefix to attach to each line of output + */ + public void debugPrint(PrintWriter out, String prefix) { + annotateEntries(prefix, out, null); + } + + /** + * Encodes the handler lists. + * + * @param file {@code non-null;} file this instance is part of + */ + public void encode(DexFile file) { + finishProcessingIfNecessary(); + + TypeIdsSection typeIds = file.getTypeIds(); + int size = table.size(); + + handlerOffsets = new TreeMap(); + + /* + * First add a map entry for each unique list. The tree structure + * will ensure they are sorted when we reiterate later. + */ + for (int i = 0; i < size; i++) { + handlerOffsets.put(table.get(i).getHandlers(), null); + } + + if (handlerOffsets.size() > 65535) { + throw new UnsupportedOperationException( + "too many catch handlers"); + } + + ByteArrayAnnotatedOutput out = new ByteArrayAnnotatedOutput(); + + // Write out the handlers "header" consisting of its size in entries. + encodedHandlerHeaderSize = + out.writeUleb128(handlerOffsets.size()); + + // Now write the lists out in order, noting the offset of each. + for (Map.Entry mapping : + handlerOffsets.entrySet()) { + CatchHandlerList list = mapping.getKey(); + int listSize = list.size(); + boolean catchesAll = list.catchesAll(); + + // Set the offset before we do any writing. + mapping.setValue(out.getCursor()); + + if (catchesAll) { + // A size <= 0 means that the list ends with a catch-all. + out.writeSleb128(-(listSize - 1)); + listSize--; + } else { + out.writeSleb128(listSize); + } + + for (int i = 0; i < listSize; i++) { + CatchHandlerList.Entry entry = list.get(i); + out.writeUleb128( + typeIds.indexOf(entry.getExceptionType())); + out.writeUleb128(entry.getHandler()); + } + + if (catchesAll) { + out.writeUleb128(list.get(listSize).getHandler()); + } + } + + encodedHandlers = out.toByteArray(); + } + + /** + * Gets the write size of this instance, in bytes. + * + * @return {@code >= 0;} the write size + */ + public int writeSize() { + return (triesSize() * TRY_ITEM_WRITE_SIZE) + + + encodedHandlers.length; + } + + /** + * Writes this instance to the given stream. + * + * @param file {@code non-null;} file this instance is part of + * @param out {@code non-null;} where to write to + */ + public void writeTo(DexFile file, AnnotatedOutput out) { + finishProcessingIfNecessary(); + + if (out.annotates()) { + annotateEntries(" ", null, out); + } + + int tableSize = table.size(); + for (int i = 0; i < tableSize; i++) { + CatchTable.Entry one = table.get(i); + int start = one.getStart(); + int end = one.getEnd(); + int insnCount = end - start; + + if (insnCount >= 65536) { + throw new UnsupportedOperationException( + "bogus exception range: " + Hex.u4(start) + ".." + + Hex.u4(end)); + } + + out.writeInt(start); + out.writeShort(insnCount); + out.writeShort(handlerOffsets.get(one.getHandlers())); + } + + out.write(encodedHandlers); + } + + /** + * Helper method to annotate or simply print the exception handlers. + * Only one of {@code printTo} or {@code annotateTo} should + * be non-null. + * + * @param prefix {@code non-null;} prefix for each line + * @param printTo {@code null-ok;} where to print to + * @param annotateTo {@code null-ok;} where to consume bytes and annotate to + */ + private void annotateEntries(String prefix, PrintWriter printTo, + AnnotatedOutput annotateTo) { + finishProcessingIfNecessary(); + + boolean consume = (annotateTo != null); + int amt1 = consume ? 6 : 0; + int amt2 = consume ? 2 : 0; + int size = table.size(); + String subPrefix = prefix + " "; + + if (consume) { + annotateTo.annotate(0, prefix + "tries:"); + } else { + printTo.println(prefix + "tries:"); + } + + for (int i = 0; i < size; i++) { + CatchTable.Entry entry = table.get(i); + CatchHandlerList handlers = entry.getHandlers(); + String s1 = subPrefix + "try " + Hex.u2or4(entry.getStart()) + + ".." + Hex.u2or4(entry.getEnd()); + String s2 = handlers.toHuman(subPrefix, ""); + + if (consume) { + annotateTo.annotate(amt1, s1); + annotateTo.annotate(amt2, s2); + } else { + printTo.println(s1); + printTo.println(s2); + } + } + + if (! consume) { + // Only emit the handler lists if we are consuming bytes. + return; + } + + annotateTo.annotate(0, prefix + "handlers:"); + annotateTo.annotate(encodedHandlerHeaderSize, + subPrefix + "size: " + Hex.u2(handlerOffsets.size())); + + int lastOffset = 0; + CatchHandlerList lastList = null; + + for (Map.Entry mapping : + handlerOffsets.entrySet()) { + CatchHandlerList list = mapping.getKey(); + int offset = mapping.getValue(); + + if (lastList != null) { + annotateAndConsumeHandlers(lastList, lastOffset, + offset - lastOffset, subPrefix, printTo, annotateTo); + } + + lastList = list; + lastOffset = offset; + } + + annotateAndConsumeHandlers(lastList, lastOffset, + encodedHandlers.length - lastOffset, + subPrefix, printTo, annotateTo); + } + + /** + * Helper for {@link #annotateEntries} to annotate a catch handler list + * while consuming it. + * + * @param handlers {@code non-null;} handlers to annotate + * @param offset {@code >= 0;} the offset of this handler + * @param size {@code >= 1;} the number of bytes the handlers consume + * @param prefix {@code non-null;} prefix for each line + * @param printTo {@code null-ok;} where to print to + * @param annotateTo {@code non-null;} where to annotate to + */ + private static void annotateAndConsumeHandlers(CatchHandlerList handlers, + int offset, int size, String prefix, PrintWriter printTo, + AnnotatedOutput annotateTo) { + String s = handlers.toHuman(prefix, Hex.u2(offset) + ": "); + + if (printTo != null) { + printTo.println(s); + } + + annotateTo.annotate(size, s); + } +} diff --git a/dx/src/com/android/jack/dx/dex/file/ClassDataItem.java b/dx/src/com/android/jack/dx/dex/file/ClassDataItem.java new file mode 100644 index 00000000..8ca353c2 --- /dev/null +++ b/dx/src/com/android/jack/dx/dex/file/ClassDataItem.java @@ -0,0 +1,426 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.dex.file; + +import com.android.jack.dx.rop.cst.Constant; +import com.android.jack.dx.rop.cst.CstArray; +import com.android.jack.dx.rop.cst.CstLiteralBits; +import com.android.jack.dx.rop.cst.CstType; +import com.android.jack.dx.rop.cst.Zeroes; +import com.android.jack.dx.util.AnnotatedOutput; +import com.android.jack.dx.util.ByteArrayAnnotatedOutput; +import com.android.jack.dx.util.Writers; + +import java.io.PrintWriter; +import java.io.Writer; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; + +/** + * Representation of all the parts of a Dalvik class that are generally + * "inflated" into an in-memory representation at runtime. Instances of + * this class are represented in a compact streamable form in a + * {@code dex} file, as opposed to a random-access form. + */ +public final class ClassDataItem extends OffsettedItem { + /** {@code non-null;} what class this data is for, just for listing generation */ + private final CstType thisClass; + + /** {@code non-null;} list of static fields */ + private final ArrayList staticFields; + + /** {@code non-null;} list of initial values for static fields */ + private final HashMap staticValues; + + /** {@code non-null;} list of instance fields */ + private final ArrayList instanceFields; + + /** {@code non-null;} list of direct methods */ + private final ArrayList directMethods; + + /** {@code non-null;} list of virtual methods */ + private final ArrayList virtualMethods; + + /** {@code null-ok;} static initializer list; set in {@link #addContents} */ + private CstArray staticValuesConstant; + + /** + * {@code null-ok;} encoded form, ready for writing to a file; set during + * {@link #place0} + */ + private byte[] encodedForm; + + /** + * Constructs an instance. Its sets of members are initially + * empty. + * + * @param thisClass {@code non-null;} what class this data is for, just + * for listing generation + */ + public ClassDataItem(CstType thisClass) { + super(1, -1); + + if (thisClass == null) { + throw new NullPointerException("thisClass == null"); + } + + this.thisClass = thisClass; + this.staticFields = new ArrayList(20); + this.staticValues = new HashMap(40); + this.instanceFields = new ArrayList(20); + this.directMethods = new ArrayList(20); + this.virtualMethods = new ArrayList(20); + this.staticValuesConstant = null; + } + + /** {@inheritDoc} */ + @Override + public ItemType itemType() { + return ItemType.TYPE_CLASS_DATA_ITEM; + } + + /** {@inheritDoc} */ + @Override + public String toHuman() { + return toString(); + } + + /** + * Returns whether this instance is empty. + * + * @return {@code true} if this instance is empty or + * {@code false} if at least one element has been added to it + */ + public boolean isEmpty() { + return staticFields.isEmpty() && instanceFields.isEmpty() + && directMethods.isEmpty() && virtualMethods.isEmpty(); + } + + /** + * Adds a static field. + * + * @param field {@code non-null;} the field to add + * @param value {@code null-ok;} initial value for the field, if any + */ + public void addStaticField(EncodedField field, Constant value) { + if (field == null) { + throw new NullPointerException("field == null"); + } + + if (staticValuesConstant != null) { + throw new UnsupportedOperationException( + "static fields already sorted"); + } + + staticFields.add(field); + staticValues.put(field, value); + } + + /** + * Adds an instance field. + * + * @param field {@code non-null;} the field to add + */ + public void addInstanceField(EncodedField field) { + if (field == null) { + throw new NullPointerException("field == null"); + } + + instanceFields.add(field); + } + + /** + * Adds a direct ({@code static} and/or {@code private}) method. + * + * @param method {@code non-null;} the method to add + */ + public void addDirectMethod(EncodedMethod method) { + if (method == null) { + throw new NullPointerException("method == null"); + } + + directMethods.add(method); + } + + /** + * Adds a virtual method. + * + * @param method {@code non-null;} the method to add + */ + public void addVirtualMethod(EncodedMethod method) { + if (method == null) { + throw new NullPointerException("method == null"); + } + + virtualMethods.add(method); + } + + /** + * Gets all the methods in this class. The returned list is not linked + * in any way to the underlying lists contained in this instance, but + * the objects contained in the list are shared. + * + * @return {@code non-null;} list of all methods + */ + public ArrayList getMethods() { + int sz = directMethods.size() + virtualMethods.size(); + ArrayList result = new ArrayList(sz); + + result.addAll(directMethods); + result.addAll(virtualMethods); + + return result; + } + + + /** + * Prints out the contents of this instance, in a debugging-friendly + * way. + * + * @param out {@code non-null;} where to output to + * @param verbose whether to be verbose with the output + */ + public void debugPrint(Writer out, boolean verbose) { + PrintWriter pw = Writers.printWriterFor(out); + + int sz = staticFields.size(); + for (int i = 0; i < sz; i++) { + pw.println(" sfields[" + i + "]: " + staticFields.get(i)); + } + + sz = instanceFields.size(); + for (int i = 0; i < sz; i++) { + pw.println(" ifields[" + i + "]: " + instanceFields.get(i)); + } + + sz = directMethods.size(); + for (int i = 0; i < sz; i++) { + pw.println(" dmeths[" + i + "]:"); + directMethods.get(i).debugPrint(pw, verbose); + } + + sz = virtualMethods.size(); + for (int i = 0; i < sz; i++) { + pw.println(" vmeths[" + i + "]:"); + virtualMethods.get(i).debugPrint(pw, verbose); + } + } + + /** {@inheritDoc} */ + @Override + public void addContents(DexFile file) { + if (!staticFields.isEmpty()) { + getStaticValuesConstant(); // Force the fields to be sorted. + for (EncodedField field : staticFields) { + field.addContents(file); + } + } + + if (!instanceFields.isEmpty()) { + Collections.sort(instanceFields); + for (EncodedField field : instanceFields) { + field.addContents(file); + } + } + + if (!directMethods.isEmpty()) { + Collections.sort(directMethods); + for (EncodedMethod method : directMethods) { + method.addContents(file); + } + } + + if (!virtualMethods.isEmpty()) { + Collections.sort(virtualMethods); + for (EncodedMethod method : virtualMethods) { + method.addContents(file); + } + } + } + + /** + * Gets a {@link CstArray} corresponding to {@link #staticValues} if + * it contains any non-zero non-{@code null} values. + * + * @return {@code null-ok;} the corresponding constant or {@code null} if + * there are no values to encode + */ + public CstArray getStaticValuesConstant() { + if ((staticValuesConstant == null) && (staticFields.size() != 0)) { + staticValuesConstant = makeStaticValuesConstant(); + } + + return staticValuesConstant; + } + + /** + * Gets a {@link CstArray} corresponding to {@link #staticValues} if + * it contains any non-zero non-{@code null} values. + * + * @return {@code null-ok;} the corresponding constant or {@code null} if + * there are no values to encode + */ + private CstArray makeStaticValuesConstant() { + // First sort the statics into their final order. + Collections.sort(staticFields); + + /* + * Get the size of staticValues minus any trailing zeros/nulls (both + * nulls per se as well as instances of CstKnownNull). + */ + + int size = staticFields.size(); + while (size > 0) { + EncodedField field = staticFields.get(size - 1); + Constant cst = staticValues.get(field); + if (cst instanceof CstLiteralBits) { + // Note: CstKnownNull extends CstLiteralBits. + if (((CstLiteralBits) cst).getLongBits() != 0) { + break; + } + } else if (cst != null) { + break; + } + size--; + } + + if (size == 0) { + return null; + } + + // There is something worth encoding, so build up a result. + + CstArray.List list = new CstArray.List(size); + for (int i = 0; i < size; i++) { + EncodedField field = staticFields.get(i); + Constant cst = staticValues.get(field); + if (cst == null) { + cst = Zeroes.zeroFor(field.getRef().getType()); + } + list.set(i, cst); + } + list.setImmutable(); + + return new CstArray(list); + } + + /** {@inheritDoc} */ + @Override + protected void place0(Section addedTo, int offset) { + // Encode the data and note the size. + + ByteArrayAnnotatedOutput out = new ByteArrayAnnotatedOutput(); + + encodeOutput(addedTo.getFile(), out); + encodedForm = out.toByteArray(); + setWriteSize(encodedForm.length); + } + + /** + * Writes out the encoded form of this instance. + * + * @param file {@code non-null;} file this instance is part of + * @param out {@code non-null;} where to write to + */ + private void encodeOutput(DexFile file, AnnotatedOutput out) { + boolean annotates = out.annotates(); + + if (annotates) { + out.annotate(0, offsetString() + " class data for " + + thisClass.toHuman()); + } + + encodeSize(file, out, "static_fields", staticFields.size()); + encodeSize(file, out, "instance_fields", instanceFields.size()); + encodeSize(file, out, "direct_methods", directMethods.size()); + encodeSize(file, out, "virtual_methods", virtualMethods.size()); + + encodeList(file, out, "static_fields", staticFields); + encodeList(file, out, "instance_fields", instanceFields); + encodeList(file, out, "direct_methods", directMethods); + encodeList(file, out, "virtual_methods", virtualMethods); + + if (annotates) { + out.endAnnotation(); + } + } + + /** + * Helper for {@link #encodeOutput}, which writes out the given + * size value, annotating it as well (if annotations are enabled). + * + * @param file {@code non-null;} file this instance is part of + * @param out {@code non-null;} where to write to + * @param label {@code non-null;} the label for the purposes of annotation + * @param size {@code >= 0;} the size to write + */ + private static void encodeSize(DexFile file, AnnotatedOutput out, + String label, int size) { + if (out.annotates()) { + out.annotate(String.format(" %-21s %08x", label + "_size:", + size)); + } + + out.writeUleb128(size); + } + + /** + * Helper for {@link #encodeOutput}, which writes out the given + * list. It also annotates the items (if any and if annotations + * are enabled). + * + * @param file {@code non-null;} file this instance is part of + * @param out {@code non-null;} where to write to + * @param label {@code non-null;} the label for the purposes of annotation + * @param list {@code non-null;} the list in question + */ + private static void encodeList(DexFile file, AnnotatedOutput out, + String label, ArrayList list) { + int size = list.size(); + int lastIndex = 0; + + if (size == 0) { + return; + } + + if (out.annotates()) { + out.annotate(0, " " + label + ":"); + } + + for (int i = 0; i < size; i++) { + lastIndex = list.get(i).encode(file, out, lastIndex, i); + } + } + + /** {@inheritDoc} */ + @Override + public void writeTo0(DexFile file, AnnotatedOutput out) { + boolean annotates = out.annotates(); + + if (annotates) { + /* + * The output is to be annotated, so redo the work previously + * done by place0(), except this time annotations will actually + * get emitted. + */ + encodeOutput(file, out); + } else { + out.write(encodedForm); + } + } +} diff --git a/dx/src/com/android/jack/dx/dex/file/ClassDefItem.java b/dx/src/com/android/jack/dx/dex/file/ClassDefItem.java new file mode 100644 index 00000000..7f485a67 --- /dev/null +++ b/dx/src/com/android/jack/dx/dex/file/ClassDefItem.java @@ -0,0 +1,408 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.dex.file; + +import com.android.jack.dx.dex.SizeOf; +import com.android.jack.dx.rop.annotation.Annotations; +import com.android.jack.dx.rop.annotation.AnnotationsList; +import com.android.jack.dx.rop.code.AccessFlags; +import com.android.jack.dx.rop.cst.Constant; +import com.android.jack.dx.rop.cst.CstArray; +import com.android.jack.dx.rop.cst.CstFieldRef; +import com.android.jack.dx.rop.cst.CstMethodRef; +import com.android.jack.dx.rop.cst.CstString; +import com.android.jack.dx.rop.cst.CstType; +import com.android.jack.dx.rop.type.StdTypeList; +import com.android.jack.dx.rop.type.TypeList; +import com.android.jack.dx.util.AnnotatedOutput; +import com.android.jack.dx.util.Hex; +import com.android.jack.dx.util.Writers; + +import java.io.PrintWriter; +import java.io.Writer; +import java.util.ArrayList; + +/** + * Representation of a Dalvik class, which is basically a set of + * members (fields or methods) along with a few more pieces of + * information. + */ +public final class ClassDefItem extends IndexedItem { + + /** {@code non-null;} type constant for this class */ + private final CstType thisClass; + + /** access flags */ + private final int accessFlags; + + /** + * {@code null-ok;} superclass or {@code null} if this class is a/the + * root class + */ + private final CstType superclass; + + /** {@code null-ok;} list of implemented interfaces */ + private TypeListItem interfaces; + + /** {@code null-ok;} source file name or {@code null} if unknown */ + private final CstString sourceFile; + + /** {@code non-null;} associated class data object */ + private final ClassDataItem classData; + + /** + * {@code null-ok;} item wrapper for the static values, initialized + * in {@link #addContents} + */ + private EncodedArrayItem staticValuesItem; + + /** {@code non-null;} annotations directory */ + private AnnotationsDirectoryItem annotationsDirectory; + + /** + * Constructs an instance. Its sets of members and annotations are + * initially empty. + * + * @param thisClass {@code non-null;} type constant for this class + * @param accessFlags access flags + * @param superclass {@code null-ok;} superclass or {@code null} if + * this class is a/the root class + * @param interfaces {@code non-null;} list of implemented interfaces + * @param sourceFile {@code null-ok;} source file name or + * {@code null} if unknown + */ + public ClassDefItem(CstType thisClass, int accessFlags, + CstType superclass, TypeList interfaces, CstString sourceFile) { + if (thisClass == null) { + throw new NullPointerException("thisClass == null"); + } + + /* + * TODO: Maybe check accessFlags and superclass, at + * least for easily-checked stuff? + */ + + if (interfaces == null) { + throw new NullPointerException("interfaces == null"); + } + + this.thisClass = thisClass; + this.accessFlags = accessFlags; + this.superclass = superclass; + this.interfaces = + (interfaces.size() == 0) ? null : new TypeListItem(interfaces); + this.sourceFile = sourceFile; + this.classData = new ClassDataItem(thisClass); + this.staticValuesItem = null; + this.annotationsDirectory = new AnnotationsDirectoryItem(); + } + + /** {@inheritDoc} */ + @Override + public ItemType itemType() { + return ItemType.TYPE_CLASS_DEF_ITEM; + } + + /** {@inheritDoc} */ + @Override + public int writeSize() { + return SizeOf.CLASS_DEF_ITEM; + } + + /** {@inheritDoc} */ + @Override + public void addContents(DexFile file) { + TypeIdsSection typeIds = file.getTypeIds(); + MixedItemSection byteData = file.getByteData(); + MixedItemSection wordData = file.getWordData(); + MixedItemSection typeLists = file.getTypeLists(); + StringIdsSection stringIds = file.getStringIds(); + + typeIds.intern(thisClass); + + if (!classData.isEmpty()) { + MixedItemSection classDataSection = file.getClassData(); + classDataSection.add(classData); + + CstArray staticValues = classData.getStaticValuesConstant(); + if (staticValues != null) { + staticValuesItem = + byteData.intern(new EncodedArrayItem(staticValues)); + } + } + + if (superclass != null) { + typeIds.intern(superclass); + } + + if (interfaces != null) { + interfaces = typeLists.intern(interfaces); + } + + if (sourceFile != null) { + stringIds.intern(sourceFile); + } + + if (! annotationsDirectory.isEmpty()) { + if (annotationsDirectory.isInternable()) { + annotationsDirectory = wordData.intern(annotationsDirectory); + } else { + wordData.add(annotationsDirectory); + } + } + } + + /** {@inheritDoc} */ + @Override + public void writeTo(DexFile file, AnnotatedOutput out) { + boolean annotates = out.annotates(); + TypeIdsSection typeIds = file.getTypeIds(); + int classIdx = typeIds.indexOf(thisClass); + int superIdx = (superclass == null) ? -1 : + typeIds.indexOf(superclass); + int interOff = OffsettedItem.getAbsoluteOffsetOr0(interfaces); + int annoOff = annotationsDirectory.isEmpty() ? 0 : + annotationsDirectory.getAbsoluteOffset(); + int sourceFileIdx = (sourceFile == null) ? -1 : + file.getStringIds().indexOf(sourceFile); + int dataOff = classData.isEmpty()? 0 : classData.getAbsoluteOffset(); + int staticValuesOff = + OffsettedItem.getAbsoluteOffsetOr0(staticValuesItem); + + if (annotates) { + out.annotate(0, indexString() + ' ' + thisClass.toHuman()); + out.annotate(4, " class_idx: " + Hex.u4(classIdx)); + out.annotate(4, " access_flags: " + + AccessFlags.classString(accessFlags)); + out.annotate(4, " superclass_idx: " + Hex.u4(superIdx) + + " // " + ((superclass == null) ? "" : + superclass.toHuman())); + out.annotate(4, " interfaces_off: " + Hex.u4(interOff)); + if (interOff != 0) { + TypeList list = interfaces.getList(); + int sz = list.size(); + for (int i = 0; i < sz; i++) { + out.annotate(0, " " + list.getType(i).toHuman()); + } + } + out.annotate(4, " source_file_idx: " + Hex.u4(sourceFileIdx) + + " // " + ((sourceFile == null) ? "" : + sourceFile.toHuman())); + out.annotate(4, " annotations_off: " + Hex.u4(annoOff)); + out.annotate(4, " class_data_off: " + Hex.u4(dataOff)); + out.annotate(4, " static_values_off: " + + Hex.u4(staticValuesOff)); + } + + out.writeInt(classIdx); + out.writeInt(accessFlags); + out.writeInt(superIdx); + out.writeInt(interOff); + out.writeInt(sourceFileIdx); + out.writeInt(annoOff); + out.writeInt(dataOff); + out.writeInt(staticValuesOff); + } + + /** + * Gets the constant corresponding to this class. + * + * @return {@code non-null;} the constant + */ + public CstType getThisClass() { + return thisClass; + } + + /** + * Gets the access flags. + * + * @return the access flags + */ + public int getAccessFlags() { + return accessFlags; + } + + /** + * Gets the superclass. + * + * @return {@code null-ok;} the superclass or {@code null} if + * this class is a/the root class + */ + public CstType getSuperclass() { + return superclass; + } + + /** + * Gets the list of interfaces implemented. + * + * @return {@code non-null;} the interfaces list + */ + public TypeList getInterfaces() { + if (interfaces == null) { + return StdTypeList.EMPTY; + } + + return interfaces.getList(); + } + + /** + * Gets the source file name. + * + * @return {@code null-ok;} the source file name or {@code null} if unknown + */ + public CstString getSourceFile() { + return sourceFile; + } + + /** + * Adds a static field. + * + * @param field {@code non-null;} the field to add + * @param value {@code null-ok;} initial value for the field, if any + */ + public void addStaticField(EncodedField field, Constant value) { + classData.addStaticField(field, value); + } + + /** + * Adds an instance field. + * + * @param field {@code non-null;} the field to add + */ + public void addInstanceField(EncodedField field) { + classData.addInstanceField(field); + } + + /** + * Adds a direct ({@code static} and/or {@code private}) method. + * + * @param method {@code non-null;} the method to add + */ + public void addDirectMethod(EncodedMethod method) { + classData.addDirectMethod(method); + } + + /** + * Adds a virtual method. + * + * @param method {@code non-null;} the method to add + */ + public void addVirtualMethod(EncodedMethod method) { + classData.addVirtualMethod(method); + } + + /** + * Gets all the methods in this class. The returned list is not linked + * in any way to the underlying lists contained in this instance, but + * the objects contained in the list are shared. + * + * @return {@code non-null;} list of all methods + */ + public ArrayList getMethods() { + return classData.getMethods(); + } + + /** + * Sets the direct annotations on this class. These are annotations + * made on the class, per se, as opposed to on one of its members. + * It is only valid to call this method at most once per instance. + * + * @param annotations {@code non-null;} annotations to set for this class + */ + public void setClassAnnotations(Annotations annotations) { + annotationsDirectory.setClassAnnotations(annotations); + } + + /** + * Adds a field annotations item to this class. + * + * @param field {@code non-null;} field in question + * @param annotations {@code non-null;} associated annotations to add + */ + public void addFieldAnnotations(CstFieldRef field, + Annotations annotations) { + annotationsDirectory.addFieldAnnotations(field, annotations); + } + + /** + * Adds a method annotations item to this class. + * + * @param method {@code non-null;} method in question + * @param annotations {@code non-null;} associated annotations to add + */ + public void addMethodAnnotations(CstMethodRef method, + Annotations annotations) { + annotationsDirectory.addMethodAnnotations(method, annotations); + } + + /** + * Adds a parameter annotations item to this class. + * + * @param method {@code non-null;} method in question + * @param list {@code non-null;} associated list of annotation sets to add + */ + public void addParameterAnnotations(CstMethodRef method, + AnnotationsList list) { + annotationsDirectory.addParameterAnnotations(method, list); + } + + /** + * Gets the method annotations for a given method, if any. This is + * meant for use by debugging / dumping code. + * + * @param method {@code non-null;} the method + * @return {@code null-ok;} the method annotations, if any + */ + public Annotations getMethodAnnotations(CstMethodRef method) { + return annotationsDirectory.getMethodAnnotations(method); + } + + /** + * Gets the parameter annotations for a given method, if any. This is + * meant for use by debugging / dumping code. + * + * @param method {@code non-null;} the method + * @return {@code null-ok;} the parameter annotations, if any + */ + public AnnotationsList getParameterAnnotations(CstMethodRef method) { + return annotationsDirectory.getParameterAnnotations(method); + } + + /** + * Prints out the contents of this instance, in a debugging-friendly + * way. + * + * @param out {@code non-null;} where to output to + * @param verbose whether to be verbose with the output + */ + public void debugPrint(Writer out, boolean verbose) { + PrintWriter pw = Writers.printWriterFor(out); + + pw.println(getClass().getName() + " {"); + pw.println(" accessFlags: " + Hex.u2(accessFlags)); + pw.println(" superclass: " + superclass); + pw.println(" interfaces: " + + ((interfaces == null) ? "" : interfaces)); + pw.println(" sourceFile: " + + ((sourceFile == null) ? "" : sourceFile.toQuoted())); + + classData.debugPrint(out, verbose); + annotationsDirectory.debugPrint(pw); + + pw.println("}"); + } +} diff --git a/dx/src/com/android/jack/dx/dex/file/ClassDefsSection.java b/dx/src/com/android/jack/dx/dex/file/ClassDefsSection.java new file mode 100644 index 00000000..512ffc19 --- /dev/null +++ b/dx/src/com/android/jack/dx/dex/file/ClassDefsSection.java @@ -0,0 +1,187 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.dex.file; + +import com.android.jack.dx.rop.cst.Constant; +import com.android.jack.dx.rop.cst.CstType; +import com.android.jack.dx.rop.type.Type; +import com.android.jack.dx.rop.type.TypeList; +import com.android.jack.dx.util.AnnotatedOutput; +import com.android.jack.dx.util.Hex; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.TreeMap; + +/** + * Class definitions list section of a {@code .dex} file. + */ +public final class ClassDefsSection extends UniformItemSection { + /** + * {@code non-null;} map from type constants for classes to {@link + * ClassDefItem} instances that define those classes + */ + private final TreeMap classDefs; + + /** {@code null-ok;} ordered list of classes; set in {@link #orderItems} */ + private ArrayList orderedDefs; + + /** + * Constructs an instance. The file offset is initially unknown. + * + * @param file {@code non-null;} file that this instance is part of + */ + public ClassDefsSection(DexFile file) { + super("class_defs", file, 4); + + classDefs = new TreeMap(); + orderedDefs = null; + } + + /** {@inheritDoc} */ + @Override + public Collection items() { + if (orderedDefs != null) { + return orderedDefs; + } + + return classDefs.values(); + } + + /** {@inheritDoc} */ + @Override + public IndexedItem get(Constant cst) { + if (cst == null) { + throw new NullPointerException("cst == null"); + } + + throwIfNotPrepared(); + + Type type = ((CstType) cst).getClassType(); + IndexedItem result = classDefs.get(type); + + if (result == null) { + throw new IllegalArgumentException("not found"); + } + + return result; + } + + /** + * Writes the portion of the file header that refers to this instance. + * + * @param out {@code non-null;} where to write + */ + public void writeHeaderPart(AnnotatedOutput out) { + throwIfNotPrepared(); + + int sz = classDefs.size(); + int offset = (sz == 0) ? 0 : getFileOffset(); + + if (out.annotates()) { + out.annotate(4, "class_defs_size: " + Hex.u4(sz)); + out.annotate(4, "class_defs_off: " + Hex.u4(offset)); + } + + out.writeInt(sz); + out.writeInt(offset); + } + + /** + * Adds an element to this instance. It is illegal to attempt to add more + * than one class with the same name. + * + * @param clazz {@code non-null;} the class def to add + */ + public void add(ClassDefItem clazz) { + Type type; + + try { + type = clazz.getThisClass().getClassType(); + } catch (NullPointerException ex) { + // Elucidate the exception. + throw new NullPointerException("clazz == null"); + } + + throwIfPrepared(); + + if (classDefs.get(type) != null) { + throw new IllegalArgumentException("already added: " + type); + } + + classDefs.put(type, clazz); + } + + /** {@inheritDoc} */ + @Override + protected void orderItems() { + int sz = classDefs.size(); + int idx = 0; + + orderedDefs = new ArrayList(sz); + + /* + * Iterate over all the classes, recursively assigning an + * index to each, implicitly skipping the ones that have + * already been assigned by the time this (top-level) + * iteration reaches them. + */ + for (Type type : classDefs.keySet()) { + idx = orderItems0(type, idx, sz - idx); + } + } + + /** + * Helper for {@link #orderItems}, which recursively assigns indices + * to classes. + * + * @param type {@code null-ok;} type ref to assign, if any + * @param idx {@code >= 0;} the next index to assign + * @param maxDepth maximum recursion depth; if negative, this will + * throw an exception indicating class definition circularity + * @return {@code >= 0;} the next index to assign + */ + private int orderItems0(Type type, int idx, int maxDepth) { + ClassDefItem c = classDefs.get(type); + + if ((c == null) || (c.hasIndex())) { + return idx; + } + + if (maxDepth < 0) { + throw new RuntimeException("class circularity with " + type); + } + + maxDepth--; + + CstType superclassCst = c.getSuperclass(); + if (superclassCst != null) { + Type superclass = superclassCst.getClassType(); + idx = orderItems0(superclass, idx, maxDepth); + } + + TypeList interfaces = c.getInterfaces(); + int sz = interfaces.size(); + for (int i = 0; i < sz; i++) { + idx = orderItems0(interfaces.getType(i), idx, maxDepth); + } + + c.setIndex(idx); + orderedDefs.add(c); + return idx + 1; + } +} diff --git a/dx/src/com/android/jack/dx/dex/file/Code.java b/dx/src/com/android/jack/dx/dex/file/Code.java new file mode 100644 index 00000000..8e8c1c05 --- /dev/null +++ b/dx/src/com/android/jack/dx/dex/file/Code.java @@ -0,0 +1,24 @@ +package com.android.jack.dx.dex.file; + +import java.io.PrintWriter; + +/** + * Interface representing code. + */ +public interface Code { + + /** file alignment of this class, in bytes */ + public static final int ALIGNMENT = 4; + + /** write size of the header of this class, in bytes */ + public static final int HEADER_SIZE = 16; + + /** + * Does a human-friendly dump of this instance. + * + * @param out {@code non-null;} where to dump + * @param prefix {@code non-null;} per-line prefix to use + * @param verbose whether to be verbose with the output + */ + public void debugPrint(PrintWriter out, String prefix, boolean verbose); +} diff --git a/dx/src/com/android/jack/dx/dex/file/CodeItem.java b/dx/src/com/android/jack/dx/dex/file/CodeItem.java new file mode 100644 index 00000000..15cb5a8d --- /dev/null +++ b/dx/src/com/android/jack/dx/dex/file/CodeItem.java @@ -0,0 +1,324 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.dex.file; + +import com.android.jack.dx.dex.code.DalvCode; +import com.android.jack.dx.dex.code.DalvInsnList; +import com.android.jack.dx.rop.cst.Constant; +import com.android.jack.dx.rop.cst.CstMethodRef; +import com.android.jack.dx.rop.type.StdTypeList; +import com.android.jack.dx.rop.type.Type; +import com.android.jack.dx.rop.type.TypeList; +import com.android.jack.dx.util.AnnotatedOutput; +import com.android.jack.dx.util.ExceptionWithContext; +import com.android.jack.dx.util.Hex; + +import java.io.PrintWriter; + +/** + * Representation of all the parts needed for concrete methods in a + * {@code dex} file. + */ +public final class CodeItem extends OffsettedItem implements Code { + + /** {@code non-null;} method that this code implements */ + private final CstMethodRef ref; + + /** {@code non-null;} the bytecode instructions and associated data */ + private final DalvCode code; + + /** {@code null-ok;} the catches, if needed; set in {@link #addContents} */ + private CatchStructs catches; + + /** whether this instance is for a {@code static} method */ + private final boolean isStatic; + + /** + * {@code non-null;} list of possibly-thrown exceptions; just used in + * generating debugging output (listings) + */ + private final TypeList throwsList; + + /** + * {@code null-ok;} the debug info or {@code null} if there is none; + * set in {@link #addContents} + */ + private DebugInfoItem debugInfo; + + /** + * Constructs an instance. + * + * @param ref {@code non-null;} method that this code implements + * @param code {@code non-null;} the underlying code + * @param isStatic whether this instance is for a {@code static} + * method + * @param throwsList {@code non-null;} list of possibly-thrown exceptions, + * just used in generating debugging output (listings) + */ + public CodeItem(CstMethodRef ref, DalvCode code, boolean isStatic, + TypeList throwsList) { + super(ALIGNMENT, -1); + + if (ref == null) { + throw new NullPointerException("ref == null"); + } + + if (code == null) { + throw new NullPointerException("code == null"); + } + + if (throwsList == null) { + throw new NullPointerException("throwsList == null"); + } + + this.ref = ref; + this.code = code; + this.isStatic = isStatic; + this.throwsList = throwsList; + this.catches = null; + this.debugInfo = null; + } + + /** {@inheritDoc} */ + @Override + public ItemType itemType() { + return ItemType.TYPE_CODE_ITEM; + } + + /** {@inheritDoc} */ + @Override + public void addContents(DexFile file) { + MixedItemSection byteData = file.getByteData(); + TypeIdsSection typeIds = file.getTypeIds(); + + if (code.hasPositions() || code.hasLocals()) { + debugInfo = new DebugInfoItem(code, isStatic, ref); + byteData.add(debugInfo); + } + + if (code.hasAnyCatches()) { + for (Type type : code.getCatchTypes()) { + typeIds.intern(type); + } + catches = new CatchStructs(code); + } + + for (Constant c : code.getInsnConstants()) { + file.internIfAppropriate(c); + } + } + + /** {@inheritDoc} */ + @Override + public String toString() { + return "CodeItem{" + toHuman() + "}"; + } + + /** {@inheritDoc} */ + @Override + public String toHuman() { + return ref.toHuman(); + } + + /** + * Gets the reference to the method this instance implements. + * + * @return {@code non-null;} the method reference + */ + public CstMethodRef getRef() { + return ref; + } + + /** + * Does a human-friendly dump of this instance. + * + * @param out {@code non-null;} where to dump + * @param prefix {@code non-null;} per-line prefix to use + * @param verbose whether to be verbose with the output + */ + @Override + public void debugPrint(PrintWriter out, String prefix, boolean verbose) { + out.println(ref.toHuman() + ":"); + + DalvInsnList insns = code.getInsns(); + out.println("regs: " + Hex.u2(getRegistersSize()) + + "; ins: " + Hex.u2(getInsSize()) + "; outs: " + + Hex.u2(getOutsSize())); + + insns.debugPrint(out, prefix, verbose); + + String prefix2 = prefix + " "; + + if (catches != null) { + out.print(prefix); + out.println("catches"); + catches.debugPrint(out, prefix2); + } + + if (debugInfo != null) { + out.print(prefix); + out.println("debug info"); + debugInfo.debugPrint(out, prefix2); + } + } + + /** {@inheritDoc} */ + @Override + protected void place0(Section addedTo, int offset) { + final DexFile file = addedTo.getFile(); + int catchesSize; + + /* + * In order to get the catches and insns, all the code's + * constants need to be assigned indices. + */ + code.assignIndices(new DalvCode.AssignIndicesCallback() { + @Override + public int getIndex(Constant cst) { + IndexedItem item = file.findItemOrNull(cst); + if (item == null) { + return -1; + } + return item.getIndex(); + } + }); + + if (catches != null) { + catches.encode(file); + catchesSize = catches.writeSize(); + } else { + catchesSize = 0; + } + + /* + * The write size includes the header, two bytes per code + * unit, post-code padding if necessary, and however much + * space the catches need. + */ + + int insnsSize = code.getInsns().codeSize(); + if ((insnsSize & 1) != 0) { + insnsSize++; + } + + setWriteSize(HEADER_SIZE + (insnsSize * 2) + catchesSize); + } + + /** {@inheritDoc} */ + @Override + protected void writeTo0(DexFile file, AnnotatedOutput out) { + boolean annotates = out.annotates(); + int regSz = getRegistersSize(); + int outsSz = getOutsSize(); + int insSz = getInsSize(); + int insnsSz = code.getInsns().codeSize(); + boolean needPadding = (insnsSz & 1) != 0; + int triesSz = (catches == null) ? 0 : catches.triesSize(); + int debugOff = (debugInfo == null) ? 0 : debugInfo.getAbsoluteOffset(); + + if (annotates) { + out.annotate(0, offsetString() + ' ' + ref.toHuman()); + out.annotate(2, " registers_size: " + Hex.u2(regSz)); + out.annotate(2, " ins_size: " + Hex.u2(insSz)); + out.annotate(2, " outs_size: " + Hex.u2(outsSz)); + out.annotate(2, " tries_size: " + Hex.u2(triesSz)); + out.annotate(4, " debug_off: " + Hex.u4(debugOff)); + out.annotate(4, " insns_size: " + Hex.u4(insnsSz)); + + // This isn't represented directly here, but it is useful to see. + int size = throwsList.size(); + if (size != 0) { + out.annotate(0, " throws " + StdTypeList.toHuman(throwsList)); + } + } + + out.writeShort(regSz); + out.writeShort(insSz); + out.writeShort(outsSz); + out.writeShort(triesSz); + out.writeInt(debugOff); + out.writeInt(insnsSz); + + writeCodes(file, out); + + if (catches != null) { + if (needPadding) { + if (annotates) { + out.annotate(2, " padding: 0"); + } + out.writeShort(0); + } + + catches.writeTo(file, out); + } + + if (annotates) { + /* + * These are pointed at in the code header (above), but it's less + * distracting to expand on them at the bottom of the code. + */ + if (debugInfo != null) { + out.annotate(0, " debug info"); + debugInfo.annotateTo(file, out, " "); + } + } + } + + /** + * Helper for {@link #writeTo0} which writes out the actual bytecode. + * + * @param file {@code non-null;} file we are part of + * @param out {@code non-null;} where to write to + */ + private void writeCodes(DexFile file, AnnotatedOutput out) { + DalvInsnList insns = code.getInsns(); + + try { + insns.writeTo(out); + } catch (RuntimeException ex) { + throw ExceptionWithContext.withContext(ex, "...while writing " + + "instructions for " + ref.toHuman()); + } + } + + /** + * Get the in registers count. + * + * @return the count + */ + private int getInsSize() { + return ref.getParameterWordCount(isStatic); + } + + /** + * Get the out registers count. + * + * @return the count + */ + private int getOutsSize() { + return code.getInsns().getOutsSize(); + } + + /** + * Get the total registers count. + * + * @return the count + */ + private int getRegistersSize() { + return code.getInsns().getRegistersSize(); + } +} diff --git a/dx/src/com/android/jack/dx/dex/file/DebugInfoConstants.java b/dx/src/com/android/jack/dx/dex/file/DebugInfoConstants.java new file mode 100644 index 00000000..63d8a34d --- /dev/null +++ b/dx/src/com/android/jack/dx/dex/file/DebugInfoConstants.java @@ -0,0 +1,154 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.dex.file; + +/** + * Constants for the dex debug info state machine format. + */ +public interface DebugInfoConstants { + + /* + * normal opcodes + */ + + /** + * Terminates a debug info sequence for a method.

+ * Args: none + * + */ + static final int DBG_END_SEQUENCE = 0x00; + + /** + * Advances the program counter/address register without emitting + * a positions entry.

+ * + * Args: + *

    + *
  1. Unsigned LEB128 — amount to advance pc by + *
+ */ + static final int DBG_ADVANCE_PC = 0x01; + + /** + * Advances the line register without emitting + * a positions entry.

+ * + * Args: + *

    + *
  1. Signed LEB128 — amount to change line register by. + *
+ */ + static final int DBG_ADVANCE_LINE = 0x02; + + /** + * Introduces a local variable at the current address.

+ * + * Args: + *

    + *
  1. Unsigned LEB128 — register that will contain local. + *
  2. Unsigned LEB128 — string index (shifted by 1) of local name. + *
  3. Unsigned LEB128 — type index (shifted by 1) of type. + *
+ */ + static final int DBG_START_LOCAL = 0x03; + + /** + * Introduces a local variable at the current address with a type + * signature specified.

+ * + * Args: + *

    + *
  1. Unsigned LEB128 — register that will contain local. + *
  2. Unsigned LEB128 — string index (shifted by 1) of local name. + *
  3. Unsigned LEB128 — type index (shifted by 1) of type. + *
  4. Unsigned LEB128 — string index (shifted by 1) of + * type signature. + *
+ */ + static final int DBG_START_LOCAL_EXTENDED = 0x04; + + /** + * Marks a currently-live local variable as out of scope at the + * current address.

+ * + * Args: + *

    + *
  1. Unsigned LEB128 — register that contained local + *
+ */ + static final int DBG_END_LOCAL = 0x05; + + /** + * Re-introduces a local variable at the current address. The name + * and type are the same as the last local that was live in the specified + * register.

+ * + * Args: + *

    + *
  1. Unsigned LEB128 — register to re-start. + *
+ */ + static final int DBG_RESTART_LOCAL = 0x06; + + + /** + * Sets the "prologue_end" state machine register, indicating that the + * next position entry that is added should be considered the end of + * a method prologue (an appropriate place for a method breakpoint).

+ * + * The prologue_end register is cleared by any special + * ({@code >= OPCODE_BASE}) opcode. + */ + static final int DBG_SET_PROLOGUE_END = 0x07; + + /** + * Sets the "epilogue_begin" state machine register, indicating that the + * next position entry that is added should be considered the beginning of + * a method epilogue (an appropriate place to suspend execution before + * method exit).

+ * + * The epilogue_begin register is cleared by any special + * ({@code >= OPCODE_BASE}) opcode. + */ + static final int DBG_SET_EPILOGUE_BEGIN = 0x08; + + /** + * Sets the current file that that line numbers refer to. All subsequent + * line number entries make reference to this source file name, instead + * of the default name specified in code_item. + * + * Args: + *

    + *
  1. Unsigned LEB128 — string index (shifted by 1) of source + * file name. + *
+ */ + static final int DBG_SET_FILE = 0x09; + + /* IF YOU ADD A NEW OPCODE, increase OPCODE_BASE */ + + /* + * "special opcode" configuration, essentially what's found in + * the line number program header in DWARFv3, Section 6.2.4 + */ + + /** the smallest value a special opcode can take */ + static final int DBG_FIRST_SPECIAL = 0x0a; + static final int DBG_LINE_BASE = -4; + static final int DBG_LINE_RANGE = 15; + // MIN_INSN_LENGTH is always 1 +} diff --git a/dx/src/com/android/jack/dx/dex/file/DebugInfoDecoder.java b/dx/src/com/android/jack/dx/dex/file/DebugInfoDecoder.java new file mode 100644 index 00000000..98ebc080 --- /dev/null +++ b/dx/src/com/android/jack/dx/dex/file/DebugInfoDecoder.java @@ -0,0 +1,597 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.dex.file; + +import com.android.jack.dx.dex.code.DalvCode; +import com.android.jack.dx.dex.code.DalvInsnList; +import com.android.jack.dx.dex.code.LocalList; +import com.android.jack.dx.dex.code.PositionList; +import com.android.jack.dx.io.ClassDef; +import com.android.jack.dx.rop.cst.CstMethodRef; +import com.android.jack.dx.rop.cst.CstString; +import com.android.jack.dx.rop.type.Prototype; +import com.android.jack.dx.rop.type.StdTypeList; +import com.android.jack.dx.rop.type.Type; +import com.android.jack.dx.util.ByteArrayByteInput; +import com.android.jack.dx.util.ByteInput; +import com.android.jack.dx.util.ExceptionWithContext; +import com.android.jack.dx.util.Leb128Utils; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import static com.android.jack.dx.dex.file.DebugInfoConstants.*; + +/** + * A decoder for the dex debug info state machine format. + * This code exists mostly as a reference implementation and test for + * for the {@code DebugInfoEncoder} + */ +public class DebugInfoDecoder { + /** encoded debug info */ + private final ByteInput encoded; + + /** positions decoded */ + private final ArrayList positions; + + /** locals decoded */ + private final ArrayList locals; + + /** indexed by register, the last local variable live in a reg */ + private final LocalEntry[] lastEntryForReg; + + /** method descriptor of method this debug info is for */ + private final Prototype desc; + + /** true if method is static */ + private final boolean isStatic; + + /** + * register size, in register units, of the register space + * used by this method + */ + private final int regSize; + + /** current decoding state: line number */ + private int line = 1; + + /** current decoding state: bytecode address */ + private int address = 0; + + /** string index of the string "this" */ + private final int thisStringIdx; + + /** + * Constructs an instance. + * + * @param encoded encoded debug info + * @param regSize register size, in register units, of the register space + * used by this method + * @param isStatic true if method is static + * @param ref method descriptor of method this debug info is for + * @param file dex file this debug info will be stored in + */ + DebugInfoDecoder(byte[] encoded, int regSize, + boolean isStatic, CstMethodRef ref, DexFile file) { + this(new ByteArrayByteInput(encoded), regSize, isStatic, ref.getPrototype(), + extractThisIdx(file)); + } + + /** + * Constructs an instance. + * + * @param encoded encoded debug info + * @param regSize register size, in register units, of the register space + * used by this method + * @param isStatic true if method is static + * @param desc method descriptor of method this debug info is for + * @param thisIdx string index of the string "this" or -1 if the string does not exists in the + * dex. + */ + public DebugInfoDecoder(ByteInput encoded, int regSize, + boolean isStatic, Prototype desc, int thisIdx) { + if (encoded == null) { + throw new NullPointerException("encoded == null"); + } + + this.encoded = encoded; + this.isStatic = isStatic; + this.desc = desc; + this.regSize = regSize; + + positions = new ArrayList(); + locals = new ArrayList(); + lastEntryForReg = new LocalEntry[regSize]; + + thisStringIdx = thisIdx; + } + + /** + * An entry in the resulting postions table + */ + static public class PositionEntry { + /** bytecode address */ + public int address; + + /** line number */ + public int line; + + public PositionEntry(int address, int line) { + this.address = address; + this.line = line; + } + } + + /** + * An entry in the resulting locals table + */ + static public class LocalEntry { + /** address of event */ + public int address; + + /** {@code true} iff it's a local start */ + public boolean isStart; + + /** register number */ + public int reg; + + /** index of name in strings table */ + public int nameIndex; + + /** index of type in types table */ + public int typeIndex; + + /** index of type signature in strings table */ + public int signatureIndex; + + public LocalEntry(int address, boolean isStart, int reg, int nameIndex, + int typeIndex, int signatureIndex) { + this.address = address; + this.isStart = isStart; + this.reg = reg; + this.nameIndex = nameIndex; + this.typeIndex = typeIndex; + this.signatureIndex = signatureIndex; + } + + public String toString() { + return String.format("[%x %s v%d %04x %04x %04x]", + address, isStart ? "start" : "end", reg, + nameIndex, typeIndex, signatureIndex); + } + } + + /** + * Gets the decoded positions list. + * Valid after calling {@code decode}. + * + * @return positions list in ascending address order. + */ + public List getPositionList() { + return positions; + } + + /** + * Gets the decoded locals list, in ascending start-address order. + * Valid after calling {@code decode}. + * + * @return locals list in ascending address order. + */ + public List getLocals() { + return locals; + } + + /** + * Decodes the debug info sequence. + */ + public void decode() { + try { + decode0(); + } catch (Exception ex) { + throw ExceptionWithContext.withContext(ex, + "...while decoding debug info"); + } + } + + /** + * Reads a string index. String indicies are offset by 1, and a 0 value + * in the stream (-1 as returned by this method) means "null" + * + * @return index into file's string ids table, -1 means null + * @throws IOException + */ + private int readStringIndex(ByteInput bs) throws IOException { + int offsetIndex = Leb128Utils.readUnsignedLeb128(bs); + + return offsetIndex - 1; + } + + /** + * Gets the register that begins the method's parameter range (including + * the 'this' parameter for non-static methods). The range continues until + * {@code regSize} + * + * @return register as noted above. + */ + private int getParamBase() { + return regSize + - desc.getParameterTypes().getWordCount() - (isStatic? 0 : 1); + } + + private void decode0() throws IOException { + line = Leb128Utils.readUnsignedLeb128(encoded); + int szParams = Leb128Utils.readUnsignedLeb128(encoded); + StdTypeList params = desc.getParameterTypes(); + int curReg = getParamBase(); + + if (szParams != params.size()) { + throw new RuntimeException( + "Mismatch between parameters_size and prototype"); + } + + if (!isStatic) { + // Start off with implicit 'this' entry + LocalEntry thisEntry = + new LocalEntry(0, true, curReg, thisStringIdx, ClassDef.NO_INDEX, + ClassDef.NO_INDEX); + locals.add(thisEntry); + lastEntryForReg[curReg] = thisEntry; + curReg++; + } + + for (int i = 0; i < szParams; i++) { + Type paramType = params.getType(i); + LocalEntry le; + + int nameIdx = readStringIndex(encoded); + + if (nameIdx == -1) { + /* + * Unnamed parameter; often but not always filled in by an + * extended start op after the prologue + */ + le = new LocalEntry(0, true, curReg, ClassDef.NO_INDEX, ClassDef.NO_INDEX, + ClassDef.NO_INDEX); + } else { + // TODO: Final 0 should be idx of paramType.getDescriptor(). + le = new LocalEntry(0, true, curReg, nameIdx, ClassDef.NO_INDEX, ClassDef.NO_INDEX); + } + + locals.add(le); + lastEntryForReg[curReg] = le; + curReg += paramType.getCategory(); + } + + for (;;) { + int opcode = encoded.readByte() & 0xff; + + switch (opcode) { + case DBG_START_LOCAL: { + int reg = Leb128Utils.readUnsignedLeb128(encoded); + int nameIdx = readStringIndex(encoded); + int typeIdx = readStringIndex(encoded); + LocalEntry le = new LocalEntry( + address, true, reg, nameIdx, typeIdx, ClassDef.NO_INDEX); + + locals.add(le); + lastEntryForReg[reg] = le; + } + break; + + case DBG_START_LOCAL_EXTENDED: { + int reg = Leb128Utils.readUnsignedLeb128(encoded); + int nameIdx = readStringIndex(encoded); + int typeIdx = readStringIndex(encoded); + int sigIdx = readStringIndex(encoded); + LocalEntry le = new LocalEntry( + address, true, reg, nameIdx, typeIdx, sigIdx); + + locals.add(le); + lastEntryForReg[reg] = le; + } + break; + + case DBG_RESTART_LOCAL: { + int reg = Leb128Utils.readUnsignedLeb128(encoded); + LocalEntry prevle; + LocalEntry le; + + try { + prevle = lastEntryForReg[reg]; + + if (prevle.isStart) { + throw new RuntimeException("nonsensical " + + "RESTART_LOCAL on live register v" + + reg); + } + + le = new LocalEntry(address, true, reg, + prevle.nameIndex, prevle.typeIndex, prevle.signatureIndex); + } catch (NullPointerException ex) { + throw new RuntimeException( + "Encountered RESTART_LOCAL on new v" + reg); + } + + locals.add(le); + lastEntryForReg[reg] = le; + } + break; + + case DBG_END_LOCAL: { + int reg = Leb128Utils.readUnsignedLeb128(encoded); + LocalEntry prevle; + LocalEntry le; + + try { + prevle = lastEntryForReg[reg]; + + if (!prevle.isStart) { + throw new RuntimeException("nonsensical " + + "END_LOCAL on dead register v" + reg); + } + + le = new LocalEntry(address, false, reg, + prevle.nameIndex, prevle.typeIndex, + prevle.signatureIndex); + } catch (NullPointerException ex) { + throw new RuntimeException( + "Encountered END_LOCAL on new v" + reg); + } + + locals.add(le); + lastEntryForReg[reg] = le; + } + break; + + case DBG_END_SEQUENCE: + // all done + return; + + case DBG_ADVANCE_PC: + address += Leb128Utils.readUnsignedLeb128(encoded); + break; + + case DBG_ADVANCE_LINE: + line += Leb128Utils.readSignedLeb128(encoded); + break; + + case DBG_SET_PROLOGUE_END: + //TODO do something with this. + break; + + case DBG_SET_EPILOGUE_BEGIN: + //TODO do something with this. + break; + + case DBG_SET_FILE: + //TODO do something with this. + break; + + default: + if (opcode < DBG_FIRST_SPECIAL) { + throw new RuntimeException( + "Invalid extended opcode encountered " + + opcode); + } + + int adjopcode = opcode - DBG_FIRST_SPECIAL; + + address += adjopcode / DBG_LINE_RANGE; + line += DBG_LINE_BASE + (adjopcode % DBG_LINE_RANGE); + + positions.add(new PositionEntry(address, line)); + break; + + } + } + } + + /** + * Validates an encoded debug info stream against data used to encode it, + * throwing an exception if they do not match. Used to validate the + * encoder. + * + * @param info encoded debug info + * @param file {@code non-null;} file to refer to during decoding + * @param ref {@code non-null;} method whose info is being decoded + * @param code {@code non-null;} original code object that was encoded + * @param isStatic whether the method is static + */ + public static void validateEncode(byte[] info, DexFile file, + CstMethodRef ref, DalvCode code, boolean isStatic) { + PositionList pl = code.getPositions(); + LocalList ll = code.getLocals(); + DalvInsnList insns = code.getInsns(); + int countRegisters = insns.getRegistersSize(); + + try { + validateEncode0(info, countRegisters, + isStatic, ref, file, pl, ll); + } catch (RuntimeException ex) { + System.err.println("instructions:"); + insns.debugPrint(System.err, " ", true); + System.err.println("local list:"); + ll.debugPrint(System.err, " "); + throw ExceptionWithContext.withContext(ex, + "while processing " + ref.toHuman()); + } + } + + private static void validateEncode0(byte[] info, + int countRegisters, boolean isStatic, CstMethodRef ref, + DexFile file, PositionList pl, LocalList ll) { + DebugInfoDecoder decoder + = new DebugInfoDecoder(info, countRegisters, + isStatic, ref, file); + + decoder.decode(); + + /* + * Go through the decoded position entries, matching up + * with original entries. + */ + + List decodedEntries = decoder.getPositionList(); + + if (decodedEntries.size() != pl.size()) { + throw new RuntimeException( + "Decoded positions table not same size was " + + decodedEntries.size() + " expected " + pl.size()); + } + + for (PositionEntry entry : decodedEntries) { + boolean found = false; + for (int i = pl.size() - 1; i >= 0; i--) { + PositionList.Entry ple = pl.get(i); + + if (entry.line == ple.getPosition().getLine() + && entry.address == ple.getAddress()) { + found = true; + break; + } + } + + if (!found) { + throw new RuntimeException ("Could not match position entry: " + + entry.address + ", " + entry.line); + } + } + + /* + * Go through the original local list, in order, matching up + * with decoded entries. + */ + + List decodedLocals = decoder.getLocals(); + int thisStringIdx = decoder.thisStringIdx; + int decodedSz = decodedLocals.size(); + int paramBase = decoder.getParamBase(); + + /* + * Preflight to fill in any parameters that were skipped in + * the prologue (including an implied "this") but then + * identified by full signature. + */ + for (int i = 0; i < decodedSz; i++) { + LocalEntry entry = decodedLocals.get(i); + int idx = entry.nameIndex; + + if ((idx < 0) || (idx == thisStringIdx)) { + for (int j = i + 1; j < decodedSz; j++) { + LocalEntry e2 = decodedLocals.get(j); + if (e2.address != 0) { + break; + } + if ((entry.reg == e2.reg) && e2.isStart) { + decodedLocals.set(i, e2); + decodedLocals.remove(j); + decodedSz--; + break; + } + } + } + } + + int origSz = ll.size(); + int decodeAt = 0; + boolean problem = false; + + for (int i = 0; i < origSz; i++) { + LocalList.Entry origEntry = ll.get(i); + + if (origEntry.getDisposition() + == LocalList.Disposition.END_REPLACED) { + /* + * The encoded list doesn't represent replacements, so + * ignore them for the sake of comparison. + */ + continue; + } + + LocalEntry decodedEntry; + + do { + decodedEntry = decodedLocals.get(decodeAt); + if (decodedEntry.nameIndex >= 0) { + break; + } + /* + * A negative name index means this is an anonymous + * parameter, and we shouldn't expect to see it in the + * original list. So, skip it. + */ + decodeAt++; + } while (decodeAt < decodedSz); + + int decodedAddress = decodedEntry.address; + + if (decodedEntry.reg != origEntry.getRegister()) { + System.err.println("local register mismatch at orig " + i + + " / decoded " + decodeAt); + problem = true; + break; + } + + if (decodedEntry.isStart != origEntry.isStart()) { + System.err.println("local start/end mismatch at orig " + i + + " / decoded " + decodeAt); + problem = true; + break; + } + + /* + * The secondary check here accounts for the fact that a + * parameter might not be marked as starting at 0 in the + * original list. + */ + if ((decodedAddress != origEntry.getAddress()) + && !((decodedAddress == 0) + && (decodedEntry.reg >= paramBase))) { + System.err.println("local address mismatch at orig " + i + + " / decoded " + decodeAt); + problem = true; + break; + } + + decodeAt++; + } + + if (problem) { + System.err.println("decoded locals:"); + for (LocalEntry e : decodedLocals) { + System.err.println(" " + e); + } + throw new RuntimeException("local table problem"); + } + } + + private static int extractThisIdx(DexFile file) { + int idx = -1; + + try { + idx = file.getStringIds().indexOf(new CstString("this")); + } catch (IllegalArgumentException ex) { + /* + * Silently tolerate not finding "this". It just means that + * no method has local variable info that looks like + * a standard instance method. + */ + } + return idx; + } +} diff --git a/dx/src/com/android/jack/dx/dex/file/DebugInfoEncoder.java b/dx/src/com/android/jack/dx/dex/file/DebugInfoEncoder.java new file mode 100644 index 00000000..026d0c05 --- /dev/null +++ b/dx/src/com/android/jack/dx/dex/file/DebugInfoEncoder.java @@ -0,0 +1,920 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.dex.file; + +import com.android.jack.dx.dex.code.LocalList; +import com.android.jack.dx.dex.code.PositionList; +import com.android.jack.dx.rop.code.RegisterSpec; +import com.android.jack.dx.rop.code.SourcePosition; +import com.android.jack.dx.rop.cst.CstMethodRef; +import com.android.jack.dx.rop.cst.CstString; +import com.android.jack.dx.rop.cst.CstType; +import com.android.jack.dx.rop.type.Prototype; +import com.android.jack.dx.rop.type.StdTypeList; +import com.android.jack.dx.rop.type.Type; +import com.android.jack.dx.util.AnnotatedOutput; +import com.android.jack.dx.util.ByteArrayAnnotatedOutput; +import com.android.jack.dx.util.ExceptionWithContext; + +import java.io.IOException; +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.BitSet; + +import static com.android.jack.dx.dex.file.DebugInfoConstants.*; + +/** + * An encoder for the dex debug info state machine format. The format + * for each method enrty is as follows: + *
    + *
  1. signed LEB128: initial value for line register. + *
  2. n instances of signed LEB128: string indicies (offset by 1) + * for each method argument in left-to-right order + * with {@code this} excluded. A value of '0' indicates "no name" + *
  3. A sequence of special or normal opcodes as defined in + * {@code DebugInfoConstants}. + *
  4. A single terminating {@code OP_END_SEQUENCE} + *
+ */ +public final class DebugInfoEncoder { + private static final boolean DEBUG = false; + + /** {@code null-ok;} positions (line numbers) to encode */ + private final PositionList positions; + + /** {@code null-ok;} local variables to encode */ + private final LocalList locals; + + private final ByteArrayAnnotatedOutput output; + private final DexFile file; + private final int codeSize; + private final int regSize; + + private final Prototype desc; + private final boolean isStatic; + + /** current encoding state: bytecode address */ + private int address = 0; + + /** current encoding state: line number */ + private int line = 1; + + /** + * if non-null: the output to write annotations to. No normal + * output is written to this. + */ + private AnnotatedOutput annotateTo; + + /** if non-null: another possible output for annotations */ + private PrintWriter debugPrint; + + /** if non-null: the prefix for each annotation or debugPrint line */ + private String prefix; + + /** true if output should be consumed during annotation */ + private boolean shouldConsume; + + /** indexed by register; last local alive in register */ + private final LocalList.Entry[] lastEntryForReg; + + /** + * Creates an instance. + * + * @param positions {@code null-ok;} positions (line numbers) to encode + * @param locals {@code null-ok;} local variables to encode + * @param file {@code null-ok;} may only be {@code null} if simply using + * this class to do a debug print + * @param codeSize + * @param regSize + * @param isStatic + * @param ref + */ + public DebugInfoEncoder(PositionList positions, LocalList locals, + DexFile file, int codeSize, int regSize, + boolean isStatic, CstMethodRef ref) { + this.positions = positions; + this.locals = locals; + this.file = file; + this.desc = ref.getPrototype(); + this.isStatic = isStatic; + this.codeSize = codeSize; + this.regSize = regSize; + + output = new ByteArrayAnnotatedOutput(); + lastEntryForReg = new LocalList.Entry[regSize]; + } + + /** + * Annotates or writes a message to the {@code debugPrint} writer + * if applicable. + * + * @param length the number of bytes associated with this message + * @param message the message itself + */ + private void annotate(int length, String message) { + if (prefix != null) { + message = prefix + message; + } + + if (annotateTo != null) { + annotateTo.annotate(shouldConsume ? length : 0, message); + } + + if (debugPrint != null) { + debugPrint.println(message); + } + } + + /** + * Converts this (PositionList, LocalList) pair into a state machine + * sequence. + * + * @return {@code non-null;} encoded byte sequence without padding and + * terminated with a {@code 0x00} byte + */ + public byte[] convert() { + try { + byte[] ret; + ret = convert0(); + + if (DEBUG) { + for (int i = 0 ; i < ret.length; i++) { + System.err.printf("byte %02x\n", (0xff & ret[i])); + } + } + + return ret; + } catch (IOException ex) { + throw ExceptionWithContext + .withContext(ex, "...while encoding debug info"); + } + } + + /** + * Converts and produces annotations on a stream. Does not write + * actual bits to the {@code AnnotatedOutput}. + * + * @param prefix {@code null-ok;} prefix to attach to each line of output + * @param debugPrint {@code null-ok;} if specified, an alternate output for + * annotations + * @param out {@code null-ok;} if specified, where annotations should go + * @param consume whether to claim to have consumed output for + * {@code out} + * @return {@code non-null;} encoded output + */ + public byte[] convertAndAnnotate(String prefix, PrintWriter debugPrint, + AnnotatedOutput out, boolean consume) { + this.prefix = prefix; + this.debugPrint = debugPrint; + annotateTo = out; + shouldConsume = consume; + + byte[] result = convert(); + + return result; + } + + private byte[] convert0() throws IOException { + ArrayList sortedPositions = buildSortedPositions(); + ArrayList methodArgs = extractMethodArguments(); + + emitHeader(sortedPositions, methodArgs); + + // TODO: Make this mark be the actual prologue end. + output.writeByte(DBG_SET_PROLOGUE_END); + + if (annotateTo != null || debugPrint != null) { + annotate(1, String.format("%04x: prologue end",address)); + } + + int positionsSz = sortedPositions.size(); + int localsSz = locals.size(); + + // Current index in sortedPositions + int curPositionIdx = 0; + // Current index in locals + int curLocalIdx = 0; + + for (;;) { + /* + * Emit any information for the current address. + */ + + curLocalIdx = emitLocalsAtAddress(curLocalIdx); + curPositionIdx = + emitPositionsAtAddress(curPositionIdx, sortedPositions); + + /* + * Figure out what the next important address is. + */ + + int nextAddrL = Integer.MAX_VALUE; // local variable + int nextAddrP = Integer.MAX_VALUE; // position (line number) + + if (curLocalIdx < localsSz) { + nextAddrL = locals.get(curLocalIdx).getAddress(); + } + + if (curPositionIdx < positionsSz) { + nextAddrP = sortedPositions.get(curPositionIdx).getAddress(); + } + + int next = Math.min(nextAddrP, nextAddrL); + + // No next important address == done. + if (next == Integer.MAX_VALUE) { + break; + } + + /* + * If the only work remaining are local ends at the end of the + * block, stop here. Those are implied anyway. + */ + if (next == codeSize + && nextAddrL == Integer.MAX_VALUE + && nextAddrP == Integer.MAX_VALUE) { + break; + } + + if (next == nextAddrP) { + // Combined advance PC + position entry + emitPosition(sortedPositions.get(curPositionIdx++)); + } else { + emitAdvancePc(next - address); + } + } + + emitEndSequence(); + + return output.toByteArray(); + } + + /** + * Emits all local variable activity that occurs at the current + * {@link #address} starting at the given index into {@code + * locals} and including all subsequent activity at the same + * address. + * + * @param curLocalIdx Current index in locals + * @return new value for {@code curLocalIdx} + * @throws IOException + */ + private int emitLocalsAtAddress(int curLocalIdx) + throws IOException { + int sz = locals.size(); + + // TODO: Don't emit ends implied by starts. + + while ((curLocalIdx < sz) + && (locals.get(curLocalIdx).getAddress() == address)) { + LocalList.Entry entry = locals.get(curLocalIdx++); + int reg = entry.getRegister(); + LocalList.Entry prevEntry = lastEntryForReg[reg]; + + if (entry == prevEntry) { + /* + * Here we ignore locals entries for parameters, + * which have already been represented and placed in the + * lastEntryForReg array. + */ + continue; + } + + // At this point we have a new entry one way or another. + lastEntryForReg[reg] = entry; + + if (entry.isStart()) { + if ((prevEntry != null) && entry.matches(prevEntry)) { + /* + * The previous local in this register has the same + * name and type as the one being introduced now, so + * use the more efficient "restart" form. + */ + if (prevEntry.isStart()) { + /* + * We should never be handed a start when a + * a matching local is already active. + */ + throw new RuntimeException("shouldn't happen"); + } + emitLocalRestart(entry); + } else { + emitLocalStart(entry); + } + } else { + /* + * Only emit a local end if it is *not* due to a direct + * replacement. Direct replacements imply an end of the + * previous local in the same register. + * + * TODO: Make sure the runtime can deal with implied + * local ends from category-2 interactions, and when so, + * also stop emitting local ends for those cases. + */ + if (entry.getDisposition() + != LocalList.Disposition.END_REPLACED) { + emitLocalEnd(entry); + } + } + } + + return curLocalIdx; + } + + /** + * Emits all positions that occur at the current {@code address} + * + * @param curPositionIdx Current index in sortedPositions + * @param sortedPositions positions, sorted by ascending address + * @return new value for {@code curPositionIdx} + * @throws IOException + */ + private int emitPositionsAtAddress(int curPositionIdx, + ArrayList sortedPositions) + throws IOException { + int positionsSz = sortedPositions.size(); + while ((curPositionIdx < positionsSz) + && (sortedPositions.get(curPositionIdx).getAddress() + == address)) { + emitPosition(sortedPositions.get(curPositionIdx++)); + } + return curPositionIdx; + } + + /** + * Emits the header sequence, which consists of LEB128-encoded initial + * line number and string indicies for names of all non-"this" arguments. + * + * @param sortedPositions positions, sorted by ascending address + * @param methodArgs local list entries for method argumens arguments, + * in left-to-right order omitting "this" + * @throws IOException + */ + private void emitHeader(ArrayList sortedPositions, + ArrayList methodArgs) throws IOException { + boolean annotate = (annotateTo != null) || (debugPrint != null); + int mark = output.getCursor(); + + // Start by initializing the line number register. + if (sortedPositions.size() > 0) { + PositionList.Entry entry = sortedPositions.get(0); + line = entry.getPosition().getLine(); + } + output.writeUleb128(line); + + if (annotate) { + annotate(output.getCursor() - mark, "line_start: " + line); + } + + int curParam = getParamBase(); + // paramTypes will not include 'this' + StdTypeList paramTypes = desc.getParameterTypes(); + int szParamTypes = paramTypes.size(); + + /* + * Initialize lastEntryForReg to have an initial + * entry for the 'this' pointer. + */ + if (!isStatic) { + for (LocalList.Entry arg : methodArgs) { + if (curParam == arg.getRegister()) { + lastEntryForReg[curParam] = arg; + break; + } + } + curParam++; + } + + // Write out the number of parameter entries that will follow. + mark = output.getCursor(); + output.writeUleb128(szParamTypes); + + if (annotate) { + annotate(output.getCursor() - mark, + String.format("parameters_size: %04x", szParamTypes)); + } + + /* + * Then emit the string indicies of all the method parameters. + * Note that 'this', if applicable, is excluded. + */ + for (int i = 0; i < szParamTypes; i++) { + Type pt = paramTypes.get(i); + LocalList.Entry found = null; + + mark = output.getCursor(); + + for (LocalList.Entry arg : methodArgs) { + if (curParam == arg.getRegister()) { + found = arg; + + if (arg.getSignature() != null) { + /* + * Parameters with signatures will be re-emitted + * in complete as LOCAL_START_EXTENDED's below. + */ + emitStringIndex(null); + } else { + emitStringIndex(arg.getName()); + } + lastEntryForReg[curParam] = arg; + + break; + } + } + + if (found == null) { + /* + * Emit a null symbol for "unnamed." This is common + * for, e.g., synthesized methods and inner-class + * this$0 arguments. + */ + emitStringIndex(null); + } + + if (annotate) { + String parameterName + = (found == null || found.getSignature() != null) + ? "" : found.getName().toHuman(); + annotate(output.getCursor() - mark, + "parameter " + parameterName + " " + + RegisterSpec.PREFIX + curParam); + } + + curParam += pt.getCategory(); + } + + /* + * If anything emitted above has a type signature, emit it again as + * a LOCAL_RESTART_EXTENDED + */ + + for (LocalList.Entry arg : lastEntryForReg) { + if (arg == null) { + continue; + } + + CstString signature = arg.getSignature(); + + if (signature != null) { + emitLocalStartExtended(arg); + } + } + } + + /** + * Builds a list of position entries, sorted by ascending address. + * + * @return A sorted positions list + */ + private ArrayList buildSortedPositions() { + int sz = (positions == null) ? 0 : positions.size(); + ArrayList result = new ArrayList(sz); + + for (int i = 0; i < sz; i++) { + result.add(positions.get(i)); + } + + // Sort ascending by address. + Collections.sort (result, new Comparator() { + public int compare (PositionList.Entry a, PositionList.Entry b) { + return a.getAddress() - b.getAddress(); + } + + public boolean equals (Object obj) { + return obj == this; + } + }); + return result; + } + + /** + * Gets the register that begins the method's parameter range (including + * the 'this' parameter for non-static methods). The range continues until + * {@code regSize} + * + * @return register as noted above + */ + private int getParamBase() { + return regSize + - desc.getParameterTypes().getWordCount() - (isStatic? 0 : 1); + } + + /** + * Extracts method arguments from a locals list. These will be collected + * from the input list and sorted by ascending register in the + * returned list. + * + * @return list of non-{@code this} method argument locals, + * sorted by ascending register + */ + private ArrayList extractMethodArguments() { + ArrayList result + = new ArrayList(desc.getParameterTypes().size()); + int argBase = getParamBase(); + BitSet seen = new BitSet(regSize - argBase); + int sz = locals.size(); + + for (int i = 0; i < sz; i++) { + LocalList.Entry e = locals.get(i); + int reg = e.getRegister(); + + if (reg < argBase) { + continue; + } + + // only the lowest-start-address entry is included. + if (seen.get(reg - argBase)) { + continue; + } + + seen.set(reg - argBase); + result.add(e); + } + + // Sort by ascending register. + Collections.sort(result, new Comparator() { + public int compare(LocalList.Entry a, LocalList.Entry b) { + return a.getRegister() - b.getRegister(); + } + + public boolean equals(Object obj) { + return obj == this; + } + }); + + return result; + } + + /** + * Returns a string representation of this LocalList entry that is + * appropriate for emitting as an annotation. + * + * @param e {@code non-null;} entry + * @return {@code non-null;} annotation string + */ + private String entryAnnotationString(LocalList.Entry e) { + StringBuilder sb = new StringBuilder(); + + sb.append(RegisterSpec.PREFIX); + sb.append(e.getRegister()); + sb.append(' '); + + CstString name = e.getName(); + if (name == null) { + sb.append("null"); + } else { + sb.append(name.toHuman()); + } + sb.append(' '); + + CstType type = e.getType(); + if (type == null) { + sb.append("null"); + } else { + sb.append(type.toHuman()); + } + + CstString signature = e.getSignature(); + + if (signature != null) { + sb.append(' '); + sb.append(signature.toHuman()); + } + + return sb.toString(); + } + + /** + * Emits a {@link DebugInfoConstants#DBG_RESTART_LOCAL DBG_RESTART_LOCAL} + * sequence. + * + * @param entry entry associated with this restart + * @throws IOException + */ + private void emitLocalRestart(LocalList.Entry entry) + throws IOException { + + int mark = output.getCursor(); + + output.writeByte(DBG_RESTART_LOCAL); + emitUnsignedLeb128(entry.getRegister()); + + if (annotateTo != null || debugPrint != null) { + annotate(output.getCursor() - mark, + String.format("%04x: +local restart %s", + address, entryAnnotationString(entry))); + } + + if (DEBUG) { + System.err.println("emit local restart"); + } + } + + /** + * Emits a string index as an unsigned LEB128. The actual value written + * is shifted by 1, so that the '0' value is reserved for "null". The + * null symbol is used in some cases by the parameter name list + * at the beginning of the sequence. + * + * @param string {@code null-ok;} string to emit + * @throws IOException + */ + private void emitStringIndex(CstString string) throws IOException { + if ((string == null) || (file == null)) { + output.writeUleb128(0); + } else { + output.writeUleb128( + 1 + file.getStringIds().indexOf(string)); + } + + if (DEBUG) { + System.err.printf("Emit string %s\n", + string == null ? "" : string.toQuoted()); + } + } + + /** + * Emits a type index as an unsigned LEB128. The actual value written + * is shifted by 1, so that the '0' value is reserved for "null". + * + * @param type {@code null-ok;} type to emit + * @throws IOException + */ + private void emitTypeIndex(CstType type) throws IOException { + if ((type == null) || (file == null)) { + output.writeUleb128(0); + } else { + output.writeUleb128( + 1 + file.getTypeIds().indexOf(type)); + } + + if (DEBUG) { + System.err.printf("Emit type %s\n", + type == null ? "" : type.toHuman()); + } + } + + /** + * Emits a {@link DebugInfoConstants#DBG_START_LOCAL DBG_START_LOCAL} or + * {@link DebugInfoConstants#DBG_START_LOCAL_EXTENDED + * DBG_START_LOCAL_EXTENDED} sequence. + * + * @param entry entry to emit + * @throws IOException + */ + private void emitLocalStart(LocalList.Entry entry) + throws IOException { + + if (entry.getSignature() != null) { + emitLocalStartExtended(entry); + return; + } + + int mark = output.getCursor(); + + output.writeByte(DBG_START_LOCAL); + + emitUnsignedLeb128(entry.getRegister()); + emitStringIndex(entry.getName()); + emitTypeIndex(entry.getType()); + + if (annotateTo != null || debugPrint != null) { + annotate(output.getCursor() - mark, + String.format("%04x: +local %s", address, + entryAnnotationString(entry))); + } + + if (DEBUG) { + System.err.println("emit local start"); + } + } + + /** + * Emits a {@link DebugInfoConstants#DBG_START_LOCAL_EXTENDED + * DBG_START_LOCAL_EXTENDED} sequence. + * + * @param entry entry to emit + * @throws IOException + */ + private void emitLocalStartExtended(LocalList.Entry entry) + throws IOException { + + int mark = output.getCursor(); + + output.writeByte(DBG_START_LOCAL_EXTENDED); + + emitUnsignedLeb128(entry.getRegister()); + emitStringIndex(entry.getName()); + emitTypeIndex(entry.getType()); + emitStringIndex(entry.getSignature()); + + if (annotateTo != null || debugPrint != null) { + annotate(output.getCursor() - mark, + String.format("%04x: +localx %s", address, + entryAnnotationString(entry))); + } + + if (DEBUG) { + System.err.println("emit local start"); + } + } + + /** + * Emits a {@link DebugInfoConstants#DBG_END_LOCAL DBG_END_LOCAL} sequence. + * + * @param entry {@code entry non-null;} entry associated with end. + * @throws IOException + */ + private void emitLocalEnd(LocalList.Entry entry) + throws IOException { + + int mark = output.getCursor(); + + output.writeByte(DBG_END_LOCAL); + output.writeUleb128(entry.getRegister()); + + if (annotateTo != null || debugPrint != null) { + annotate(output.getCursor() - mark, + String.format("%04x: -local %s", address, + entryAnnotationString(entry))); + } + + if (DEBUG) { + System.err.println("emit local end"); + } + } + + /** + * Emits the necessary byte sequences to emit the given position table + * entry. This will typically be a single special opcode, although + * it may also require DBG_ADVANCE_PC or DBG_ADVANCE_LINE. + * + * @param entry position entry to emit. + * @throws IOException + */ + private void emitPosition(PositionList.Entry entry) + throws IOException { + + SourcePosition pos = entry.getPosition(); + int newLine = pos.getLine(); + int newAddress = entry.getAddress(); + + int opcode; + + int deltaLines = newLine - line; + int deltaAddress = newAddress - address; + + if (deltaAddress < 0) { + throw new RuntimeException( + "Position entries must be in ascending address order"); + } + + if ((deltaLines < DBG_LINE_BASE) + || (deltaLines > (DBG_LINE_BASE + DBG_LINE_RANGE -1))) { + emitAdvanceLine(deltaLines); + deltaLines = 0; + } + + opcode = computeOpcode (deltaLines, deltaAddress); + + if ((opcode & ~0xff) > 0) { + emitAdvancePc(deltaAddress); + deltaAddress = 0; + opcode = computeOpcode (deltaLines, deltaAddress); + + if ((opcode & ~0xff) > 0) { + emitAdvanceLine(deltaLines); + deltaLines = 0; + opcode = computeOpcode (deltaLines, deltaAddress); + } + } + + output.writeByte(opcode); + + line += deltaLines; + address += deltaAddress; + + if (annotateTo != null || debugPrint != null) { + annotate(1, + String.format("%04x: line %d", address, line)); + } + } + + /** + * Computes a special opcode that will encode the given position change. + * If the return value is > 0xff, then the request cannot be fulfilled. + * Essentially the same as described in "DWARF Debugging Format Version 3" + * section 6.2.5.1. + * + * @param deltaLines {@code >= DBG_LINE_BASE, <= DBG_LINE_BASE + + * DBG_LINE_RANGE;} the line change to encode + * @param deltaAddress {@code >= 0;} the address change to encode + * @return {@code <= 0xff} if in range, otherwise parameters are out + * of range + */ + private static int computeOpcode(int deltaLines, int deltaAddress) { + if (deltaLines < DBG_LINE_BASE + || deltaLines > (DBG_LINE_BASE + DBG_LINE_RANGE -1)) { + + throw new RuntimeException("Parameter out of range"); + } + + return (deltaLines - DBG_LINE_BASE) + + (DBG_LINE_RANGE * deltaAddress) + DBG_FIRST_SPECIAL; + } + + /** + * Emits an {@link DebugInfoConstants#DBG_ADVANCE_LINE DBG_ADVANCE_LINE} + * sequence. + * + * @param deltaLines amount to change line number register by + * @throws IOException + */ + private void emitAdvanceLine(int deltaLines) throws IOException { + int mark = output.getCursor(); + + output.writeByte(DBG_ADVANCE_LINE); + output.writeSleb128(deltaLines); + line += deltaLines; + + if (annotateTo != null || debugPrint != null) { + annotate(output.getCursor() - mark, + String.format("line = %d", line)); + } + + if (DEBUG) { + System.err.printf("Emitting advance_line for %d\n", deltaLines); + } + } + + /** + * Emits an {@link DebugInfoConstants#DBG_ADVANCE_PC DBG_ADVANCE_PC} + * sequence. + * + * @param deltaAddress {@code >= 0;} amount to change program counter by + * @throws IOException + */ + private void emitAdvancePc(int deltaAddress) throws IOException { + int mark = output.getCursor(); + + output.writeByte(DBG_ADVANCE_PC); + output.writeUleb128(deltaAddress); + address += deltaAddress; + + if (annotateTo != null || debugPrint != null) { + annotate(output.getCursor() - mark, + String.format("%04x: advance pc", address)); + } + + if (DEBUG) { + System.err.printf("Emitting advance_pc for %d\n", deltaAddress); + } + } + + /** + * Emits an unsigned LEB128 value. + * + * @param n {@code >= 0;} value to emit. Note that, although this can + * represent integers larger than Integer.MAX_VALUE, we currently don't + * allow that. + * @throws IOException + */ + private void emitUnsignedLeb128(int n) throws IOException { + // We'll never need the top end of the unsigned range anyway. + if (n < 0) { + throw new RuntimeException( + "Signed value where unsigned required: " + n); + } + + output.writeUleb128(n); + } + + /** + * Emits the {@link DebugInfoConstants#DBG_END_SEQUENCE DBG_END_SEQUENCE} + * bytecode. + */ + private void emitEndSequence() { + output.writeByte(DBG_END_SEQUENCE); + + if (annotateTo != null || debugPrint != null) { + annotate(1, "end sequence"); + } + } +} diff --git a/dx/src/com/android/jack/dx/dex/file/DebugInfoItem.java b/dx/src/com/android/jack/dx/dex/file/DebugInfoItem.java new file mode 100644 index 00000000..61db8d94 --- /dev/null +++ b/dx/src/com/android/jack/dx/dex/file/DebugInfoItem.java @@ -0,0 +1,194 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.dex.file; + +import com.android.jack.dx.dex.code.DalvCode; +import com.android.jack.dx.dex.code.DalvInsnList; +import com.android.jack.dx.dex.code.LocalList; +import com.android.jack.dx.dex.code.PositionList; +import com.android.jack.dx.rop.cst.CstMethodRef; +import com.android.jack.dx.util.AnnotatedOutput; +import com.android.jack.dx.util.ExceptionWithContext; + +import java.io.PrintWriter; + +public class DebugInfoItem extends OffsettedItem { + /** the required alignment for instances of this class */ + private static final int ALIGNMENT = 1; + + private static final boolean ENABLE_ENCODER_SELF_CHECK = false; + + /** {@code non-null;} the code this item represents */ + private final DalvCode code; + + private byte[] encoded; + + private final boolean isStatic; + private final CstMethodRef ref; + + public DebugInfoItem(DalvCode code, boolean isStatic, CstMethodRef ref) { + // We don't know the write size yet. + super (ALIGNMENT, -1); + + if (code == null) { + throw new NullPointerException("code == null"); + } + + this.code = code; + this.isStatic = isStatic; + this.ref = ref; + } + + /** {@inheritDoc} */ + @Override + public ItemType itemType() { + return ItemType.TYPE_DEBUG_INFO_ITEM; + } + + /** {@inheritDoc} */ + @Override + public void addContents(DexFile file) { + // No contents to add. + } + + /** {@inheritDoc} */ + @Override + protected void place0(Section addedTo, int offset) { + // Encode the data and note the size. + + try { + encoded = encode(addedTo.getFile(), null, null, null, false); + setWriteSize(encoded.length); + } catch (RuntimeException ex) { + throw ExceptionWithContext.withContext(ex, + "...while placing debug info for " + ref.toHuman()); + } + } + + /** {@inheritDoc} */ + @Override + public String toHuman() { + throw new RuntimeException("unsupported"); + } + + /** + * Writes annotations for the elements of this list, as + * zero-length. This is meant to be used for dumping this instance + * directly after a code dump (with the real local list actually + * existing elsewhere in the output). + * + * @param file {@code non-null;} the file to use for referencing other sections + * @param out {@code non-null;} where to annotate to + * @param prefix {@code null-ok;} prefix to attach to each line of output + */ + public void annotateTo(DexFile file, AnnotatedOutput out, String prefix) { + encode(file, prefix, null, out, false); + } + + /** + * Does a human-friendly dump of this instance. + * + * @param out {@code non-null;} where to dump + * @param prefix {@code non-null;} prefix to attach to each line of output + */ + public void debugPrint(PrintWriter out, String prefix) { + encode(null, prefix, out, null, false); + } + + /** {@inheritDoc} */ + @Override + protected void writeTo0(DexFile file, AnnotatedOutput out) { + if (out.annotates()) { + /* + * Re-run the encoder to generate the annotations, + * but write the bits from the original encode + */ + + out.annotate(offsetString() + " debug info"); + encode(file, null, null, out, true); + } + + out.write(encoded); + } + + /** + * Performs debug info encoding. + * + * @param file {@code null-ok;} file to refer to during encoding + * @param prefix {@code null-ok;} prefix to attach to each line of output + * @param debugPrint {@code null-ok;} if specified, an alternate output for + * annotations + * @param out {@code null-ok;} if specified, where annotations should go + * @param consume whether to claim to have consumed output for + * {@code out} + * @return {@code non-null;} the encoded array + */ + private byte[] encode(DexFile file, String prefix, PrintWriter debugPrint, + AnnotatedOutput out, boolean consume) { + byte[] result = encode0(file, prefix, debugPrint, out, consume); + + if (ENABLE_ENCODER_SELF_CHECK && (file != null)) { + try { + DebugInfoDecoder.validateEncode(result, file, ref, code, + isStatic); + } catch (RuntimeException ex) { + // Reconvert, annotating to System.err. + encode0(file, "", new PrintWriter(System.err, true), null, + false); + throw ex; + } + } + + return result; + } + + /** + * Helper for {@link #encode} to do most of the work. + * + * @param file {@code null-ok;} file to refer to during encoding + * @param prefix {@code null-ok;} prefix to attach to each line of output + * @param debugPrint {@code null-ok;} if specified, an alternate output for + * annotations + * @param out {@code null-ok;} if specified, where annotations should go + * @param consume whether to claim to have consumed output for + * {@code out} + * @return {@code non-null;} the encoded array + */ + private byte[] encode0(DexFile file, String prefix, PrintWriter debugPrint, + AnnotatedOutput out, boolean consume) { + PositionList positions = code.getPositions(); + LocalList locals = code.getLocals(); + DalvInsnList insns = code.getInsns(); + int codeSize = insns.codeSize(); + int regSize = insns.getRegistersSize(); + + DebugInfoEncoder encoder = + new DebugInfoEncoder(positions, locals, + file, codeSize, regSize, isStatic, ref); + + byte[] result; + + if ((debugPrint == null) && (out == null)) { + result = encoder.convert(); + } else { + result = encoder.convertAndAnnotate(prefix, debugPrint, out, + consume); + } + + return result; + } +} diff --git a/dx/src/com/android/jack/dx/dex/file/DexFile.java b/dx/src/com/android/jack/dx/dex/file/DexFile.java new file mode 100644 index 00000000..c0fa5b1f --- /dev/null +++ b/dx/src/com/android/jack/dx/dex/file/DexFile.java @@ -0,0 +1,678 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.dex.file; + +import com.android.jack.dx.dex.DexOptions; +import com.android.jack.dx.rop.cst.Constant; +import com.android.jack.dx.rop.cst.CstBaseMethodRef; +import com.android.jack.dx.rop.cst.CstEnumRef; +import com.android.jack.dx.rop.cst.CstFieldRef; +import com.android.jack.dx.rop.cst.CstIndexMap; +import com.android.jack.dx.rop.cst.CstString; +import com.android.jack.dx.rop.cst.CstType; +import com.android.jack.dx.rop.type.Type; +import com.android.jack.dx.util.ByteArrayAnnotatedOutput; +import com.android.jack.dx.util.ExceptionWithContext; + +import java.io.IOException; +import java.io.OutputStream; +import java.io.Writer; +import java.security.DigestException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.List; +import java.util.zip.Adler32; + +import static com.android.jack.dx.dex.file.MixedItemSection.SortType; + +/** + * Representation of an entire {@code .dex} (Dalvik EXecutable) + * file, which itself consists of a set of Dalvik classes. + */ +public final class DexFile { + /** options controlling the creation of the file */ + private DexOptions dexOptions; + + /** {@code non-null;} word data section */ + private final MixedItemSection wordData; + + /** + * {@code non-null;} type lists section. This is word data, but separating + * it from {@link #wordData} helps break what would otherwise be a + * circular dependency between the that and {@link #protoIds}. + */ + private final MixedItemSection typeLists; + + /** + * {@code non-null;} map section. The map needs to be in a section by itself + * for the self-reference mechanics to work in a reasonably + * straightforward way. See {@link MapItem#addMap} for more detail. + */ + private final MixedItemSection map; + + /** {@code non-null;} string data section */ + private final MixedItemSection stringData; + + /** {@code non-null;} string identifiers section */ + private final StringIdsSection stringIds; + + /** {@code non-null;} type identifiers section */ + private final TypeIdsSection typeIds; + + /** {@code non-null;} prototype identifiers section */ + private final ProtoIdsSection protoIds; + + /** {@code non-null;} field identifiers section */ + private final FieldIdsSection fieldIds; + + /** {@code non-null;} method identifiers section */ + private final MethodIdsSection methodIds; + + /** {@code non-null;} class definitions section */ + private final ClassDefsSection classDefs; + + /** {@code non-null;} class data section */ + private final MixedItemSection classData; + + /** {@code non-null;} byte data section */ + private final MixedItemSection byteData; + + /** {@code non-null;} file header */ + private final HeaderSection header; + + /** + * {@code non-null;} array of sections in the order they will appear in the + * final output file + */ + private final Section[] sections; + + /** {@code >= -1;} total file size or {@code -1} if unknown */ + private int fileSize; + + /** {@code >= 40;} maximum width of the file dump */ + private int dumpWidth; + + /** List of constant index mapping that must be used to remap binary */ + private List cstIndexMaps; + + public DexFile(DexOptions dexOptions, List cstIndexMaps) { + this.cstIndexMaps = cstIndexMaps; + this.dexOptions = dexOptions; + + header = new HeaderSection(this); + typeLists = new MixedItemSection(null, this, 4, SortType.NONE); + wordData = new MixedItemSection("word_data", this, 4, SortType.TYPE); + stringData = + new MixedItemSection("string_data", this, 1, SortType.INSTANCE); + classData = new MixedItemSection(null, this, 1, SortType.NONE); + byteData = new MixedItemSection("byte_data", this, 1, SortType.TYPE); + stringIds = new StringIdsSection(this); + typeIds = new TypeIdsSection(this); + protoIds = new ProtoIdsSection(this); + fieldIds = new FieldIdsSection(this); + methodIds = new MethodIdsSection(this); + classDefs = new ClassDefsSection(this); + map = new MixedItemSection("map", this, 4, SortType.NONE); + + /* + * This is the list of sections in the order they appear in + * the final output. + */ + sections = new Section[] { + header, stringIds, typeIds, protoIds, fieldIds, methodIds, + classDefs, wordData, typeLists, stringData, byteData, + classData, map }; + + fileSize = -1; + dumpWidth = 79; + } + + /** + * Constructs an instance. It is initially empty. + */ + public DexFile(DexOptions dexOptions) { + this(dexOptions, null); + } + + /** + * Returns true if this dex doesn't contain any class defs. + */ + public boolean isEmpty() { + return classDefs.items().isEmpty(); + } + + /** + * Gets the dex-creation options object. + */ + public DexOptions getDexOptions() { + return dexOptions; + } + + /** + * Adds a class to this instance. It is illegal to attempt to add more + * than one class with the same name. + * + * @param clazz {@code non-null;} the class to add + */ + public void add(ClassDefItem clazz) { + classDefs.add(clazz); + } + + /** + * Gets the class definition with the given name, if any. + * + * @param name {@code non-null;} the class name to look for + * @return {@code null-ok;} the class with the given name, or {@code null} + * if there is no such class + */ + public ClassDefItem getClassOrNull(String name) { + try { + Type type = Type.internClassName(name); + return (ClassDefItem) classDefs.get(new CstType(type)); + } catch (IllegalArgumentException ex) { + // Translate exception, per contract. + return null; + } + } + + /** + * Writes the contents of this instance as either a binary or a + * human-readable form, or both. + * + * @param out {@code null-ok;} where to write to + * @param humanOut {@code null-ok;} where to write human-oriented output to + * @param verbose whether to be verbose when writing human-oriented output + */ + public void writeTo(OutputStream out, Writer humanOut, boolean verbose) + throws IOException { + boolean annotate = (humanOut != null); + ByteArrayAnnotatedOutput result = toDex0(annotate, verbose); + + if (out != null) { + out.write(result.getArray()); + } + + if (annotate) { + result.writeAnnotationsTo(humanOut); + } + } + + /** + * Returns the contents of this instance as a {@code .dex} file, + * in {@code byte[]} form. + * + * @param humanOut {@code null-ok;} where to write human-oriented output to + * @param verbose whether to be verbose when writing human-oriented output + * @return {@code non-null;} a {@code .dex} file for this instance + */ + public byte[] toDex(Writer humanOut, boolean verbose) + throws IOException { + boolean annotate = (humanOut != null); + ByteArrayAnnotatedOutput result = toDex0(annotate, verbose); + + if (annotate) { + result.writeAnnotationsTo(humanOut); + } + + return result.getArray(); + } + + /** + * Sets the maximum width of the human-oriented dump of the instance. + * + * @param dumpWidth {@code >= 40;} the width + */ + public void setDumpWidth(int dumpWidth) { + if (dumpWidth < 40) { + throw new IllegalArgumentException("dumpWidth < 40"); + } + + this.dumpWidth = dumpWidth; + } + + /** + * Gets the total file size, if known. + * + *

This is package-scope in order to allow + * the {@link HeaderSection} to set itself up properly.

+ * + * @return {@code >= 0;} the total file size + * @throws RuntimeException thrown if the file size is not yet known + */ + /*package*/ int getFileSize() { + if (fileSize < 0) { + throw new RuntimeException("file size not yet known"); + } + + return fileSize; + } + + /** + * Gets the string data section. + * + *

This is package-scope in order to allow + * the various {@link Item} instances to add items to the + * instance.

+ * + * @return {@code non-null;} the string data section + */ + /*package*/ MixedItemSection getStringData() { + return stringData; + } + + /** + * Gets the word data section. + * + *

This is package-scope in order to allow + * the various {@link Item} instances to add items to the + * instance.

+ * + * @return {@code non-null;} the word data section + */ + /*package*/ MixedItemSection getWordData() { + return wordData; + } + + /** + * Gets the type lists section. + * + *

This is package-scope in order to allow + * the various {@link Item} instances to add items to the + * instance.

+ * + * @return {@code non-null;} the word data section + */ + /*package*/ MixedItemSection getTypeLists() { + return typeLists; + } + + /** + * Gets the map section. + * + *

This is package-scope in order to allow the header section + * to query it.

+ * + * @return {@code non-null;} the map section + */ + /*package*/ MixedItemSection getMap() { + return map; + } + + /** + * Gets the string identifiers section. + * + *

This is package-scope in order to allow + * the various {@link Item} instances to add items to the + * instance.

+ * + * @return {@code non-null;} the string identifiers section + */ + public StringIdsSection getStringIds() { + return stringIds; + } + + /** + * Gets the class definitions section. + * + *

This is package-scope in order to allow + * the various {@link Item} instances to add items to the + * instance.

+ * + * @return {@code non-null;} the class definitions section + */ + /*package*/ ClassDefsSection getClassDefs() { + return classDefs; + } + + /** + * Gets the class data section. + * + *

This is package-scope in order to allow + * the various {@link Item} instances to add items to the + * instance.

+ * + * @return {@code non-null;} the class data section + */ + /*package*/ MixedItemSection getClassData() { + return classData; + } + + /** + * Gets the type identifiers section. + * + *

This is package-scope in order to allow + * the various {@link Item} instances to add items to the + * instance.

+ * + * @return {@code non-null;} the class identifiers section + */ + public TypeIdsSection getTypeIds() { + return typeIds; + } + + /** + * Gets the prototype identifiers section. + * + *

This is package-scope in order to allow + * the various {@link Item} instances to add items to the + * instance.

+ * + * @return {@code non-null;} the prototype identifiers section + */ + /*package*/ ProtoIdsSection getProtoIds() { + return protoIds; + } + + /** + * Gets the field identifiers section. + * + *

This is package-scope in order to allow + * the various {@link Item} instances to add items to the + * instance.

+ * + * @return {@code non-null;} the field identifiers section + */ + public FieldIdsSection getFieldIds() { + return fieldIds; + } + + /** + * Gets the method identifiers section. + * + *

This is package-scope in order to allow + * the various {@link Item} instances to add items to the + * instance.

+ * + * @return {@code non-null;} the method identifiers section + */ + public MethodIdsSection getMethodIds() { + return methodIds; + } + + /** + * Gets the byte data section. + * + *

This is package-scope in order to allow + * the various {@link Item} instances to add items to the + * instance.

+ * + * @return {@code non-null;} the byte data section + */ + /*package*/ MixedItemSection getByteData() { + return byteData; + } + + /** + * Gets the first section of the file that is to be considered + * part of the data section. + * + *

This is package-scope in order to allow the header section + * to query it.

+ * + * @return {@code non-null;} the section + */ + /*package*/ Section getFirstDataSection() { + return wordData; + } + + /** + * Gets the last section of the file that is to be considered + * part of the data section. + * + *

This is package-scope in order to allow the header section + * to query it.

+ * + * @return {@code non-null;} the section + */ + /*package*/ Section getLastDataSection() { + return map; + } + + /** + * Interns the given constant in the appropriate section of this + * instance, or do nothing if the given constant isn't the sort + * that should be interned. + * + * @param cst {@code non-null;} constant to possibly intern + */ + /*package*/ void internIfAppropriate(Constant cst) { + if (cst instanceof CstString) { + stringIds.intern((CstString) cst); + } else if (cst instanceof CstType) { + typeIds.intern((CstType) cst); + } else if (cst instanceof CstBaseMethodRef) { + methodIds.intern((CstBaseMethodRef) cst); + } else if (cst instanceof CstFieldRef) { + fieldIds.intern((CstFieldRef) cst); + } else if (cst instanceof CstEnumRef) { + fieldIds.intern(((CstEnumRef) cst).getFieldRef()); + } else if (cst == null) { + throw new NullPointerException("cst == null"); + } + } + + /** + * Gets the {@link IndexedItem} corresponding to the given constant, + * if it is a constant that has such a correspondence, or return + * {@code null} if it isn't such a constant. This will throw + * an exception if the given constant should have been found + * but wasn't. + * + * @param cst {@code non-null;} the constant to look up + * @return {@code null-ok;} its corresponding item, if it has a corresponding + * item, or {@code null} if it's not that sort of constant + */ + public IndexedItem findItemOrNull(Constant cst) { + if (cst instanceof CstString) { + return stringIds.get(cst); + } else if (cst instanceof CstType) { + return typeIds.get(cst); + } else if (cst instanceof CstBaseMethodRef) { + return methodIds.get(cst); + } else if (cst instanceof CstFieldRef) { + return fieldIds.get(cst); + } else { + return null; + } + } + + /** + * Returns the contents of this instance as a {@code .dex} file, + * in a {@link ByteArrayAnnotatedOutput} instance. + * + * @param annotate whether or not to keep annotations + * @param verbose if annotating, whether to be verbose + * @return {@code non-null;} a {@code .dex} file for this instance + */ + private ByteArrayAnnotatedOutput toDex0(boolean annotate, + boolean verbose) { + /* + * The following is ordered so that the prepare() calls which + * add items happen before the calls to the sections that get + * added to. + */ + + if (cstIndexMaps != null) { + for (CstIndexMap cstIndexMap : cstIndexMaps) { + cstIndexMap.mergeConstantsIntoDexFile(this); + } + } + + classDefs.prepare(); + classData.prepare(); + wordData.prepare(); + byteData.prepare(); + methodIds.prepare(); + fieldIds.prepare(); + protoIds.prepare(); + typeLists.prepare(); + typeIds.prepare(); + stringIds.prepare(); + stringData.prepare(); + header.prepare(); + + // Place the sections within the file. + + int count = sections.length; + int offset = 0; + + for (int i = 0; i < count; i++) { + Section one = sections[i]; + int placedAt = one.setFileOffset(offset); + if (placedAt < offset) { + throw new RuntimeException("bogus placement for section " + i); + } + + try { + if (one == map) { + /* + * Inform the map of all the sections, and add it + * to the file. This can only be done after all + * the other items have been sorted and placed. + */ + MapItem.addMap(sections, map); + map.prepare(); + } + + if (one instanceof MixedItemSection) { + /* + * Place the items of a MixedItemSection that just + * got placed. + */ + ((MixedItemSection) one).placeItems(); + } + + offset = placedAt + one.writeSize(); + } catch (RuntimeException ex) { + throw ExceptionWithContext.withContext(ex, + "...while writing section " + i); + } + } + + // Write out all the sections. + + fileSize = offset; + byte[] barr = new byte[fileSize]; + ByteArrayAnnotatedOutput out = new ByteArrayAnnotatedOutput(barr); + + if (annotate) { + out.enableAnnotations(dumpWidth, verbose); + } + + for (int i = 0; i < count; i++) { + try { + Section one = sections[i]; + int zeroCount = one.getFileOffset() - out.getCursor(); + if (zeroCount < 0) { + throw new ExceptionWithContext("excess write of " + + (-zeroCount)); + } + out.writeZeroes(one.getFileOffset() - out.getCursor()); + one.writeTo(out); + } catch (RuntimeException ex) { + ExceptionWithContext ec; + if (ex instanceof ExceptionWithContext) { + ec = (ExceptionWithContext) ex; + } else { + ec = new ExceptionWithContext(ex); + } + ec.addContext("...while writing section " + i); + throw ec; + } + } + + if (out.getCursor() != fileSize) { + throw new RuntimeException("foreshortened write"); + } + + // Perform final bookkeeping. + + calcSignature(barr); + calcChecksum(barr); + + if (annotate) { + wordData.writeIndexAnnotation(out, ItemType.TYPE_CODE_ITEM, + "\nmethod code index:\n\n"); + getStatistics().writeAnnotation(out); + out.finishAnnotating(); + } + + return out; + } + + /** + * Generates and returns statistics for all the items in the file. + * + * @return {@code non-null;} the statistics + */ + public Statistics getStatistics() { + Statistics stats = new Statistics(); + + for (Section s : sections) { + stats.addAll(s); + } + + return stats; + } + + /** + * Calculates the signature for the {@code .dex} file in the + * given array, and modify the array to contain it. + * + * @param bytes {@code non-null;} the bytes of the file + */ + private static void calcSignature(byte[] bytes) { + MessageDigest md; + + try { + md = MessageDigest.getInstance("SHA-1"); + } catch (NoSuchAlgorithmException ex) { + throw new RuntimeException(ex); + } + + md.update(bytes, 32, bytes.length - 32); + + try { + int amt = md.digest(bytes, 12, 20); + if (amt != 20) { + throw new RuntimeException("unexpected digest write: " + amt + + " bytes"); + } + } catch (DigestException ex) { + throw new RuntimeException(ex); + } + } + + /** + * Calculates the checksum for the {@code .dex} file in the + * given array, and modify the array to contain it. + * + * @param bytes {@code non-null;} the bytes of the file + */ + private static void calcChecksum(byte[] bytes) { + Adler32 a32 = new Adler32(); + + a32.update(bytes, 12, bytes.length - 12); + + int sum = (int) a32.getValue(); + + bytes[8] = (byte) sum; + bytes[9] = (byte) (sum >> 8); + bytes[10] = (byte) (sum >> 16); + bytes[11] = (byte) (sum >> 24); + } +} diff --git a/dx/src/com/android/jack/dx/dex/file/EncodedArrayItem.java b/dx/src/com/android/jack/dx/dex/file/EncodedArrayItem.java new file mode 100644 index 00000000..b87b7cce --- /dev/null +++ b/dx/src/com/android/jack/dx/dex/file/EncodedArrayItem.java @@ -0,0 +1,122 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.dex.file; + +import com.android.jack.dx.rop.cst.CstArray; +import com.android.jack.dx.util.AnnotatedOutput; +import com.android.jack.dx.util.ByteArrayAnnotatedOutput; + +/** + * Encoded array of constant values. + */ +public final class EncodedArrayItem extends OffsettedItem { + /** the required alignment for instances of this class */ + private static final int ALIGNMENT = 1; + + /** {@code non-null;} the array to represent */ + private final CstArray array; + + /** + * {@code null-ok;} encoded form, ready for writing to a file; set during + * {@link #place0} + */ + private byte[] encodedForm; + + /** + * Constructs an instance. + * + * @param array {@code non-null;} array to represent + */ + public EncodedArrayItem(CstArray array) { + /* + * The write size isn't known up-front because (the variable-lengthed) + * leb128 type is used to represent some things. + */ + super(ALIGNMENT, -1); + + if (array == null) { + throw new NullPointerException("array == null"); + } + + this.array = array; + this.encodedForm = null; + } + + /** {@inheritDoc} */ + @Override + public ItemType itemType() { + return ItemType.TYPE_ENCODED_ARRAY_ITEM; + } + + /** {@inheritDoc} */ + @Override + public int hashCode() { + return array.hashCode(); + } + + /** {@inheritDoc} */ + @Override + protected int compareTo0(OffsettedItem other) { + EncodedArrayItem otherArray = (EncodedArrayItem) other; + + return array.compareTo(otherArray.array); + } + + /** {@inheritDoc} */ + @Override + public String toHuman() { + return array.toHuman(); + } + + /** {@inheritDoc} */ + public void addContents(DexFile file) { + ValueEncoder.addContents(file, array); + } + + /** {@inheritDoc} */ + @Override + protected void place0(Section addedTo, int offset) { + // Encode the data and note the size. + + ByteArrayAnnotatedOutput out = new ByteArrayAnnotatedOutput(); + ValueEncoder encoder = new ValueEncoder(addedTo.getFile(), out); + + encoder.writeArray(array, false); + encodedForm = out.toByteArray(); + setWriteSize(encodedForm.length); + } + + /** {@inheritDoc} */ + @Override + protected void writeTo0(DexFile file, AnnotatedOutput out) { + boolean annotates = out.annotates(); + + if (annotates) { + out.annotate(0, offsetString() + " encoded array"); + + /* + * The output is to be annotated, so redo the work previously + * done by place0(), except this time annotations will actually + * get emitted. + */ + ValueEncoder encoder = new ValueEncoder(file, out); + encoder.writeArray(array, true); + } else { + out.write(encodedForm); + } + } +} diff --git a/dx/src/com/android/jack/dx/dex/file/EncodedField.java b/dx/src/com/android/jack/dx/dex/file/EncodedField.java new file mode 100644 index 00000000..29a64848 --- /dev/null +++ b/dx/src/com/android/jack/dx/dex/file/EncodedField.java @@ -0,0 +1,154 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.dex.file; + +import com.android.jack.dx.rop.code.AccessFlags; +import com.android.jack.dx.rop.cst.CstFieldRef; +import com.android.jack.dx.rop.cst.CstString; +import com.android.jack.dx.util.AnnotatedOutput; +import com.android.jack.dx.util.Hex; +import com.android.jack.dx.util.Leb128Utils; + +import java.io.PrintWriter; + +/** + * Representation of a field of a class, of any sort. + */ +public final class EncodedField extends EncodedMember + implements Comparable { + /** {@code non-null;} constant for the field */ + private final CstFieldRef field; + + /** + * Constructs an instance. + * + * @param field {@code non-null;} constant for the field + * @param accessFlags access flags + */ + public EncodedField(CstFieldRef field, int accessFlags) { + super(accessFlags); + + if (field == null) { + throw new NullPointerException("field == null"); + } + + /* + * TODO: Maybe check accessFlags, at least for + * easily-checked stuff? + */ + + this.field = field; + } + + /** {@inheritDoc} */ + public int hashCode() { + return field.hashCode(); + } + + /** {@inheritDoc} */ + public boolean equals(Object other) { + if (! (other instanceof EncodedField)) { + return false; + } + + return compareTo((EncodedField) other) == 0; + } + + /** + * {@inheritDoc} + * + *

Note: This compares the method constants only, + * ignoring any associated code, because it should never be the + * case that two different items with the same method constant + * ever appear in the same list (or same file, even).

+ */ + public int compareTo(EncodedField other) { + return field.compareTo(other.field); + } + + /** {@inheritDoc} */ + @Override + public String toString() { + StringBuffer sb = new StringBuffer(100); + + sb.append(getClass().getName()); + sb.append('{'); + sb.append(Hex.u2(getAccessFlags())); + sb.append(' '); + sb.append(field); + sb.append('}'); + return sb.toString(); + } + + /** {@inheritDoc} */ + @Override + public void addContents(DexFile file) { + FieldIdsSection fieldIds = file.getFieldIds(); + fieldIds.intern(field); + } + + /** {@inheritDoc} */ + @Override + public CstString getName() { + return field.getNat().getName(); + } + + /** {@inheritDoc} */ + public String toHuman() { + return field.toHuman(); + } + + /** {@inheritDoc} */ + @Override + public void debugPrint(PrintWriter out, boolean verbose) { + // TODO: Maybe put something better here? + out.println(toString()); + } + + /** + * Gets the constant for the field. + * + * @return {@code non-null;} the constant + */ + public CstFieldRef getRef() { + return field; + } + + /** {@inheritDoc} */ + @Override + public int encode(DexFile file, AnnotatedOutput out, + int lastIndex, int dumpSeq) { + int fieldIdx = file.getFieldIds().indexOf(field); + int diff = fieldIdx - lastIndex; + int accessFlags = getAccessFlags(); + + if (out.annotates()) { + out.annotate(0, String.format(" [%x] %s", dumpSeq, + field.toHuman())); + out.annotate(Leb128Utils.unsignedLeb128Size(diff), + " field_idx: " + Hex.u4(fieldIdx)); + out.annotate(Leb128Utils.unsignedLeb128Size(accessFlags), + " access_flags: " + + AccessFlags.fieldString(accessFlags)); + } + + out.writeUleb128(diff); + out.writeUleb128(accessFlags); + + return fieldIdx; + } +} diff --git a/dx/src/com/android/jack/dx/dex/file/EncodedMember.java b/dx/src/com/android/jack/dx/dex/file/EncodedMember.java new file mode 100644 index 00000000..6502d0f6 --- /dev/null +++ b/dx/src/com/android/jack/dx/dex/file/EncodedMember.java @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.dex.file; + +import com.android.jack.dx.rop.cst.CstString; +import com.android.jack.dx.util.AnnotatedOutput; +import com.android.jack.dx.util.ToHuman; + +import java.io.PrintWriter; + +/** + * Representation of a member (field or method) of a class, for the + * purposes of encoding it inside a {@link ClassDataItem}. + */ +public abstract class EncodedMember implements ToHuman { + /** access flags */ + private final int accessFlags; + + /** + * Constructs an instance. + * + * @param accessFlags access flags for the member + */ + public EncodedMember(int accessFlags) { + this.accessFlags = accessFlags; + } + + /** + * Gets the access flags. + * + * @return the access flags + */ + public final int getAccessFlags() { + return accessFlags; + } + + /** + * Gets the name. + * + * @return {@code non-null;} the name + */ + public abstract CstString getName(); + + /** + * Does a human-friendly dump of this instance. + * + * @param out {@code non-null;} where to dump + * @param verbose whether to be verbose with the output + */ + public abstract void debugPrint(PrintWriter out, boolean verbose); + + /** + * Populates a {@link DexFile} with items from within this instance. + * + * @param file {@code non-null;} the file to populate + */ + public abstract void addContents(DexFile file); + + /** + * Encodes this instance to the given output. + * + * @param file {@code non-null;} file this instance is part of + * @param out {@code non-null;} where to write to + * @param lastIndex {@code >= 0;} the previous member index value encoded, or + * {@code 0} if this is the first element to encode + * @param dumpSeq {@code >= 0;} sequence number of this instance for + * annotation purposes + * @return {@code >= 0;} the member index value that was encoded + */ + public abstract int encode(DexFile file, AnnotatedOutput out, + int lastIndex, int dumpSeq); +} diff --git a/dx/src/com/android/jack/dx/dex/file/EncodedMethod.java b/dx/src/com/android/jack/dx/dex/file/EncodedMethod.java new file mode 100644 index 00000000..924a6041 --- /dev/null +++ b/dx/src/com/android/jack/dx/dex/file/EncodedMethod.java @@ -0,0 +1,191 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.dex.file; + +import com.android.jack.dx.rop.code.AccessFlags; +import com.android.jack.dx.rop.cst.CstMethodRef; +import com.android.jack.dx.rop.cst.CstString; +import com.android.jack.dx.util.AnnotatedOutput; +import com.android.jack.dx.util.Hex; +import com.android.jack.dx.util.Leb128Utils; + +import java.io.PrintWriter; + +/** + * Class that representats a method of a class. + */ +public final class EncodedMethod extends EncodedMember + implements Comparable { + /** {@code non-null;} constant for the method */ + private final CstMethodRef method; + + /** + * {@code null-ok;} code for the method, if the method is neither + * {@code abstract} nor {@code native} + */ + private final OffsettedItem code; + + /** + * Constructs an instance. + * + * @param method {@code non-null;} constant for the method + * @param accessFlags access flags + * @param code {@code null-ok;} code for the method, if it is neither + * {@code abstract} nor {@code native} + */ + public EncodedMethod(CstMethodRef method, int accessFlags, OffsettedItem code) { + super(accessFlags); + + assert code == null || code instanceof Code; + + if (method == null) { + throw new NullPointerException("method == null"); + } + + this.method = method; + + this.code = code; + } + + /** {@inheritDoc} */ + @Override + public boolean equals(Object other) { + if (! (other instanceof EncodedMethod)) { + return false; + } + + return compareTo((EncodedMethod) other) == 0; + } + + /** + * {@inheritDoc} + * + *

Note: This compares the method constants only, + * ignoring any associated code, because it should never be the + * case that two different items with the same method constant + * ever appear in the same list (or same file, even).

+ */ + @Override + public int compareTo(EncodedMethod other) { + return method.compareTo(other.method); + } + + /** {@inheritDoc} */ + @Override + public String toString() { + StringBuffer sb = new StringBuffer(100); + + sb.append(getClass().getName()); + sb.append('{'); + sb.append(Hex.u2(getAccessFlags())); + sb.append(' '); + sb.append(method); + + if (code != null) { + sb.append(' '); + sb.append(code); + } + + sb.append('}'); + + return sb.toString(); + } + + /** {@inheritDoc} */ + @Override + public void addContents(DexFile file) { + MethodIdsSection methodIds = file.getMethodIds(); + MixedItemSection wordData = file.getWordData(); + + methodIds.intern(method); + + if (code != null) { + wordData.add(code); + } + } + + /** {@inheritDoc} */ + @Override + public final String toHuman() { + return method.toHuman(); + } + + /** {@inheritDoc} */ + @Override + public final CstString getName() { + return method.getNat().getName(); + } + + /** {@inheritDoc} */ + @Override + public void debugPrint(PrintWriter out, boolean verbose) { + if (code == null) { + out.println(getRef().toHuman() + ": abstract or native"); + } else { + ((Code) code).debugPrint(out, " ", verbose); + } + } + + /** + * Gets the constant for the method. + * + * @return {@code non-null;} the constant + */ + public final CstMethodRef getRef() { + return method; + } + + /** {@inheritDoc} */ + @Override + public int encode(DexFile file, AnnotatedOutput out, + int lastIndex, int dumpSeq) { + int methodIdx = file.getMethodIds().indexOf(method); + int diff = methodIdx - lastIndex; + int accessFlags = getAccessFlags(); + int codeOff = OffsettedItem.getAbsoluteOffsetOr0(code); + boolean hasCode = (codeOff != 0); + boolean shouldHaveCode = (accessFlags & + (AccessFlags.ACC_ABSTRACT | AccessFlags.ACC_NATIVE)) == 0; + + /* + * Verify that code appears if and only if a method is + * declared to have it. + */ + if (hasCode != shouldHaveCode) { + throw new UnsupportedOperationException( + "code vs. access_flags mismatch"); + } + + if (out.annotates()) { + out.annotate(0, String.format(" [%x] %s", dumpSeq, + method.toHuman())); + out.annotate(Leb128Utils.unsignedLeb128Size(diff), + " method_idx: " + Hex.u4(methodIdx)); + out.annotate(Leb128Utils.unsignedLeb128Size(accessFlags), + " access_flags: " + + AccessFlags.methodString(accessFlags)); + out.annotate(Leb128Utils.unsignedLeb128Size(codeOff), + " code_off: " + Hex.u4(codeOff)); + } + + out.writeUleb128(diff); + out.writeUleb128(accessFlags); + out.writeUleb128(codeOff); + + return methodIdx; + } +} diff --git a/dx/src/com/android/jack/dx/dex/file/FieldAnnotationStruct.java b/dx/src/com/android/jack/dx/dex/file/FieldAnnotationStruct.java new file mode 100644 index 00000000..8d11f73b --- /dev/null +++ b/dx/src/com/android/jack/dx/dex/file/FieldAnnotationStruct.java @@ -0,0 +1,122 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.dex.file; + +import com.android.jack.dx.rop.annotation.Annotations; +import com.android.jack.dx.rop.cst.CstFieldRef; +import com.android.jack.dx.util.AnnotatedOutput; +import com.android.jack.dx.util.Hex; +import com.android.jack.dx.util.ToHuman; + +/** + * Association of a field and its annotations. + */ +public final class FieldAnnotationStruct + implements ToHuman, Comparable { + /** {@code non-null;} the field in question */ + private final CstFieldRef field; + + /** {@code non-null;} the associated annotations */ + private AnnotationSetItem annotations; + + /** + * Constructs an instance. + * + * @param field {@code non-null;} the field in question + * @param annotations {@code non-null;} the associated annotations + */ + public FieldAnnotationStruct(CstFieldRef field, + AnnotationSetItem annotations) { + if (field == null) { + throw new NullPointerException("field == null"); + } + + if (annotations == null) { + throw new NullPointerException("annotations == null"); + } + + this.field = field; + this.annotations = annotations; + } + + /** {@inheritDoc} */ + public int hashCode() { + return field.hashCode(); + } + + /** {@inheritDoc} */ + public boolean equals(Object other) { + if (! (other instanceof FieldAnnotationStruct)) { + return false; + } + + return field.equals(((FieldAnnotationStruct) other).field); + } + + /** {@inheritDoc} */ + public int compareTo(FieldAnnotationStruct other) { + return field.compareTo(other.field); + } + + /** {@inheritDoc} */ + public void addContents(DexFile file) { + FieldIdsSection fieldIds = file.getFieldIds(); + MixedItemSection wordData = file.getWordData(); + + fieldIds.intern(field); + annotations = wordData.intern(annotations); + } + + /** {@inheritDoc} */ + public void writeTo(DexFile file, AnnotatedOutput out) { + int fieldIdx = file.getFieldIds().indexOf(field); + int annotationsOff = annotations.getAbsoluteOffset(); + + if (out.annotates()) { + out.annotate(0, " " + field.toHuman()); + out.annotate(4, " field_idx: " + Hex.u4(fieldIdx)); + out.annotate(4, " annotations_off: " + + Hex.u4(annotationsOff)); + } + + out.writeInt(fieldIdx); + out.writeInt(annotationsOff); + } + + /** {@inheritDoc} */ + public String toHuman() { + return field.toHuman() + ": " + annotations; + } + + /** + * Gets the field this item is for. + * + * @return {@code non-null;} the field + */ + public CstFieldRef getField() { + return field; + } + + /** + * Gets the associated annotations. + * + * @return {@code non-null;} the annotations + */ + public Annotations getAnnotations() { + return annotations.getAnnotations(); + } +} diff --git a/dx/src/com/android/jack/dx/dex/file/FieldIdItem.java b/dx/src/com/android/jack/dx/dex/file/FieldIdItem.java new file mode 100644 index 00000000..373ae6c1 --- /dev/null +++ b/dx/src/com/android/jack/dx/dex/file/FieldIdItem.java @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.dex.file; + +import com.android.jack.dx.rop.cst.CstFieldRef; + +/** + * Representation of a field reference inside a Dalvik file. + */ +public final class FieldIdItem extends MemberIdItem { + /** + * Constructs an instance. + * + * @param field {@code non-null;} the constant for the field + */ + public FieldIdItem(CstFieldRef field) { + super(field); + } + + /** {@inheritDoc} */ + @Override + public ItemType itemType() { + return ItemType.TYPE_FIELD_ID_ITEM; + } + + /** {@inheritDoc} */ + @Override + public void addContents(DexFile file) { + super.addContents(file); + + TypeIdsSection typeIds = file.getTypeIds(); + typeIds.intern(getFieldRef().getType()); + } + + /** + * Gets the field constant. + * + * @return {@code non-null;} the constant + */ + public CstFieldRef getFieldRef() { + return (CstFieldRef) getRef(); + } + + /** {@inheritDoc} */ + @Override + protected int getTypoidIdx(DexFile file) { + TypeIdsSection typeIds = file.getTypeIds(); + return typeIds.indexOf(getFieldRef().getType()); + } + + /** {@inheritDoc} */ + @Override + protected String getTypoidName() { + return "type_idx"; + } +} diff --git a/dx/src/com/android/jack/dx/dex/file/FieldIdsSection.java b/dx/src/com/android/jack/dx/dex/file/FieldIdsSection.java new file mode 100644 index 00000000..66d30f47 --- /dev/null +++ b/dx/src/com/android/jack/dx/dex/file/FieldIdsSection.java @@ -0,0 +1,137 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.dex.file; + +import com.android.jack.dx.rop.cst.Constant; +import com.android.jack.dx.rop.cst.CstFieldRef; +import com.android.jack.dx.util.AnnotatedOutput; +import com.android.jack.dx.util.Hex; + +import java.util.Collection; +import java.util.TreeMap; + +/** + * Field refs list section of a {@code .dex} file. + */ +public final class FieldIdsSection extends MemberIdsSection { + /** + * {@code non-null;} map from field constants to {@link + * FieldIdItem} instances + */ + private final TreeMap fieldIds; + + /** + * Constructs an instance. The file offset is initially unknown. + * + * @param file {@code non-null;} file that this instance is part of + */ + public FieldIdsSection(DexFile file) { + super("field_ids", file); + + fieldIds = new TreeMap(); + } + + /** {@inheritDoc} */ + @Override + public Collection items() { + return fieldIds.values(); + } + + /** {@inheritDoc} */ + @Override + public IndexedItem get(Constant cst) { + if (cst == null) { + throw new NullPointerException("cst == null"); + } + + throwIfNotPrepared(); + + IndexedItem result = fieldIds.get((CstFieldRef) cst); + + if (result == null) { + throw new IllegalArgumentException("not found"); + } + + return result; + } + + /** + * Writes the portion of the file header that refers to this instance. + * + * @param out {@code non-null;} where to write + */ + public void writeHeaderPart(AnnotatedOutput out) { + throwIfNotPrepared(); + + int sz = fieldIds.size(); + int offset = (sz == 0) ? 0 : getFileOffset(); + + if (out.annotates()) { + out.annotate(4, "field_ids_size: " + Hex.u4(sz)); + out.annotate(4, "field_ids_off: " + Hex.u4(offset)); + } + + out.writeInt(sz); + out.writeInt(offset); + } + + /** + * Interns an element into this instance. + * + * @param field {@code non-null;} the reference to intern + * @return {@code non-null;} the interned reference + */ + public FieldIdItem intern(CstFieldRef field) { + if (field == null) { + throw new NullPointerException("field == null"); + } + + throwIfPrepared(); + + FieldIdItem result = fieldIds.get(field); + + if (result == null) { + result = new FieldIdItem(field); + fieldIds.put(field, result); + } + + return result; + } + + /** + * Gets the index of the given reference, which must have been added + * to this instance. + * + * @param ref {@code non-null;} the reference to look up + * @return {@code >= 0;} the reference's index + */ + public int indexOf(CstFieldRef ref) { + if (ref == null) { + throw new NullPointerException("ref == null"); + } + + throwIfNotPrepared(); + + FieldIdItem item = fieldIds.get(ref); + + if (item == null) { + throw new IllegalArgumentException("not found"); + } + + return item.getIndex(); + } +} diff --git a/dx/src/com/android/jack/dx/dex/file/HeaderItem.java b/dx/src/com/android/jack/dx/dex/file/HeaderItem.java new file mode 100644 index 00000000..48462355 --- /dev/null +++ b/dx/src/com/android/jack/dx/dex/file/HeaderItem.java @@ -0,0 +1,115 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.dex.file; + +import com.android.jack.dx.dex.DexFormat; +import com.android.jack.dx.dex.SizeOf; +import com.android.jack.dx.rop.cst.CstString; +import com.android.jack.dx.util.AnnotatedOutput; +import com.android.jack.dx.util.Hex; + +/** + * File header section of a {@code .dex} file. + */ +public final class HeaderItem extends IndexedItem { + /** + * Constructs an instance. + */ + public HeaderItem() { + // This space intentionally left blank. + } + + /** {@inheritDoc} */ + @Override + public ItemType itemType() { + return ItemType.TYPE_HEADER_ITEM; + } + + /** {@inheritDoc} */ + @Override + public int writeSize() { + return SizeOf.HEADER_ITEM; + } + + /** {@inheritDoc} */ + @Override + public void addContents(DexFile file) { + // Nothing to do here. + } + + /** {@inheritDoc} */ + @Override + public void writeTo(DexFile file, AnnotatedOutput out) { + int mapOff = file.getMap().getFileOffset(); + Section firstDataSection = file.getFirstDataSection(); + Section lastDataSection = file.getLastDataSection(); + int dataOff = firstDataSection.getFileOffset(); + int dataSize = lastDataSection.getFileOffset() + + lastDataSection.writeSize() - dataOff; + + String magic = file.getDexOptions().getMagic(); + + if (out.annotates()) { + out.annotate(8, "magic: " + new CstString(magic).toQuoted()); + out.annotate(4, "checksum"); + out.annotate(20, "signature"); + out.annotate(4, "file_size: " + + Hex.u4(file.getFileSize())); + out.annotate(4, "header_size: " + Hex.u4(SizeOf.HEADER_ITEM)); + out.annotate(4, "endian_tag: " + Hex.u4(DexFormat.ENDIAN_TAG)); + out.annotate(4, "link_size: 0"); + out.annotate(4, "link_off: 0"); + out.annotate(4, "map_off: " + Hex.u4(mapOff)); + } + + // Write the magic number. + for (int i = 0; i < 8; i++) { + out.writeByte(magic.charAt(i)); + } + + // Leave space for the checksum and signature. + out.writeZeroes(24); + + out.writeInt(file.getFileSize()); + out.writeInt(SizeOf.HEADER_ITEM); + out.writeInt(DexFormat.ENDIAN_TAG); + + /* + * Write zeroes for the link size and data, as the output + * isn't a staticly linked file. + */ + out.writeZeroes(8); + + out.writeInt(mapOff); + + // Write out each section's respective header part. + file.getStringIds().writeHeaderPart(out); + file.getTypeIds().writeHeaderPart(out); + file.getProtoIds().writeHeaderPart(out); + file.getFieldIds().writeHeaderPart(out); + file.getMethodIds().writeHeaderPart(out); + file.getClassDefs().writeHeaderPart(out); + + if (out.annotates()) { + out.annotate(4, "data_size: " + Hex.u4(dataSize)); + out.annotate(4, "data_off: " + Hex.u4(dataOff)); + } + + out.writeInt(dataSize); + out.writeInt(dataOff); + } +} diff --git a/dx/src/com/android/jack/dx/dex/file/HeaderSection.java b/dx/src/com/android/jack/dx/dex/file/HeaderSection.java new file mode 100644 index 00000000..e2043e6c --- /dev/null +++ b/dx/src/com/android/jack/dx/dex/file/HeaderSection.java @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.dex.file; + +import com.android.jack.dx.rop.cst.Constant; + +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +/** + * File header section of a {@code .dex} file. + */ +public final class HeaderSection extends UniformItemSection { + /** {@code non-null;} the list of the one item in the section */ + private final List list; + + /** + * Constructs an instance. The file offset is initially unknown. + * + * @param file {@code non-null;} file that this instance is part of + */ + public HeaderSection(DexFile file) { + super(null, file, 4); + + HeaderItem item = new HeaderItem(); + item.setIndex(0); + + this.list = Collections.singletonList(item); + } + + /** {@inheritDoc} */ + @Override + public IndexedItem get(Constant cst) { + return null; + } + + /** {@inheritDoc} */ + @Override + public Collection items() { + return list; + } + + /** {@inheritDoc} */ + @Override + protected void orderItems() { + // Nothing to do here. + } +} diff --git a/dx/src/com/android/jack/dx/dex/file/IdItem.java b/dx/src/com/android/jack/dx/dex/file/IdItem.java new file mode 100644 index 00000000..acf82c4a --- /dev/null +++ b/dx/src/com/android/jack/dx/dex/file/IdItem.java @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.dex.file; + +import com.android.jack.dx.rop.cst.CstType; + +/** + * Representation of a reference to an item inside a Dalvik file. + */ +public abstract class IdItem extends IndexedItem { + /** + * {@code non-null;} the type constant for the defining class of + * the reference + */ + private final CstType type; + + /** + * Constructs an instance. + * + * @param type {@code non-null;} the type constant for the defining + * class of the reference + */ + public IdItem(CstType type) { + if (type == null) { + throw new NullPointerException("type == null"); + } + + this.type = type; + } + + /** {@inheritDoc} */ + @Override + public void addContents(DexFile file) { + TypeIdsSection typeIds = file.getTypeIds(); + typeIds.intern(type); + } + + /** + * Gets the type constant for the defining class of the + * reference. + * + * @return {@code non-null;} the type constant + */ + public final CstType getDefiningClass() { + return type; + } +} diff --git a/dx/src/com/android/jack/dx/dex/file/ImportedCodeItem.java b/dx/src/com/android/jack/dx/dex/file/ImportedCodeItem.java new file mode 100644 index 00000000..c3d6fd19 --- /dev/null +++ b/dx/src/com/android/jack/dx/dex/file/ImportedCodeItem.java @@ -0,0 +1,400 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.dex.file; + +import java.io.PrintWriter; + +import com.android.jack.dx.io.Code; +import com.android.jack.dx.io.CodeReader; +import com.android.jack.dx.io.Opcodes; +import com.android.jack.dx.io.Code.CatchHandler; +import com.android.jack.dx.io.Code.Try; +import com.android.jack.dx.io.instructions.DecodedInstruction; +import com.android.jack.dx.io.instructions.ShortArrayCodeOutput; +import com.android.jack.dx.rop.cst.Constant; +import com.android.jack.dx.rop.cst.CstIndexMap; +import com.android.jack.dx.rop.cst.CstMethodRef; +import com.android.jack.dx.util.AnnotatedOutput; +import com.android.jack.dx.util.ByteArrayAnnotatedOutput; +import com.android.jack.dx.util.DexException; +import com.android.jack.dx.util.Hex; + +/** + * Representation of all the parts needed to import methods from a {@code dex} file into another. + */ +public final class ImportedCodeItem extends OffsettedItem implements com.android.jack.dx.dex.file.Code { + + /** {@code null-ok;} the imported debug info or {@code null} if there is none; */ + ImportedDebugInfoItem debugInfoItem = null; + + /** {@code non-null;} method that this code implements */ + private final CstMethodRef ref; + + /** {@code non-null;} code representing the imported method */ + private final Code code; + + /** + * {@code non-null;} map index values used into code that references {@link Constant} from one + * dex file into index values compliant with another dex file. + */ + private CstIndexMap cstIndexMap; + + /** Array of remapped instructions */ + private DecodedInstruction[] remappedInstructions; + + /** Index used during instructions remapping.*/ + private int remappingIndex; + + /** Binary representation of catch handlers. */ + private byte[] encodedHandlers; + + /** Array containing the result of catch handler remapping. */ + private int[] remappedCatchHandlerOffsets; + + /** + * Constructs an instance. + * @param ref {@code non-null;} method that this code implements + * @param code {@code non-null;} the underlying code + * @param debugInfoItem {@code null-ok;} the imported debug information of method {@code ref} + * @param cstIndexMap {@code non-null;} maps constant index of one dex file into another + */ + public ImportedCodeItem(CstMethodRef ref, Code code, ImportedDebugInfoItem debugInfoItem, + CstIndexMap cstIndexMap) { + super(ALIGNMENT, -1); + + if (ref == null) { + throw new NullPointerException("ref == null"); + } + this.ref = ref; + + if (code == null) { + throw new NullPointerException("code == null"); + } + this.code = code; + + this.debugInfoItem = debugInfoItem; + + if (cstIndexMap == null) { + throw new NullPointerException("cstIndexMap == null"); + } + this.cstIndexMap = cstIndexMap; + } + + /** {@inheritDoc} */ + @Override + public ItemType itemType() { + return ItemType.TYPE_CODE_ITEM; + } + + /** {@inheritDoc} */ + public void addContents(DexFile file) { + if (debugInfoItem != null) { + file.getByteData().add(debugInfoItem); + } + } + + /** {@inheritDoc} */ + @Override + public String toString() { + return "CodeItem{" + toHuman() + "}"; + } + + /** {@inheritDoc} */ + @Override + public String toHuman() { + return ref.toHuman(); + } + + /** + * Gets the reference to the method this instance implements. + * @return {@code non-null;} the method reference + */ + public CstMethodRef getRef() { + return ref; + } + + /** {@inheritDoc} */ + @Override + protected void place0(Section addedTo, int offset) { + int triesLength = code.getTries().length; + + encodedHandlers = triesLength != 0 ? encodeAndRemapCatchHandler(addedTo.getFile()) : new byte[0]; + + int catchesSize = triesLength * CatchStructs.TRY_ITEM_WRITE_SIZE + encodedHandlers.length; + + int insnsSize = code.getInstructions().length; + if ((insnsSize & 1) != 0) { + // Requires padding to align tries and handlers + insnsSize++; + } + + setWriteSize(HEADER_SIZE + (insnsSize * 2) + catchesSize); + } + + /** {@inheritDoc} */ + @Override + protected void writeTo0(DexFile file, AnnotatedOutput out) { + boolean annotates = out.annotates(); + int regSz = getRegistersSize(); + int outsSz = getOutsSize(); + int insSz = getInsSize(); + int insnsSz = code.getInstructions().length; + boolean needPadding = (insnsSz & 1) != 0; + int debugOff = (debugInfoItem == null) ? 0 : debugInfoItem.getAbsoluteOffset(); + int triesSz = code.getTries().length; + + if (annotates) { + out.annotate(0, offsetString() + ' ' + ref.toHuman()); + out.annotate(2, " registers_size: " + Hex.u2(regSz)); + out.annotate(2, " ins_size: " + Hex.u2(insSz)); + out.annotate(2, " outs_size: " + Hex.u2(outsSz)); + out.annotate(2, " tries_size: " + Hex.u2(triesSz)); + out.annotate(4, " debug_off: " + Hex.u4(debugOff)); + out.annotate(4, " insns_size: " + Hex.u4(insnsSz)); + } + + out.writeShort(regSz); + out.writeShort(insSz); + out.writeShort(outsSz); + out.writeShort(triesSz); + out.writeInt(debugOff); + out.writeInt(insnsSz); + + for (short inst : encodeAndRemapCode(file, code.getInstructions())) { + out.writeShort(inst); + } + + if (triesSz != 0) { + if (needPadding) { + if (annotates) { + out.annotate(2, " padding: 0"); + } + out.writeShort(0); + } + + for (Try atry : code.getTries()) { + out.writeInt(atry.getStartAddress()); + out.writeShort(atry.getInstructionCount()); + out.writeShort(remappedCatchHandlerOffsets[atry.getCatchHandlerIndex()]); + } + + out.write(encodedHandlers); + } + + if (annotates) { + /* + * These are pointed at in the code header (above), but it's less distracting to expand on + * them at the bottom of the code. + */ + if (debugInfoItem != null) { + out.annotate(0, " debug info"); + debugInfoItem.annotateTo(file, out, " "); + } + } + } + + /** + * Encode and remap catch handlers. + * @param file {@link DexFile} which will contains remapped catch handlers. + * @return Byte array representing encoded catch handlers. + */ + private byte[] encodeAndRemapCatchHandler(DexFile file) { + ByteArrayAnnotatedOutput out = new ByteArrayAnnotatedOutput(); + + CatchHandler[] catchHandlers = code.getCatchHandlers(); + out.writeUleb128(catchHandlers.length); + + remappedCatchHandlerOffsets = new int[catchHandlers.length]; + int catchHandlerIdx = 0; + + for (CatchHandler catchHandler : catchHandlers) { + remappedCatchHandlerOffsets[catchHandlerIdx++] = out.getCursor(); + + int catchAllAddress = catchHandler.getCatchAllAddress(); + int[] typeIndexes = catchHandler.getTypeIndexes(); + int[] addresses = catchHandler.getAddresses(); + + if (catchAllAddress != -1) { + out.writeSleb128(-typeIndexes.length); + } else { + out.writeSleb128(typeIndexes.length); + } + + for (int i = 0; i < typeIndexes.length; i++) { + out.writeUleb128(cstIndexMap.getRemappedCstTypeIndex(file, typeIndexes[i])); + out.writeUleb128(addresses[i]); + } + + if (catchAllAddress != -1) { + out.writeUleb128(catchAllAddress); + } + } + + return out.toByteArray(); + } + + /** + * Encode and remap code. + * @param file {@link DexFile} which will contains remapped code. + * @param insts Instructions in binary form that must be remap. + * @return Remapped instructions in binary form. + */ + private short[] encodeAndRemapCode(DexFile file, short[] insts) { + CodeReader codeReader = new CodeReader(); + DecodedInstruction[] decodedInstructions = DecodedInstruction.decodeAll(insts); + remappedInstructions = new DecodedInstruction[decodedInstructions.length]; + remappingIndex = 0; + + codeReader.setFallbackVisitor(new GenericVisitor()); + codeReader.setStringVisitor(new StringRemapper(file)); + codeReader.setFieldVisitor(new FieldRemapper(file)); + codeReader.setTypeVisitor(new TypeRemapper(file)); + codeReader.setMethodVisitor(new MethodRemapper(file)); + + codeReader.visitAll(decodedInstructions); + + ShortArrayCodeOutput outputCode = new ShortArrayCodeOutput(insts.length); + for (DecodedInstruction instruction : remappedInstructions) { + if (instruction != null) { + instruction.encode(outputCode); + } + } + + return outputCode.getArray(); + } + + /** + * Get the in registers count. + * @return the count + */ + private int getInsSize() { + return code.getInsSize(); + } + + /** + * Get the out registers count. + * @return the count + */ + private int getOutsSize() { + return code.getOutsSize(); + } + + /** + * Get the total registers count. + * @return the count + */ + private int getRegistersSize() { + return code.getRegistersSize(); + } + + /** + * Does a human-friendly dump of this instance. + * @param out {@code non-null;} where to dump + * @param prefix {@code non-null;} per-line prefix to use + * @param verbose whether to be verbose with the output + */ + @Override + public void debugPrint(PrintWriter out, String prefix, boolean verbose) { + throw new AssertionError("Not yet supported"); + } + + private class GenericVisitor implements CodeReader.Visitor { + + public void visit(DecodedInstruction[] all, DecodedInstruction decodedInst) { + remappedInstructions[remappingIndex++] = decodedInst; + } + } + + /** + * {@link CodeReader.Visitor} remapping instructions using string index. + */ + private class StringRemapper implements CodeReader.Visitor { + + private DexFile file; + + public StringRemapper(DexFile dex) { + this.file = dex; + } + + @Override + public void visit(DecodedInstruction[] all, DecodedInstruction decodedInst) { + int newIndex = cstIndexMap.getRemappedCstStringIndex(file, decodedInst.getIndex()); + + if (decodedInst.getOpcode() != Opcodes.CONST_STRING_JUMBO && (newIndex > 0xffff)) { + throw new DexException("Cannot remap new index " + newIndex + + " into a non-jumbo instruction!"); + } + + remappedInstructions[remappingIndex++] = decodedInst.withIndex(newIndex); + } + } + + /** + * {@link CodeReader.Visitor} remapping instructions using field index. + */ + private class FieldRemapper implements CodeReader.Visitor { + + private DexFile file; + + public FieldRemapper(DexFile dex) { + this.file = dex; + } + + @Override + public void visit(DecodedInstruction[] all, DecodedInstruction decodedInst) { + remappedInstructions[remappingIndex++] = + decodedInst.withIndex(cstIndexMap.getRemappedCstFieldRefIndex(file, decodedInst.getIndex())); + } + } + + /** + * {@link CodeReader.Visitor} remapping instructions using type index. + */ + private class TypeRemapper implements CodeReader.Visitor { + + private DexFile file; + + public TypeRemapper(DexFile dex) { + this.file = dex; + } + + @Override + public void visit(DecodedInstruction[] all, DecodedInstruction decodedInst) { + remappedInstructions[remappingIndex++] = + decodedInst.withIndex(cstIndexMap.getRemappedCstTypeIndex(file, decodedInst.getIndex())); + } + } + + /** + * {@link CodeReader.Visitor} remapping instructions using method index. + */ + private class MethodRemapper implements CodeReader.Visitor { + + private DexFile file; + + public MethodRemapper(DexFile dex) { + this.file = dex; + } + + @Override + public void visit(DecodedInstruction[] all, DecodedInstruction decodedInst) { + remappedInstructions[remappingIndex++] = + decodedInst.withIndex(cstIndexMap.getRemappedCstBaseMethodRefIndex(file, + decodedInst.getIndex())); + } + } +} diff --git a/dx/src/com/android/jack/dx/dex/file/ImportedDebugInfoItem.java b/dx/src/com/android/jack/dx/dex/file/ImportedDebugInfoItem.java new file mode 100644 index 00000000..1d7b4cbd --- /dev/null +++ b/dx/src/com/android/jack/dx/dex/file/ImportedDebugInfoItem.java @@ -0,0 +1,193 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.dex.file; + +import static com.android.jack.dx.dex.file.DebugInfoConstants.DBG_ADVANCE_LINE; +import static com.android.jack.dx.dex.file.DebugInfoConstants.DBG_ADVANCE_PC; +import static com.android.jack.dx.dex.file.DebugInfoConstants.DBG_END_LOCAL; +import static com.android.jack.dx.dex.file.DebugInfoConstants.DBG_END_SEQUENCE; +import static com.android.jack.dx.dex.file.DebugInfoConstants.DBG_RESTART_LOCAL; +import static com.android.jack.dx.dex.file.DebugInfoConstants.DBG_SET_EPILOGUE_BEGIN; +import static com.android.jack.dx.dex.file.DebugInfoConstants.DBG_SET_FILE; +import static com.android.jack.dx.dex.file.DebugInfoConstants.DBG_SET_PROLOGUE_END; +import static com.android.jack.dx.dex.file.DebugInfoConstants.DBG_START_LOCAL; +import static com.android.jack.dx.dex.file.DebugInfoConstants.DBG_START_LOCAL_EXTENDED; + +import java.io.PrintWriter; + +import com.android.jack.dx.io.DexBuffer; +import com.android.jack.dx.rop.cst.Constant; +import com.android.jack.dx.rop.cst.CstIndexMap; +import com.android.jack.dx.util.AnnotatedOutput; + +public class ImportedDebugInfoItem extends OffsettedItem { + + /** the required alignment for instances of this class */ + private static final int ALIGNMENT = 1; + + private final DexBuffer dexBuffer; + + private final int debugInfoOffset; + + private final int debugInfoSize; + + /** + * {@code non-null;} map index values used into debug information that references {@link Constant} + * from one dex file into index values compliant with another dex file. + */ + private CstIndexMap cstIndexMap; + + public ImportedDebugInfoItem(DexBuffer dexBuffer, int debugInfoOffset, int debugInfoSize, + CstIndexMap cstIndexMap) { + // We don't know the write size yet. + super(ALIGNMENT, -1); + + assert dexBuffer != null; + + this.dexBuffer = dexBuffer; + + this.debugInfoOffset = debugInfoOffset; + + this.debugInfoSize = debugInfoSize; + + this.cstIndexMap = cstIndexMap; + } + + /** {@inheritDoc} */ + @Override + public ItemType itemType() { + return ItemType.TYPE_DEBUG_INFO_ITEM; + } + + /** {@inheritDoc} */ + @Override + public void addContents(DexFile file) { + // Nothing to do + } + + + /** {@inheritDoc} */ + @Override + protected void place0(Section addedTo, int offset) { + setWriteSize(debugInfoSize); + } + + /** {@inheritDoc} */ + @Override + public String toHuman() { + throw new RuntimeException("unsupported"); + } + + /** + * Writes annotations for the elements of this list, as zero-length. This is meant to be used for + * dumping this instance directly after a code dump (with the real local list actually existing + * elsewhere in the output). + * @param file {@code non-null;} the file to use for referencing other sections + * @param out {@code non-null;} where to annotate to + * @param prefix {@code null-ok;} prefix to attach to each line of output + */ + public void annotateTo(DexFile file, AnnotatedOutput out, String prefix) { + throw new RuntimeException("unsupported"); + } + + /** + * Does a human-friendly dump of this instance. + * @param out {@code non-null;} where to dump + * @param prefix {@code non-null;} prefix to attach to each line of output + */ + public void debugPrint(PrintWriter out, String prefix) { + throw new RuntimeException("unsupported"); + } + + /** {@inheritDoc} */ + @Override + protected void writeTo0(DexFile file, AnnotatedOutput out) { + encodeAndRemapDebugInfoItem(file, out); + } + + private void encodeAndRemapDebugInfoItem(DexFile file, AnnotatedOutput out) { + com.android.jack.dx.io.DexBuffer.Section in = dexBuffer.open(debugInfoOffset); + + int lineStart = in.readUleb128(); + out.writeUleb128(lineStart); + + int parametersSize = in.readUleb128(); + out.writeUleb128(parametersSize); + + for (int p = 0; p < parametersSize; p++) { + int parameterName = in.readUleb128p1(); + out.writeUleb128(cstIndexMap.getRemappedCstStringIndex(file, parameterName) + 1); + } + + int addrDiff; // uleb128 address delta. + int lineDiff; // sleb128 line delta. + int registerNum; // uleb128 register number. + int nameIndex; // uleb128p1 string index. Needs indexMap adjustment. + int typeIndex; // uleb128p1 type index. Needs indexMap adjustment. + int sigIndex; // uleb128p1 string index. Needs indexMap adjustment. + + while (true) { + int opcode = in.readByte(); + out.writeByte(opcode); + + switch (opcode) { + case DBG_END_SEQUENCE: + return; + + case DBG_ADVANCE_PC: + addrDiff = in.readUleb128(); + out.writeUleb128(addrDiff); + break; + + case DBG_ADVANCE_LINE: + lineDiff = in.readSleb128(); + out.writeSleb128(lineDiff); + break; + + case DBG_START_LOCAL: + case DBG_START_LOCAL_EXTENDED: + registerNum = in.readUleb128(); + out.writeUleb128(registerNum); + nameIndex = in.readUleb128p1(); + out.writeUleb128(cstIndexMap.getRemappedCstStringIndex(file, nameIndex) + 1); + typeIndex = in.readUleb128p1(); + out.writeUleb128(cstIndexMap.getRemappedCstTypeIndex(file, typeIndex) + 1); + if (opcode == DBG_START_LOCAL_EXTENDED) { + sigIndex = in.readUleb128p1(); + out.writeUleb128(cstIndexMap.getRemappedCstStringIndex(file, sigIndex) + 1); + } + break; + + case DBG_END_LOCAL: + case DBG_RESTART_LOCAL: + registerNum = in.readUleb128(); + out.writeUleb128(registerNum); + break; + + case DBG_SET_FILE: + nameIndex = in.readUleb128p1(); + out.writeUleb128(cstIndexMap.getRemappedCstStringIndex(file, nameIndex) + 1); + break; + + case DBG_SET_PROLOGUE_END: + case DBG_SET_EPILOGUE_BEGIN: + default: + break; + } + } + } +} diff --git a/dx/src/com/android/jack/dx/dex/file/IndexedItem.java b/dx/src/com/android/jack/dx/dex/file/IndexedItem.java new file mode 100644 index 00000000..6e2c83b9 --- /dev/null +++ b/dx/src/com/android/jack/dx/dex/file/IndexedItem.java @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.dex.file; + +/** + * An item in a Dalvik file which is referenced by index. + */ +public abstract class IndexedItem extends Item { + /** {@code >= -1;} assigned index of the item, or {@code -1} if not + * yet assigned */ + private int index; + + /** + * Constructs an instance. The index is initially unassigned. + */ + public IndexedItem() { + index = -1; + } + + /** + * Gets whether or not this instance has been assigned an index. + * + * @return {@code true} iff this instance has been assigned an index + */ + public final boolean hasIndex() { + return (index >= 0); + } + + /** + * Gets the item index. + * + * @return {@code >= 0;} the index + * @throws RuntimeException thrown if the item index is not yet assigned + */ + public final int getIndex() { + if (index < 0) { + throw new RuntimeException("index not yet set"); + } + + return index; + } + + /** + * Sets the item index. This method may only ever be called once + * per instance, and this will throw a {@code RuntimeException} if + * called a second (or subsequent) time. + * + * @param index {@code >= 0;} the item index + */ + public final void setIndex(int index) { + if (this.index != -1) { + throw new RuntimeException("index already set"); + } + + this.index = index; + } + + /** + * Gets the index of this item as a string, suitable for including in + * annotations. + * + * @return {@code non-null;} the index string + */ + public final String indexString() { + return '[' + Integer.toHexString(index) + ']'; + } +} diff --git a/dx/src/com/android/jack/dx/dex/file/Item.java b/dx/src/com/android/jack/dx/dex/file/Item.java new file mode 100644 index 00000000..6dca0e54 --- /dev/null +++ b/dx/src/com/android/jack/dx/dex/file/Item.java @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.dex.file; + +import com.android.jack.dx.util.AnnotatedOutput; + +/** + * Base class for any structurally-significant and (potentially) + * repeated piece of a Dalvik file. + */ +public abstract class Item { + /** + * Constructs an instance. + */ + public Item() { + // This space intentionally left blank. + } + + /** + * Returns the item type for this instance. + * + * @return {@code non-null;} the item type + */ + public abstract ItemType itemType(); + + /** + * Returns the human name for the particular type of item this + * instance is. + * + * @return {@code non-null;} the name + */ + public final String typeName() { + return itemType().toHuman(); + } + + /** + * Gets the size of this instance when written, in bytes. + * + * @return {@code >= 0;} the write size + */ + public abstract int writeSize(); + + /** + * Populates a {@link DexFile} with items from within this instance. + * This will not add an item to the file for this instance itself + * (which should have been done by whatever refers to this instance). + * + *

Note: Subclasses must override this to do something + * appropriate.

+ * + * @param file {@code non-null;} the file to populate + */ + public abstract void addContents(DexFile file); + + /** + * Writes the representation of this instance to the given data section, + * using the given {@link DexFile} to look things up as needed. + * If this instance keeps track of its offset, then this method will + * note the written offset and will also throw an exception if this + * instance has already been written. + * + * @param file {@code non-null;} the file to use for reference + * @param out {@code non-null;} where to write to + */ + public abstract void writeTo(DexFile file, AnnotatedOutput out); +} diff --git a/dx/src/com/android/jack/dx/dex/file/ItemType.java b/dx/src/com/android/jack/dx/dex/file/ItemType.java new file mode 100644 index 00000000..6d8f274a --- /dev/null +++ b/dx/src/com/android/jack/dx/dex/file/ItemType.java @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.dex.file; + +import com.android.jack.dx.util.ToHuman; + +/** + * Enumeration of all the top-level item types. + */ +public enum ItemType implements ToHuman { + TYPE_HEADER_ITEM( 0x0000, "header_item"), + TYPE_STRING_ID_ITEM( 0x0001, "string_id_item"), + TYPE_TYPE_ID_ITEM( 0x0002, "type_id_item"), + TYPE_PROTO_ID_ITEM( 0x0003, "proto_id_item"), + TYPE_FIELD_ID_ITEM( 0x0004, "field_id_item"), + TYPE_METHOD_ID_ITEM( 0x0005, "method_id_item"), + TYPE_CLASS_DEF_ITEM( 0x0006, "class_def_item"), + TYPE_MAP_LIST( 0x1000, "map_list"), + TYPE_TYPE_LIST( 0x1001, "type_list"), + TYPE_ANNOTATION_SET_REF_LIST( 0x1002, "annotation_set_ref_list"), + TYPE_ANNOTATION_SET_ITEM( 0x1003, "annotation_set_item"), + TYPE_CLASS_DATA_ITEM( 0x2000, "class_data_item"), + TYPE_CODE_ITEM( 0x2001, "code_item"), + TYPE_STRING_DATA_ITEM( 0x2002, "string_data_item"), + TYPE_DEBUG_INFO_ITEM( 0x2003, "debug_info_item"), + TYPE_ANNOTATION_ITEM( 0x2004, "annotation_item"), + TYPE_ENCODED_ARRAY_ITEM( 0x2005, "encoded_array_item"), + TYPE_ANNOTATIONS_DIRECTORY_ITEM(0x2006, "annotations_directory_item"), + TYPE_MAP_ITEM( -1, "map_item"), + TYPE_TYPE_ITEM( -1, "type_item"), + TYPE_EXCEPTION_HANDLER_ITEM( -1, "exception_handler_item"), + TYPE_ANNOTATION_SET_REF_ITEM( -1, "annotation_set_ref_item"); + + /** value when represented in a {@link MapItem} */ + private final int mapValue; + + /** {@code non-null;} name of the type */ + private final String typeName; + + /** {@code non-null;} the short human name */ + private final String humanName; + + /** + * Constructs an instance. + * + * @param mapValue value when represented in a {@link MapItem} + * @param typeName {@code non-null;} name of the type + */ + private ItemType(int mapValue, String typeName) { + this.mapValue = mapValue; + this.typeName = typeName; + + // Make the human name. + String human = typeName; + if (human.endsWith("_item")) { + human = human.substring(0, human.length() - 5); + } + this.humanName = human.replace('_', ' '); + } + + /** + * Gets the map value. + * + * @return the map value + */ + public int getMapValue() { + return mapValue; + } + + /** + * Gets the type name. + * + * @return {@code non-null;} the type name + */ + public String getTypeName() { + return typeName; + } + + /** {@inheritDoc} */ + public String toHuman() { + return humanName; + } +} diff --git a/dx/src/com/android/jack/dx/dex/file/MapItem.java b/dx/src/com/android/jack/dx/dex/file/MapItem.java new file mode 100644 index 00000000..d0bc3319 --- /dev/null +++ b/dx/src/com/android/jack/dx/dex/file/MapItem.java @@ -0,0 +1,235 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.dex.file; + +import com.android.jack.dx.util.AnnotatedOutput; +import com.android.jack.dx.util.Hex; + +import java.util.ArrayList; + +/** + * Class that represents a map item. + */ +public final class MapItem extends OffsettedItem { + /** file alignment of this class, in bytes */ + private static final int ALIGNMENT = 4; + + /** write size of this class, in bytes: three {@code uint}s */ + private static final int WRITE_SIZE = (4 * 3); + + /** {@code non-null;} item type this instance covers */ + private final ItemType type; + + /** {@code non-null;} section this instance covers */ + private final Section section; + + /** + * {@code null-ok;} first item covered or {@code null} if this is + * a self-reference + */ + private final Item firstItem; + + /** + * {@code null-ok;} last item covered or {@code null} if this is + * a self-reference + */ + private final Item lastItem; + + /** + * {@code > 0;} count of items covered; {@code 1} if this + * is a self-reference + */ + private final int itemCount; + + /** + * Constructs a list item with instances of this class representing + * the contents of the given array of sections, adding it to the + * given map section. + * + * @param sections {@code non-null;} the sections + * @param mapSection {@code non-null;} the section that the resulting map + * should be added to; it should be empty on entry to this method + */ + public static void addMap(Section[] sections, + MixedItemSection mapSection) { + if (sections == null) { + throw new NullPointerException("sections == null"); + } + + if (mapSection.items().size() != 0) { + throw new IllegalArgumentException( + "mapSection.items().size() != 0"); + } + + ArrayList items = new ArrayList(50); + + for (Section section : sections) { + ItemType currentType = null; + Item firstItem = null; + Item lastItem = null; + int count = 0; + + for (Item item : section.items()) { + ItemType type = item.itemType(); + if (type != currentType) { + if (count != 0) { + items.add(new MapItem(currentType, section, + firstItem, lastItem, count)); + } + currentType = type; + firstItem = item; + count = 0; + } + lastItem = item; + count++; + } + + if (count != 0) { + // Add a MapItem for the final items in the section. + items.add(new MapItem(currentType, section, + firstItem, lastItem, count)); + } else if (section == mapSection) { + // Add a MapItem for the self-referential section. + items.add(new MapItem(mapSection)); + } + } + + mapSection.add( + new UniformListItem(ItemType.TYPE_MAP_LIST, items)); + } + + /** + * Constructs an instance. + * + * @param type {@code non-null;} item type this instance covers + * @param section {@code non-null;} section this instance covers + * @param firstItem {@code non-null;} first item covered + * @param lastItem {@code non-null;} last item covered + * @param itemCount {@code > 0;} count of items covered + */ + private MapItem(ItemType type, Section section, Item firstItem, + Item lastItem, int itemCount) { + super(ALIGNMENT, WRITE_SIZE); + + if (type == null) { + throw new NullPointerException("type == null"); + } + + if (section == null) { + throw new NullPointerException("section == null"); + } + + if (firstItem == null) { + throw new NullPointerException("firstItem == null"); + } + + if (lastItem == null) { + throw new NullPointerException("lastItem == null"); + } + + if (itemCount <= 0) { + throw new IllegalArgumentException("itemCount <= 0"); + } + + this.type = type; + this.section = section; + this.firstItem = firstItem; + this.lastItem = lastItem; + this.itemCount = itemCount; + } + + /** + * Constructs a self-referential instance. This instance is meant to + * represent the section containing the {@code map_list}. + * + * @param section {@code non-null;} section this instance covers + */ + private MapItem(Section section) { + super(ALIGNMENT, WRITE_SIZE); + + if (section == null) { + throw new NullPointerException("section == null"); + } + + this.type = ItemType.TYPE_MAP_LIST; + this.section = section; + this.firstItem = null; + this.lastItem = null; + this.itemCount = 1; + } + + /** {@inheritDoc} */ + @Override + public ItemType itemType() { + return ItemType.TYPE_MAP_ITEM; + } + + /** {@inheritDoc} */ + @Override + public String toString() { + StringBuffer sb = new StringBuffer(100); + + sb.append(getClass().getName()); + sb.append('{'); + sb.append(section.toString()); + sb.append(' '); + sb.append(type.toHuman()); + sb.append('}'); + + return sb.toString(); + } + + /** {@inheritDoc} */ + @Override + public void addContents(DexFile file) { + // We have nothing to add. + } + + /** {@inheritDoc} */ + @Override + public final String toHuman() { + return toString(); + } + + /** {@inheritDoc} */ + @Override + protected void writeTo0(DexFile file, AnnotatedOutput out) { + int value = type.getMapValue(); + int offset; + + if (firstItem == null) { + offset = section.getFileOffset(); + } else { + offset = section.getAbsoluteItemOffset(firstItem); + } + + if (out.annotates()) { + out.annotate(0, offsetString() + ' ' + type.getTypeName() + + " map"); + out.annotate(2, " type: " + Hex.u2(value) + " // " + + type.toString()); + out.annotate(2, " unused: 0"); + out.annotate(4, " size: " + Hex.u4(itemCount)); + out.annotate(4, " offset: " + Hex.u4(offset)); + } + + out.writeShort(value); + out.writeShort(0); // unused + out.writeInt(itemCount); + out.writeInt(offset); + } +} diff --git a/dx/src/com/android/jack/dx/dex/file/MemberIdItem.java b/dx/src/com/android/jack/dx/dex/file/MemberIdItem.java new file mode 100644 index 00000000..bb6674a2 --- /dev/null +++ b/dx/src/com/android/jack/dx/dex/file/MemberIdItem.java @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.dex.file; + +import com.android.jack.dx.dex.SizeOf; +import com.android.jack.dx.rop.cst.CstMemberRef; +import com.android.jack.dx.rop.cst.CstNat; +import com.android.jack.dx.util.AnnotatedOutput; +import com.android.jack.dx.util.Hex; + +/** + * Representation of a member (field or method) reference inside a + * Dalvik file. + */ +public abstract class MemberIdItem extends IdItem { + /** {@code non-null;} the constant for the member */ + private final CstMemberRef cst; + + /** + * Constructs an instance. + * + * @param cst {@code non-null;} the constant for the member + */ + public MemberIdItem(CstMemberRef cst) { + super(cst.getDefiningClass()); + + this.cst = cst; + } + + /** {@inheritDoc} */ + @Override + public int writeSize() { + return SizeOf.MEMBER_ID_ITEM; + } + + /** {@inheritDoc} */ + @Override + public void addContents(DexFile file) { + super.addContents(file); + + StringIdsSection stringIds = file.getStringIds(); + stringIds.intern(getRef().getNat().getName()); + } + + /** {@inheritDoc} */ + @Override + public final void writeTo(DexFile file, AnnotatedOutput out) { + TypeIdsSection typeIds = file.getTypeIds(); + StringIdsSection stringIds = file.getStringIds(); + CstNat nat = cst.getNat(); + int classIdx = typeIds.indexOf(getDefiningClass()); + int nameIdx = stringIds.indexOf(nat.getName()); + int typoidIdx = getTypoidIdx(file); + + if (out.annotates()) { + out.annotate(0, indexString() + ' ' + cst.toHuman()); + out.annotate(2, " class_idx: " + Hex.u2(classIdx)); + out.annotate(2, String.format(" %-10s %s", getTypoidName() + ':', + Hex.u2(typoidIdx))); + out.annotate(4, " name_idx: " + Hex.u4(nameIdx)); + } + + out.writeShort(classIdx); + out.writeShort(typoidIdx); + out.writeInt(nameIdx); + } + + /** + * Returns the index of the type-like thing associated with + * this item, in order that it may be written out. Subclasses must + * override this to get whatever it is they need to store. + * + * @param file {@code non-null;} the file being written + * @return the index in question + */ + protected abstract int getTypoidIdx(DexFile file); + + /** + * Returns the field name of the type-like thing associated with + * this item, for listing-generating purposes. Subclasses must override + * this. + * + * @return {@code non-null;} the name in question + */ + protected abstract String getTypoidName(); + + /** + * Gets the member constant. + * + * @return {@code non-null;} the constant + */ + public final CstMemberRef getRef() { + return cst; + } +} diff --git a/dx/src/com/android/jack/dx/dex/file/MemberIdsSection.java b/dx/src/com/android/jack/dx/dex/file/MemberIdsSection.java new file mode 100644 index 00000000..628f1091 --- /dev/null +++ b/dx/src/com/android/jack/dx/dex/file/MemberIdsSection.java @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.dex.file; + +import com.android.jack.dx.util.DexException; + +import java.util.Formatter; +import java.util.Map; +import java.util.TreeMap; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * Member (field or method) refs list section of a {@code .dex} file. + */ +public abstract class MemberIdsSection extends UniformItemSection { + /** The largest addressable member is 0xffff, in the dex spec as field@CCCC or meth@CCCC. */ + private static final int MAX_MEMBERS = 0x10000; + + /** + * Constructs an instance. The file offset is initially unknown. + * + * @param name {@code null-ok;} the name of this instance, for annotation + * purposes + * @param file {@code non-null;} file that this instance is part of + */ + public MemberIdsSection(String name, DexFile file) { + super(name, file, 4); + } + + /** {@inheritDoc} */ + @Override + protected void orderItems() { + int idx = 0; + + if (items().size() > MAX_MEMBERS) { + throw new DexException(tooManyMembersMessage()); + } + + for (Object i : items()) { + ((MemberIdItem) i).setIndex(idx); + idx++; + } + } + + private String tooManyMembersMessage() { + Map membersByPackage = new TreeMap(); + for (Object member : items()) { + String packageName = ((MemberIdItem) member).getDefiningClass().getPackageName(); + AtomicInteger count = membersByPackage.get(packageName); + if (count == null) { + count = new AtomicInteger(); + membersByPackage.put(packageName, count); + } + count.incrementAndGet(); + } + + Formatter formatter = new Formatter(); + String memberType = this instanceof MethodIdsSection ? "methods" : "fields"; + formatter.format("Too many %s: %d; max is %d. By package:", + memberType, items().size(), MAX_MEMBERS); + for (Map.Entry entry : membersByPackage.entrySet()) { + formatter.format("%n%6d %s", entry.getValue().get(), entry.getKey()); + } + return formatter.toString(); + } +} diff --git a/dx/src/com/android/jack/dx/dex/file/MethodAnnotationStruct.java b/dx/src/com/android/jack/dx/dex/file/MethodAnnotationStruct.java new file mode 100644 index 00000000..8e2ca323 --- /dev/null +++ b/dx/src/com/android/jack/dx/dex/file/MethodAnnotationStruct.java @@ -0,0 +1,122 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.dex.file; + +import com.android.jack.dx.rop.annotation.Annotations; +import com.android.jack.dx.rop.cst.CstMethodRef; +import com.android.jack.dx.util.AnnotatedOutput; +import com.android.jack.dx.util.Hex; +import com.android.jack.dx.util.ToHuman; + +/** + * Association of a method and its annotations. + */ +public final class MethodAnnotationStruct + implements ToHuman, Comparable { + /** {@code non-null;} the method in question */ + private final CstMethodRef method; + + /** {@code non-null;} the associated annotations */ + private AnnotationSetItem annotations; + + /** + * Constructs an instance. + * + * @param method {@code non-null;} the method in question + * @param annotations {@code non-null;} the associated annotations + */ + public MethodAnnotationStruct(CstMethodRef method, + AnnotationSetItem annotations) { + if (method == null) { + throw new NullPointerException("method == null"); + } + + if (annotations == null) { + throw new NullPointerException("annotations == null"); + } + + this.method = method; + this.annotations = annotations; + } + + /** {@inheritDoc} */ + public int hashCode() { + return method.hashCode(); + } + + /** {@inheritDoc} */ + public boolean equals(Object other) { + if (! (other instanceof MethodAnnotationStruct)) { + return false; + } + + return method.equals(((MethodAnnotationStruct) other).method); + } + + /** {@inheritDoc} */ + public int compareTo(MethodAnnotationStruct other) { + return method.compareTo(other.method); + } + + /** {@inheritDoc} */ + public void addContents(DexFile file) { + MethodIdsSection methodIds = file.getMethodIds(); + MixedItemSection wordData = file.getWordData(); + + methodIds.intern(method); + annotations = wordData.intern(annotations); + } + + /** {@inheritDoc} */ + public void writeTo(DexFile file, AnnotatedOutput out) { + int methodIdx = file.getMethodIds().indexOf(method); + int annotationsOff = annotations.getAbsoluteOffset(); + + if (out.annotates()) { + out.annotate(0, " " + method.toHuman()); + out.annotate(4, " method_idx: " + Hex.u4(methodIdx)); + out.annotate(4, " annotations_off: " + + Hex.u4(annotationsOff)); + } + + out.writeInt(methodIdx); + out.writeInt(annotationsOff); + } + + /** {@inheritDoc} */ + public String toHuman() { + return method.toHuman() + ": " + annotations; + } + + /** + * Gets the method this item is for. + * + * @return {@code non-null;} the method + */ + public CstMethodRef getMethod() { + return method; + } + + /** + * Gets the associated annotations. + * + * @return {@code non-null;} the annotations + */ + public Annotations getAnnotations() { + return annotations.getAnnotations(); + } +} diff --git a/dx/src/com/android/jack/dx/dex/file/MethodIdItem.java b/dx/src/com/android/jack/dx/dex/file/MethodIdItem.java new file mode 100644 index 00000000..36841724 --- /dev/null +++ b/dx/src/com/android/jack/dx/dex/file/MethodIdItem.java @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.dex.file; + +import com.android.jack.dx.rop.cst.CstBaseMethodRef; + +/** + * Representation of a method reference inside a Dalvik file. + */ +public final class MethodIdItem extends MemberIdItem { + /** + * Constructs an instance. + * + * @param method {@code non-null;} the constant for the method + */ + public MethodIdItem(CstBaseMethodRef method) { + super(method); + } + + /** {@inheritDoc} */ + @Override + public ItemType itemType() { + return ItemType.TYPE_METHOD_ID_ITEM; + } + + /** {@inheritDoc} */ + @Override + public void addContents(DexFile file) { + super.addContents(file); + + ProtoIdsSection protoIds = file.getProtoIds(); + protoIds.intern(getMethodRef().getPrototype()); + } + + /** + * Gets the method constant. + * + * @return {@code non-null;} the constant + */ + public CstBaseMethodRef getMethodRef() { + return (CstBaseMethodRef) getRef(); + } + + /** {@inheritDoc} */ + @Override + protected int getTypoidIdx(DexFile file) { + ProtoIdsSection protoIds = file.getProtoIds(); + return protoIds.indexOf(getMethodRef().getPrototype()); + } + + /** {@inheritDoc} */ + @Override + protected String getTypoidName() { + return "proto_idx"; + } +} diff --git a/dx/src/com/android/jack/dx/dex/file/MethodIdsSection.java b/dx/src/com/android/jack/dx/dex/file/MethodIdsSection.java new file mode 100644 index 00000000..34f528e9 --- /dev/null +++ b/dx/src/com/android/jack/dx/dex/file/MethodIdsSection.java @@ -0,0 +1,137 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.dex.file; + +import com.android.jack.dx.rop.cst.Constant; +import com.android.jack.dx.rop.cst.CstBaseMethodRef; +import com.android.jack.dx.util.AnnotatedOutput; +import com.android.jack.dx.util.Hex; + +import java.util.Collection; +import java.util.TreeMap; + +/** + * Method refs list section of a {@code .dex} file. + */ +public final class MethodIdsSection extends MemberIdsSection { + /** + * {@code non-null;} map from method constants to {@link + * MethodIdItem} instances + */ + private final TreeMap methodIds; + + /** + * Constructs an instance. The file offset is initially unknown. + * + * @param file {@code non-null;} file that this instance is part of + */ + public MethodIdsSection(DexFile file) { + super("method_ids", file); + + methodIds = new TreeMap(); + } + + /** {@inheritDoc} */ + @Override + public Collection items() { + return methodIds.values(); + } + + /** {@inheritDoc} */ + @Override + public IndexedItem get(Constant cst) { + if (cst == null) { + throw new NullPointerException("cst == null"); + } + + throwIfNotPrepared(); + + IndexedItem result = methodIds.get((CstBaseMethodRef) cst); + + if (result == null) { + throw new IllegalArgumentException("not found"); + } + + return result; + } + + /** + * Writes the portion of the file header that refers to this instance. + * + * @param out {@code non-null;} where to write + */ + public void writeHeaderPart(AnnotatedOutput out) { + throwIfNotPrepared(); + + int sz = methodIds.size(); + int offset = (sz == 0) ? 0 : getFileOffset(); + + if (out.annotates()) { + out.annotate(4, "method_ids_size: " + Hex.u4(sz)); + out.annotate(4, "method_ids_off: " + Hex.u4(offset)); + } + + out.writeInt(sz); + out.writeInt(offset); + } + + /** + * Interns an element into this instance. + * + * @param method {@code non-null;} the reference to intern + * @return {@code non-null;} the interned reference + */ + public MethodIdItem intern(CstBaseMethodRef method) { + if (method == null) { + throw new NullPointerException("method == null"); + } + + throwIfPrepared(); + + MethodIdItem result = methodIds.get(method); + + if (result == null) { + result = new MethodIdItem(method); + methodIds.put(method, result); + } + + return result; + } + + /** + * Gets the index of the given reference, which must have been added + * to this instance. + * + * @param ref {@code non-null;} the reference to look up + * @return {@code >= 0;} the reference's index + */ + public int indexOf(CstBaseMethodRef ref) { + if (ref == null) { + throw new NullPointerException("ref == null"); + } + + throwIfNotPrepared(); + + MethodIdItem item = methodIds.get(ref); + + if (item == null) { + throw new IllegalArgumentException("not found"); + } + + return item.getIndex(); + } +} diff --git a/dx/src/com/android/jack/dx/dex/file/MixedItemSection.java b/dx/src/com/android/jack/dx/dex/file/MixedItemSection.java new file mode 100644 index 00000000..914f78a1 --- /dev/null +++ b/dx/src/com/android/jack/dx/dex/file/MixedItemSection.java @@ -0,0 +1,362 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.dex.file; + +import com.android.jack.dx.util.AnnotatedOutput; +import com.android.jack.dx.util.ExceptionWithContext; +import com.android.jack.dx.util.Hex; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.TreeMap; + +/** + * A section of a {@code .dex} file which consists of a sequence of + * {@link OffsettedItem} objects, which may each be of a different concrete + * class and/or size. + * + * Note: It is invalid for an item in an instance of this class to + * have a larger alignment requirement than the alignment of this instance. + */ +public final class MixedItemSection extends Section { + static enum SortType { + /** no sorting */ + NONE, + + /** sort by type only */ + TYPE, + + /** sort in class-major order, with instances sorted per-class */ + INSTANCE; + }; + + /** {@code non-null;} sorter which sorts instances by type */ + private static final Comparator TYPE_SORTER = + new Comparator() { + public int compare(OffsettedItem item1, OffsettedItem item2) { + ItemType type1 = item1.itemType(); + ItemType type2 = item2.itemType(); + return type1.compareTo(type2); + } + }; + + /** {@code non-null;} the items in this part */ + private final ArrayList items; + + /** {@code non-null;} items that have been explicitly interned */ + private final HashMap interns; + + /** {@code non-null;} how to sort the items */ + private final SortType sort; + + /** + * {@code >= -1;} the current size of this part, in bytes, or {@code -1} + * if not yet calculated + */ + private int writeSize; + + /** + * Constructs an instance. The file offset is initially unknown. + * + * @param name {@code null-ok;} the name of this instance, for annotation + * purposes + * @param file {@code non-null;} file that this instance is part of + * @param alignment {@code > 0;} alignment requirement for the final output; + * must be a power of 2 + * @param sort how the items should be sorted in the final output + */ + public MixedItemSection(String name, DexFile file, int alignment, + SortType sort) { + super(name, file, alignment); + + this.items = new ArrayList(100); + this.interns = new HashMap(100); + this.sort = sort; + this.writeSize = -1; + } + + /** {@inheritDoc} */ + @Override + public Collection items() { + return items; + } + + /** {@inheritDoc} */ + @Override + public int writeSize() { + throwIfNotPrepared(); + return writeSize; + } + + /** {@inheritDoc} */ + @Override + public int getAbsoluteItemOffset(Item item) { + OffsettedItem oi = (OffsettedItem) item; + return oi.getAbsoluteOffset(); + } + + /** + * Gets the size of this instance, in items. + * + * @return {@code >= 0;} the size + */ + public int size() { + return items.size(); + } + + /** + * Writes the portion of the file header that refers to this instance. + * + * @param out {@code non-null;} where to write + */ + public void writeHeaderPart(AnnotatedOutput out) { + throwIfNotPrepared(); + + if (writeSize == -1) { + throw new RuntimeException("write size not yet set"); + } + + int sz = writeSize; + int offset = (sz == 0) ? 0 : getFileOffset(); + String name = getName(); + + if (name == null) { + name = ""; + } + + int spaceCount = 15 - name.length(); + char[] spaceArr = new char[spaceCount]; + Arrays.fill(spaceArr, ' '); + String spaces = new String(spaceArr); + + if (out.annotates()) { + out.annotate(4, name + "_size:" + spaces + Hex.u4(sz)); + out.annotate(4, name + "_off: " + spaces + Hex.u4(offset)); + } + + out.writeInt(sz); + out.writeInt(offset); + } + + /** + * Adds an item to this instance. This will in turn tell the given item + * that it has been added to this instance. It is invalid to add the + * same item to more than one instance, nor to add the same items + * multiple times to a single instance. + * + * @param item {@code non-null;} the item to add + */ + public void add(OffsettedItem item) { + throwIfPrepared(); + + try { + if (item.getAlignment() > getAlignment()) { + throw new IllegalArgumentException( + "incompatible item alignment"); + } + } catch (NullPointerException ex) { + // Elucidate the exception. + throw new NullPointerException("item == null"); + } + + items.add(item); + } + + /** + * Interns an item in this instance, returning the interned instance + * (which may not be the one passed in). This will add the item if no + * equal item has been added. + * + * @param item {@code non-null;} the item to intern + * @return {@code non-null;} the equivalent interned instance + */ + public T intern(T item) { + throwIfPrepared(); + + OffsettedItem result = interns.get(item); + + if (result != null) { + return (T) result; + } + + add(item); + interns.put(item, item); + return item; + } + + /** + * Gets an item which was previously interned. + * + * @param item {@code non-null;} the item to look for + * @return {@code non-null;} the equivalent already-interned instance + */ + public T get(T item) { + throwIfNotPrepared(); + + OffsettedItem result = interns.get(item); + + if (result != null) { + return (T) result; + } + + throw new NoSuchElementException(item.toString()); + } + + /** + * Writes an index of contents of the items in this instance of the + * given type. If there are none, this writes nothing. If there are any, + * then the index is preceded by the given intro string. + * + * @param out {@code non-null;} where to write to + * @param itemType {@code non-null;} the item type of interest + * @param intro {@code non-null;} the introductory string for non-empty indices + */ + public void writeIndexAnnotation(AnnotatedOutput out, ItemType itemType, + String intro) { + throwIfNotPrepared(); + + TreeMap index = + new TreeMap(); + + for (OffsettedItem item : items) { + if (item.itemType() == itemType) { + String label = item.toHuman(); + index.put(label, item); + } + } + + if (index.size() == 0) { + return; + } + + out.annotate(0, intro); + + for (Map.Entry entry : index.entrySet()) { + String label = entry.getKey(); + OffsettedItem item = entry.getValue(); + out.annotate(0, item.offsetString() + ' ' + label + '\n'); + } + } + + /** {@inheritDoc} */ + @Override + protected void prepare0() { + DexFile file = getFile(); + + /* + * It's okay for new items to be added as a result of an + * addContents() call; we just have to deal with the possibility. + */ + + int i = 0; + for (;;) { + int sz = items.size(); + if (i >= sz) { + break; + } + + for (/*i*/; i < sz; i++) { + OffsettedItem one = items.get(i); + one.addContents(file); + } + } + } + + /** + * Places all the items in this instance at particular offsets. This + * will call {@link OffsettedItem#place} on each item. If an item + * does not know its write size before the call to {@code place}, + * it is that call which is responsible for setting the write size. + * This method may only be called once per instance; subsequent calls + * will throw an exception. + */ + public void placeItems() { + throwIfNotPrepared(); + + switch (sort) { + case INSTANCE: { + Collections.sort(items); + break; + } + case TYPE: { + Collections.sort(items, TYPE_SORTER); + break; + } + } + + int sz = items.size(); + int outAt = 0; + for (int i = 0; i < sz; i++) { + OffsettedItem one = items.get(i); + try { + int placedAt = one.place(this, outAt); + + if (placedAt < outAt) { + throw new RuntimeException("bogus place() result for " + + one); + } + + outAt = placedAt + one.writeSize(); + } catch (RuntimeException ex) { + throw ExceptionWithContext.withContext(ex, + "...while placing " + one); + } + } + + writeSize = outAt; + } + + /** {@inheritDoc} */ + @Override + protected void writeTo0(AnnotatedOutput out) { + boolean annotates = out.annotates(); + boolean first = true; + DexFile file = getFile(); + int at = 0; + + for (OffsettedItem one : items) { + if (annotates) { + if (first) { + first = false; + } else { + out.annotate(0, "\n"); + } + } + + int alignMask = one.getAlignment() - 1; + int writeAt = (at + alignMask) & ~alignMask; + + if (at != writeAt) { + out.writeZeroes(writeAt - at); + at = writeAt; + } + + one.writeTo(file, out); + at += one.writeSize(); + } + + if (at != writeSize) { + throw new RuntimeException("output size mismatch"); + } + } +} diff --git a/dx/src/com/android/jack/dx/dex/file/OffsettedItem.java b/dx/src/com/android/jack/dx/dex/file/OffsettedItem.java new file mode 100644 index 00000000..b171f688 --- /dev/null +++ b/dx/src/com/android/jack/dx/dex/file/OffsettedItem.java @@ -0,0 +1,314 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.dex.file; + +import com.android.jack.dx.util.AnnotatedOutput; +import com.android.jack.dx.util.ExceptionWithContext; + +/** + * An item in a Dalvik file which is referenced by absolute offset. + */ +public abstract class OffsettedItem extends Item + implements Comparable { + /** {@code > 0;} alignment requirement */ + private final int alignment; + + /** {@code >= -1;} the size of this instance when written, in bytes, or + * {@code -1} if not yet known */ + private int writeSize; + + /** + * {@code null-ok;} section the item was added to, or {@code null} if + * not yet added + */ + private Section addedTo; + + /** + * {@code >= -1;} assigned offset of the item from the start of its section, + * or {@code -1} if not yet assigned + */ + private int offset; + + /** + * Gets the absolute offset of the given item, returning {@code 0} + * if handed {@code null}. + * + * @param item {@code null-ok;} the item in question + * @return {@code >= 0;} the item's absolute offset, or {@code 0} + * if {@code item == null} + */ + public static int getAbsoluteOffsetOr0(OffsettedItem item) { + if (item == null) { + return 0; + } + + return item.getAbsoluteOffset(); + } + + /** + * Constructs an instance. The offset is initially unassigned. + * + * @param alignment {@code > 0;} output alignment requirement; must be a + * power of 2 + * @param writeSize {@code >= -1;} the size of this instance when written, + * in bytes, or {@code -1} if not immediately known + */ + public OffsettedItem(int alignment, int writeSize) { + Section.validateAlignment(alignment); + + if (writeSize < -1) { + throw new IllegalArgumentException("writeSize < -1"); + } + + this.alignment = alignment; + this.writeSize = writeSize; + this.addedTo = null; + this.offset = -1; + } + + /** + * {@inheritDoc} + * + * Comparisons for this class are defined to be type-major (if the + * types don't match then the objects are not equal), with + * {@link #compareTo0} deciding same-type comparisons. + */ + @Override + public final boolean equals(Object other) { + if (this == other) { + return true; + } + + OffsettedItem otherItem = (OffsettedItem) other; + ItemType thisType = itemType(); + ItemType otherType = otherItem.itemType(); + + if (thisType != otherType) { + return false; + } + + return (compareTo0(otherItem) == 0); + } + + /** + * {@inheritDoc} + * + * Comparisons for this class are defined to be class-major (if the + * classes don't match then the objects are not equal), with + * {@link #compareTo0} deciding same-class comparisons. + */ + public final int compareTo(OffsettedItem other) { + if (this == other) { + return 0; + } + + ItemType thisType = itemType(); + ItemType otherType = other.itemType(); + + if (thisType != otherType) { + return thisType.compareTo(otherType); + } + + return compareTo0(other); + } + + /** + * Sets the write size of this item. This may only be called once + * per instance, and only if the size was unknown upon instance + * creation. + * + * @param writeSize {@code > 0;} the write size, in bytes + */ + public final void setWriteSize(int writeSize) { + if (writeSize < 0) { + throw new IllegalArgumentException("writeSize < 0"); + } + + if (this.writeSize >= 0) { + throw new UnsupportedOperationException("writeSize already set"); + } + + this.writeSize = writeSize; + } + + /** {@inheritDoc} + * + * @throws UnsupportedOperationException thrown if the write size + * is not yet known + */ + @Override + public final int writeSize() { + if (writeSize < 0) { + throw new UnsupportedOperationException("writeSize is unknown"); + } + + return writeSize; + } + + /** {@inheritDoc} */ + @Override + public final void writeTo(DexFile file, AnnotatedOutput out) { + out.alignTo(alignment); + + try { + if (writeSize < 0) { + throw new UnsupportedOperationException( + "writeSize is unknown"); + } + out.assertCursor(getAbsoluteOffset()); + } catch (RuntimeException ex) { + throw ExceptionWithContext.withContext(ex, + "...while writing " + this); + } + + writeTo0(file, out); + } + + /** + * Gets the relative item offset. The offset is from the start of + * the section which the instance was written to. + * + * @return {@code >= 0;} the offset + * @throws RuntimeException thrown if the offset is not yet known + */ + public final int getRelativeOffset() { + if (offset < 0) { + throw new RuntimeException("offset not yet known"); + } + + return offset; + } + + /** + * Gets the absolute item offset. The offset is from the start of + * the file which the instance was written to. + * + * @return {@code >= 0;} the offset + * @throws RuntimeException thrown if the offset is not yet known + */ + public final int getAbsoluteOffset() { + if (offset < 0) { + throw new RuntimeException("offset not yet known"); + } + + return addedTo.getAbsoluteOffset(offset); + } + + /** + * Indicates that this item has been added to the given section at + * the given offset. It is only valid to call this method once per + * instance. + * + * @param addedTo {@code non-null;} the section this instance has + * been added to + * @param offset {@code >= 0;} the desired offset from the start of the + * section where this instance was placed + * @return {@code >= 0;} the offset that this instance should be placed at + * in order to meet its alignment constraint + */ + public final int place(Section addedTo, int offset) { + if (addedTo == null) { + throw new NullPointerException("addedTo == null"); + } + + if (offset < 0) { + throw new IllegalArgumentException("offset < 0"); + } + + if (this.addedTo != null) { + throw new RuntimeException("already written"); + } + + int mask = alignment - 1; + offset = (offset + mask) & ~mask; + + this.addedTo = addedTo; + this.offset = offset; + + place0(addedTo, offset); + + return offset; + } + + /** + * Gets the alignment requirement of this instance. An instance should + * only be written when so aligned. + * + * @return {@code > 0;} the alignment requirement; must be a power of 2 + */ + public final int getAlignment() { + return alignment; + } + + /** + * Gets the absolute offset of this item as a string, suitable for + * including in annotations. + * + * @return {@code non-null;} the offset string + */ + public final String offsetString() { + return '[' + Integer.toHexString(getAbsoluteOffset()) + ']'; + } + + /** + * Gets a short human-readable string representing this instance. + * + * @return {@code non-null;} the human form + */ + public abstract String toHuman(); + + /** + * Compares this instance to another which is guaranteed to be of + * the same class. The default implementation of this method is to + * throw an exception (unsupported operation). If a particular + * class needs to actually sort, then it should override this + * method. + * + * @param other {@code non-null;} instance to compare to + * @return {@code -1}, {@code 0}, or {@code 1}, depending + * on the sort order of this instance and the other + */ + protected int compareTo0(OffsettedItem other) { + throw new UnsupportedOperationException("unsupported"); + } + + /** + * Does additional work required when placing an instance. The + * default implementation of this method is a no-op. If a + * particular class needs to do something special, then it should + * override this method. In particular, if this instance did not + * know its write size up-front, then this method is responsible + * for setting it. + * + * @param addedTo {@code non-null;} the section this instance has been added to + * @param offset {@code >= 0;} the offset from the start of the + * section where this instance was placed + */ + protected void place0(Section addedTo, int offset) { + // This space intentionally left blank. + } + + /** + * Performs the actual write of the contents of this instance to + * the given data section. This is called by {@link #writeTo}, + * which will have taken care of ensuring alignment. + * + * @param file {@code non-null;} the file to use for reference + * @param out {@code non-null;} where to write to + */ + protected abstract void writeTo0(DexFile file, AnnotatedOutput out); +} diff --git a/dx/src/com/android/jack/dx/dex/file/ParameterAnnotationStruct.java b/dx/src/com/android/jack/dx/dex/file/ParameterAnnotationStruct.java new file mode 100644 index 00000000..57c4db50 --- /dev/null +++ b/dx/src/com/android/jack/dx/dex/file/ParameterAnnotationStruct.java @@ -0,0 +1,161 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.dex.file; + +import com.android.jack.dx.rop.annotation.Annotations; +import com.android.jack.dx.rop.annotation.AnnotationsList; +import com.android.jack.dx.rop.cst.CstMethodRef; +import com.android.jack.dx.util.AnnotatedOutput; +import com.android.jack.dx.util.Hex; +import com.android.jack.dx.util.ToHuman; + +import java.util.ArrayList; + +/** + * Association of a method and its parameter annotations. + */ +public final class ParameterAnnotationStruct + implements ToHuman, Comparable { + /** {@code non-null;} the method in question */ + private final CstMethodRef method; + + /** {@code non-null;} the associated annotations list */ + private final AnnotationsList annotationsList; + + /** {@code non-null;} the associated annotations list, as an item */ + private final UniformListItem annotationsItem; + + /** + * Constructs an instance. + * + * @param method {@code non-null;} the method in question + * @param annotationsList {@code non-null;} the associated annotations list + */ + public ParameterAnnotationStruct(CstMethodRef method, + AnnotationsList annotationsList) { + if (method == null) { + throw new NullPointerException("method == null"); + } + + if (annotationsList == null) { + throw new NullPointerException("annotationsList == null"); + } + + this.method = method; + this.annotationsList = annotationsList; + + /* + * Construct an item for the annotations list. TODO: This + * requires way too much copying; fix it. + */ + + int size = annotationsList.size(); + ArrayList arrayList = new + ArrayList(size); + + for (int i = 0; i < size; i++) { + Annotations annotations = annotationsList.get(i); + AnnotationSetItem item = new AnnotationSetItem(annotations); + arrayList.add(new AnnotationSetRefItem(item)); + } + + this.annotationsItem = new UniformListItem( + ItemType.TYPE_ANNOTATION_SET_REF_LIST, arrayList); + } + + /** {@inheritDoc} */ + public int hashCode() { + return method.hashCode(); + } + + /** {@inheritDoc} */ + public boolean equals(Object other) { + if (! (other instanceof ParameterAnnotationStruct)) { + return false; + } + + return method.equals(((ParameterAnnotationStruct) other).method); + } + + /** {@inheritDoc} */ + public int compareTo(ParameterAnnotationStruct other) { + return method.compareTo(other.method); + } + + /** {@inheritDoc} */ + public void addContents(DexFile file) { + MethodIdsSection methodIds = file.getMethodIds(); + MixedItemSection wordData = file.getWordData(); + + methodIds.intern(method); + wordData.add(annotationsItem); + } + + /** {@inheritDoc} */ + public void writeTo(DexFile file, AnnotatedOutput out) { + int methodIdx = file.getMethodIds().indexOf(method); + int annotationsOff = annotationsItem.getAbsoluteOffset(); + + if (out.annotates()) { + out.annotate(0, " " + method.toHuman()); + out.annotate(4, " method_idx: " + Hex.u4(methodIdx)); + out.annotate(4, " annotations_off: " + + Hex.u4(annotationsOff)); + } + + out.writeInt(methodIdx); + out.writeInt(annotationsOff); + } + + /** {@inheritDoc} */ + public String toHuman() { + StringBuilder sb = new StringBuilder(); + + sb.append(method.toHuman()); + sb.append(": "); + + boolean first = true; + for (AnnotationSetRefItem item : annotationsItem.getItems()) { + if (first) { + first = false; + } else { + sb.append(", "); + } + sb.append(item.toHuman()); + } + + return sb.toString(); + } + + /** + * Gets the method this item is for. + * + * @return {@code non-null;} the method + */ + public CstMethodRef getMethod() { + return method; + } + + /** + * Gets the associated annotations list. + * + * @return {@code non-null;} the annotations list + */ + public AnnotationsList getAnnotationsList() { + return annotationsList; + } +} diff --git a/dx/src/com/android/jack/dx/dex/file/ProtoIdItem.java b/dx/src/com/android/jack/dx/dex/file/ProtoIdItem.java new file mode 100644 index 00000000..1707125b --- /dev/null +++ b/dx/src/com/android/jack/dx/dex/file/ProtoIdItem.java @@ -0,0 +1,159 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.dex.file; + +import com.android.jack.dx.dex.SizeOf; +import com.android.jack.dx.rop.cst.CstString; +import com.android.jack.dx.rop.type.Prototype; +import com.android.jack.dx.rop.type.StdTypeList; +import com.android.jack.dx.rop.type.Type; +import com.android.jack.dx.util.AnnotatedOutput; +import com.android.jack.dx.util.Hex; + +/** + * Representation of a method prototype reference inside a Dalvik file. + */ +public final class ProtoIdItem extends IndexedItem { + /** {@code non-null;} the wrapped prototype */ + private final Prototype prototype; + + /** {@code non-null;} the short-form of the prototype */ + private final CstString shortForm; + + /** + * {@code null-ok;} the list of parameter types or {@code null} if this + * prototype has no parameters + */ + private TypeListItem parameterTypes; + + /** + * Constructs an instance. + * + * @param prototype {@code non-null;} the constant for the prototype + */ + public ProtoIdItem(Prototype prototype) { + if (prototype == null) { + throw new NullPointerException("prototype == null"); + } + + this.prototype = prototype; + this.shortForm = makeShortForm(prototype); + + StdTypeList parameters = prototype.getParameterTypes(); + this.parameterTypes = (parameters.size() == 0) ? null + : new TypeListItem(parameters); + } + + /** + * Creates the short-form of the given prototype. + * + * @param prototype {@code non-null;} the prototype + * @return {@code non-null;} the short form + */ + private static CstString makeShortForm(Prototype prototype) { + StdTypeList parameters = prototype.getParameterTypes(); + int size = parameters.size(); + StringBuilder sb = new StringBuilder(size + 1); + + sb.append(shortFormCharFor(prototype.getReturnType())); + + for (int i = 0; i < size; i++) { + sb.append(shortFormCharFor(parameters.getType(i))); + } + + return new CstString(sb.toString()); + } + + /** + * Gets the short-form character for the given type. + * + * @param type {@code non-null;} the type + * @return the corresponding short-form character + */ + private static char shortFormCharFor(Type type) { + char descriptorChar = type.getDescriptor().charAt(0); + + if (descriptorChar == '[') { + return 'L'; + } + + return descriptorChar; + } + + /** {@inheritDoc} */ + @Override + public ItemType itemType() { + return ItemType.TYPE_PROTO_ID_ITEM; + } + + /** {@inheritDoc} */ + @Override + public int writeSize() { + return SizeOf.PROTO_ID_ITEM; + } + + /** {@inheritDoc} */ + @Override + public void addContents(DexFile file) { + StringIdsSection stringIds = file.getStringIds(); + TypeIdsSection typeIds = file.getTypeIds(); + MixedItemSection typeLists = file.getTypeLists(); + + typeIds.intern(prototype.getReturnType()); + stringIds.intern(shortForm); + + if (parameterTypes != null) { + parameterTypes = typeLists.intern(parameterTypes); + } + } + + /** {@inheritDoc} */ + @Override + public void writeTo(DexFile file, AnnotatedOutput out) { + int shortyIdx = file.getStringIds().indexOf(shortForm); + int returnIdx = file.getTypeIds().indexOf(prototype.getReturnType()); + int paramsOff = OffsettedItem.getAbsoluteOffsetOr0(parameterTypes); + + if (out.annotates()) { + StringBuilder sb = new StringBuilder(); + sb.append(prototype.getReturnType().toHuman()); + sb.append(" proto("); + + StdTypeList params = prototype.getParameterTypes(); + int size = params.size(); + + for (int i = 0; i < size; i++) { + if (i != 0) { + sb.append(", "); + } + sb.append(params.getType(i).toHuman()); + } + + sb.append(")"); + out.annotate(0, indexString() + ' ' + sb.toString()); + out.annotate(4, " shorty_idx: " + Hex.u4(shortyIdx) + + " // " + shortForm.toQuoted()); + out.annotate(4, " return_type_idx: " + Hex.u4(returnIdx) + + " // " + prototype.getReturnType().toHuman()); + out.annotate(4, " parameters_off: " + Hex.u4(paramsOff)); + } + + out.writeInt(shortyIdx); + out.writeInt(returnIdx); + out.writeInt(paramsOff); + } +} diff --git a/dx/src/com/android/jack/dx/dex/file/ProtoIdsSection.java b/dx/src/com/android/jack/dx/dex/file/ProtoIdsSection.java new file mode 100644 index 00000000..a588d005 --- /dev/null +++ b/dx/src/com/android/jack/dx/dex/file/ProtoIdsSection.java @@ -0,0 +1,140 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.dex.file; + +import com.android.jack.dx.rop.cst.Constant; +import com.android.jack.dx.rop.type.Prototype; +import com.android.jack.dx.util.AnnotatedOutput; +import com.android.jack.dx.util.Hex; + +import java.util.Collection; +import java.util.TreeMap; + +/** + * Proto (method prototype) identifiers list section of a + * {@code .dex} file. + */ +public final class ProtoIdsSection extends UniformItemSection { + /** + * {@code non-null;} map from method prototypes to {@link ProtoIdItem} instances + */ + private final TreeMap protoIds; + + /** + * Constructs an instance. The file offset is initially unknown. + * + * @param file {@code non-null;} file that this instance is part of + */ + public ProtoIdsSection(DexFile file) { + super("proto_ids", file, 4); + + protoIds = new TreeMap(); + } + + /** {@inheritDoc} */ + @Override + public Collection items() { + return protoIds.values(); + } + + /** {@inheritDoc} */ + @Override + public IndexedItem get(Constant cst) { + throw new UnsupportedOperationException("unsupported"); + } + + /** + * Writes the portion of the file header that refers to this instance. + * + * @param out {@code non-null;} where to write + */ + public void writeHeaderPart(AnnotatedOutput out) { + throwIfNotPrepared(); + + int sz = protoIds.size(); + int offset = (sz == 0) ? 0 : getFileOffset(); + + if (sz > 65536) { + throw new UnsupportedOperationException("too many proto ids"); + } + + if (out.annotates()) { + out.annotate(4, "proto_ids_size: " + Hex.u4(sz)); + out.annotate(4, "proto_ids_off: " + Hex.u4(offset)); + } + + out.writeInt(sz); + out.writeInt(offset); + } + + /** + * Interns an element into this instance. + * + * @param prototype {@code non-null;} the prototype to intern + * @return {@code non-null;} the interned reference + */ + public ProtoIdItem intern(Prototype prototype) { + if (prototype == null) { + throw new NullPointerException("prototype == null"); + } + + throwIfPrepared(); + + ProtoIdItem result = protoIds.get(prototype); + + if (result == null) { + result = new ProtoIdItem(prototype); + protoIds.put(prototype, result); + } + + return result; + } + + /** + * Gets the index of the given prototype, which must have + * been added to this instance. + * + * @param prototype {@code non-null;} the prototype to look up + * @return {@code >= 0;} the reference's index + */ + public int indexOf(Prototype prototype) { + if (prototype == null) { + throw new NullPointerException("prototype == null"); + } + + throwIfNotPrepared(); + + ProtoIdItem item = protoIds.get(prototype); + + if (item == null) { + throw new IllegalArgumentException("not found"); + } + + return item.getIndex(); + } + + /** {@inheritDoc} */ + @Override + protected void orderItems() { + int idx = 0; + + for (Object i : items()) { + ((ProtoIdItem) i).setIndex(idx); + idx++; + } + } +} diff --git a/dx/src/com/android/jack/dx/dex/file/Section.java b/dx/src/com/android/jack/dx/dex/file/Section.java new file mode 100644 index 00000000..6b863641 --- /dev/null +++ b/dx/src/com/android/jack/dx/dex/file/Section.java @@ -0,0 +1,287 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.dex.file; + +import com.android.jack.dx.util.AnnotatedOutput; + +import java.util.Collection; + +/** + * A section of a {@code .dex} file. Each section consists of a list + * of items of some sort or other. + */ +public abstract class Section { + /** {@code null-ok;} name of this part, for annotation purposes */ + private final String name; + + /** {@code non-null;} file that this instance is part of */ + private final DexFile file; + + /** {@code > 0;} alignment requirement for the final output; + * must be a power of 2 */ + private final int alignment; + + /** {@code >= -1;} offset from the start of the file to this part, or + * {@code -1} if not yet known */ + private int fileOffset; + + /** whether {@link #prepare} has been called successfully on this + * instance */ + private boolean prepared; + + /** + * Validates an alignment. + * + * @param alignment the alignment + * @throws IllegalArgumentException thrown if {@code alignment} + * isn't a positive power of 2 + */ + public static void validateAlignment(int alignment) { + if ((alignment <= 0) || + (alignment & (alignment - 1)) != 0) { + throw new IllegalArgumentException("invalid alignment"); + } + } + + /** + * Constructs an instance. The file offset is initially unknown. + * + * @param name {@code null-ok;} the name of this instance, for annotation + * purposes + * @param file {@code non-null;} file that this instance is part of + * @param alignment {@code > 0;} alignment requirement for the final output; + * must be a power of 2 + */ + public Section(String name, DexFile file, int alignment) { + if (file == null) { + throw new NullPointerException("file == null"); + } + + validateAlignment(alignment); + + this.name = name; + this.file = file; + this.alignment = alignment; + this.fileOffset = -1; + this.prepared = false; + } + + /** + * Gets the file that this instance is part of. + * + * @return {@code non-null;} the file + */ + public final DexFile getFile() { + return file; + } + + /** + * Gets the alignment for this instance's final output. + * + * @return {@code > 0;} the alignment + */ + public final int getAlignment() { + return alignment; + } + + /** + * Gets the offset from the start of the file to this part. This + * throws an exception if the offset has not yet been set. + * + * @return {@code >= 0;} the file offset + */ + public final int getFileOffset() { + if (fileOffset < 0) { + throw new RuntimeException("fileOffset not set"); + } + + return fileOffset; + } + + /** + * Sets the file offset. It is only valid to call this method once + * once per instance. + * + * @param fileOffset {@code >= 0;} the desired offset from the start of the + * file where this for this instance + * @return {@code >= 0;} the offset that this instance should be placed at + * in order to meet its alignment constraint + */ + public final int setFileOffset(int fileOffset) { + if (fileOffset < 0) { + throw new IllegalArgumentException("fileOffset < 0"); + } + + if (this.fileOffset >= 0) { + throw new RuntimeException("fileOffset already set"); + } + + int mask = alignment - 1; + fileOffset = (fileOffset + mask) & ~mask; + + this.fileOffset = fileOffset; + + return fileOffset; + } + + /** + * Writes this instance to the given raw data object. + * + * @param out {@code non-null;} where to write to + */ + public final void writeTo(AnnotatedOutput out) { + throwIfNotPrepared(); + align(out); + + int cursor = out.getCursor(); + + if (fileOffset < 0) { + fileOffset = cursor; + } else if (fileOffset != cursor) { + throw new RuntimeException("alignment mismatch: for " + this + + ", at " + cursor + + ", but expected " + fileOffset); + } + + if (out.annotates()) { + if (name != null) { + out.annotate(0, "\n" + name + ":"); + } else if (cursor != 0) { + out.annotate(0, "\n"); + } + } + + writeTo0(out); + } + + /** + * Returns the absolute file offset, given an offset from the + * start of this instance's output. This is only valid to call + * once this instance has been assigned a file offset (via {@link + * #setFileOffset}). + * + * @param relative {@code >= 0;} the relative offset + * @return {@code >= 0;} the corresponding absolute file offset + */ + public final int getAbsoluteOffset(int relative) { + if (relative < 0) { + throw new IllegalArgumentException("relative < 0"); + } + + if (fileOffset < 0) { + throw new RuntimeException("fileOffset not yet set"); + } + + return fileOffset + relative; + } + + /** + * Returns the absolute file offset of the given item which must + * be contained in this section. This is only valid to call + * once this instance has been assigned a file offset (via {@link + * #setFileOffset}). + * + *

Note: Subclasses must implement this as appropriate for + * their contents.

+ * + * @param item {@code non-null;} the item in question + * @return {@code >= 0;} the item's absolute file offset + */ + public abstract int getAbsoluteItemOffset(Item item); + + /** + * Prepares this instance for writing. This performs any necessary + * prerequisites, including particularly adding stuff to other + * sections. This method may only be called once per instance; + * subsequent calls will throw an exception. + */ + public final void prepare() { + throwIfPrepared(); + prepare0(); + prepared = true; + } + + /** + * Gets the collection of all the items in this section. + * It is not valid to attempt to change the returned list. + * + * @return {@code non-null;} the items + */ + public abstract Collection items(); + + /** + * Does the main work of {@link #prepare}. + */ + protected abstract void prepare0(); + + /** + * Gets the size of this instance when output, in bytes. + * + * @return {@code >= 0;} the size of this instance, in bytes + */ + public abstract int writeSize(); + + /** + * Throws an exception if {@link #prepare} has not been + * called on this instance. + */ + protected final void throwIfNotPrepared() { + if (!prepared) { + throw new RuntimeException("not prepared"); + } + } + + /** + * Throws an exception if {@link #prepare} has already been called + * on this instance. + */ + protected final void throwIfPrepared() { + if (prepared) { + throw new RuntimeException("already prepared"); + } + } + + /** + * Aligns the output of the given data to the alignment of this instance. + * + * @param out {@code non-null;} the output to align + */ + protected final void align(AnnotatedOutput out) { + out.alignTo(alignment); + } + + /** + * Writes this instance to the given raw data object. This gets + * called by {@link #writeTo} after aligning the cursor of + * {@code out} and verifying that either the assigned file + * offset matches the actual cursor {@code out} or that the + * file offset was not previously assigned, in which case it gets + * assigned to {@code out}'s cursor. + * + * @param out {@code non-null;} where to write to + */ + protected abstract void writeTo0(AnnotatedOutput out); + + /** + * Returns the name of this section, for annotation purposes. + * + * @return {@code null-ok;} name of this part, for annotation purposes + */ + protected final String getName() { + return name; + } +} diff --git a/dx/src/com/android/jack/dx/dex/file/Statistics.java b/dx/src/com/android/jack/dx/dex/file/Statistics.java new file mode 100644 index 00000000..c31a9d8c --- /dev/null +++ b/dx/src/com/android/jack/dx/dex/file/Statistics.java @@ -0,0 +1,195 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.dex.file; + +import com.android.jack.dx.util.AnnotatedOutput; + +import java.util.Collection; +import java.util.HashMap; +import java.util.TreeMap; + +/** + * Statistics about the contents of a file. + */ +public final class Statistics { + /** {@code non-null;} data about each type of item */ + private final HashMap dataMap; + + /** + * Constructs an instance. + */ + public Statistics() { + dataMap = new HashMap(50); + } + + /** + * Adds the given item to the statistics. + * + * @param item {@code non-null;} the item to add + */ + public void add(Item item) { + String typeName = item.typeName(); + Data data = dataMap.get(typeName); + + if (data == null) { + dataMap.put(typeName, new Data(item, typeName)); + } else { + data.add(item); + } + } + + /** + * Adds the given list of items to the statistics. + * + * @param list {@code non-null;} the list of items to add + */ + public void addAll(Section list) { + Collection items = list.items(); + for (Item item : items) { + add(item); + } + } + + /** + * Writes the statistics as an annotation. + * + * @param out {@code non-null;} where to write to + */ + public final void writeAnnotation(AnnotatedOutput out) { + if (dataMap.size() == 0) { + return; + } + + out.annotate(0, "\nstatistics:\n"); + + TreeMap sortedData = new TreeMap(); + + for (Data data : dataMap.values()) { + sortedData.put(data.name, data); + } + + for (Data data : sortedData.values()) { + data.writeAnnotation(out); + } + } + + public String toHuman() { + StringBuilder sb = new StringBuilder(); + + sb.append("Statistics:\n"); + + TreeMap sortedData = new TreeMap(); + + for (Data data : dataMap.values()) { + sortedData.put(data.name, data); + } + + for (Data data : sortedData.values()) { + sb.append(data.toHuman()); + } + + return sb.toString(); + } + + /** + * Statistical data about a particular class. + */ + private static class Data { + /** {@code non-null;} name to use as a label */ + private final String name; + + /** {@code >= 0;} number of instances */ + private int count; + + /** {@code >= 0;} total size of instances in bytes */ + private int totalSize; + + /** {@code >= 0;} largest size of any individual item */ + private int largestSize; + + /** {@code >= 0;} smallest size of any individual item */ + private int smallestSize; + + /** + * Constructs an instance for the given item. + * + * @param item {@code non-null;} item in question + * @param name {@code non-null;} type name to use + */ + public Data(Item item, String name) { + int size = item.writeSize(); + + this.name = name; + this.count = 1; + this.totalSize = size; + this.largestSize = size; + this.smallestSize = size; + } + + /** + * Incorporates a new item. This assumes the type name matches. + * + * @param item {@code non-null;} item to incorporate + */ + public void add(Item item) { + int size = item.writeSize(); + + count++; + totalSize += size; + + if (size > largestSize) { + largestSize = size; + } + + if (size < smallestSize) { + smallestSize = size; + } + } + + /** + * Writes this instance as an annotation. + * + * @param out {@code non-null;} where to write to + */ + public void writeAnnotation(AnnotatedOutput out) { + out.annotate(toHuman()); + } + + /** + * Generates a human-readable string for this data item. + * + * @return string for human consumption. + */ + public String toHuman() { + StringBuilder sb = new StringBuilder(); + + sb.append(" " + name + ": " + + count + " item" + (count == 1 ? "" : "s") + "; " + + totalSize + " bytes total\n"); + + if (smallestSize == largestSize) { + sb.append(" " + smallestSize + " bytes/item\n"); + } else { + int average = totalSize / count; + sb.append(" " + smallestSize + ".." + largestSize + + " bytes/item; average " + average + "\n"); + } + + return sb.toString(); + } + } +} diff --git a/dx/src/com/android/jack/dx/dex/file/StringDataItem.java b/dx/src/com/android/jack/dx/dex/file/StringDataItem.java new file mode 100644 index 00000000..ee91266b --- /dev/null +++ b/dx/src/com/android/jack/dx/dex/file/StringDataItem.java @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.dex.file; + +import com.android.jack.dx.rop.cst.CstString; +import com.android.jack.dx.util.AnnotatedOutput; +import com.android.jack.dx.util.ByteArray; +import com.android.jack.dx.util.Hex; +import com.android.jack.dx.util.Leb128Utils; + +/** + * Representation of string data for a particular string, in a Dalvik file. + */ +public final class StringDataItem extends OffsettedItem { + /** {@code non-null;} the string value */ + private final CstString value; + + /** + * Constructs an instance. + * + * @param value {@code non-null;} the string value + */ + public StringDataItem(CstString value) { + super(1, writeSize(value)); + + this.value = value; + } + + /** + * Gets the write size for a given value. + * + * @param value {@code non-null;} the string value + * @return {@code >= 2}; the write size, in bytes + */ + private static int writeSize(CstString value) { + int utf16Size = value.getUtf16Size(); + + // The +1 is for the '\0' termination byte. + return Leb128Utils.unsignedLeb128Size(utf16Size) + + value.getUtf8Size() + 1; + } + + /** {@inheritDoc} */ + @Override + public ItemType itemType() { + return ItemType.TYPE_STRING_DATA_ITEM; + } + + /** {@inheritDoc} */ + @Override + public void addContents(DexFile file) { + // Nothing to do here. + } + + /** {@inheritDoc} */ + @Override + public void writeTo0(DexFile file, AnnotatedOutput out) { + ByteArray bytes = value.getBytes(); + int utf16Size = value.getUtf16Size(); + + if (out.annotates()) { + out.annotate(Leb128Utils.unsignedLeb128Size(utf16Size), + "utf16_size: " + Hex.u4(utf16Size)); + out.annotate(bytes.size() + 1, value.toQuoted()); + } + + out.writeUleb128(utf16Size); + out.write(bytes); + out.writeByte(0); + } + + /** {@inheritDoc} */ + @Override + public String toHuman() { + return value.toQuoted(); + } + + /** {@inheritDoc} */ + @Override + protected int compareTo0(OffsettedItem other) { + StringDataItem otherData = (StringDataItem) other; + + return value.compareTo(otherData.value); + } +} diff --git a/dx/src/com/android/jack/dx/dex/file/StringIdItem.java b/dx/src/com/android/jack/dx/dex/file/StringIdItem.java new file mode 100644 index 00000000..ec2bc18d --- /dev/null +++ b/dx/src/com/android/jack/dx/dex/file/StringIdItem.java @@ -0,0 +1,126 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.dex.file; + +import com.android.jack.dx.dex.SizeOf; +import com.android.jack.dx.rop.cst.CstString; +import com.android.jack.dx.util.AnnotatedOutput; +import com.android.jack.dx.util.Hex; + +/** + * Representation of a string inside a Dalvik file. + */ +public final class StringIdItem + extends IndexedItem implements Comparable { + /** {@code non-null;} the string value */ + private final CstString value; + + /** {@code null-ok;} associated string data object, if known */ + private StringDataItem data; + + /** + * Constructs an instance. + * + * @param value {@code non-null;} the string value + */ + public StringIdItem(CstString value) { + if (value == null) { + throw new NullPointerException("value == null"); + } + + this.value = value; + this.data = null; + } + + /** {@inheritDoc} */ + @Override + public boolean equals(Object other) { + if (!(other instanceof StringIdItem)) { + return false; + } + + StringIdItem otherString = (StringIdItem) other; + return value.equals(otherString.value); + } + + /** {@inheritDoc} */ + @Override + public int hashCode() { + return value.hashCode(); + } + + /** {@inheritDoc} */ + public int compareTo(Object other) { + StringIdItem otherString = (StringIdItem) other; + return value.compareTo(otherString.value); + } + + /** {@inheritDoc} */ + @Override + public ItemType itemType() { + return ItemType.TYPE_STRING_ID_ITEM; + } + + /** {@inheritDoc} */ + @Override + public int writeSize() { + return SizeOf.STRING_ID_ITEM; + } + + /** {@inheritDoc} */ + @Override + public void addContents(DexFile file) { + if (data == null) { + // The string data hasn't yet been added, so add it. + MixedItemSection stringData = file.getStringData(); + data = new StringDataItem(value); + stringData.add(data); + } + } + + /** {@inheritDoc} */ + @Override + public void writeTo(DexFile file, AnnotatedOutput out) { + int dataOff = data.getAbsoluteOffset(); + + if (out.annotates()) { + out.annotate(0, indexString() + ' ' + value.toQuoted(100)); + out.annotate(4, " string_data_off: " + Hex.u4(dataOff)); + } + + out.writeInt(dataOff); + } + + /** + * Gets the string value. + * + * @return {@code non-null;} the value + */ + public CstString getValue() { + return value; + } + + /** + * Gets the associated data object for this instance, if known. + * + * @return {@code null-ok;} the associated data object or {@code null} + * if not yet known + */ + public StringDataItem getData() { + return data; + } +} diff --git a/dx/src/com/android/jack/dx/dex/file/StringIdsSection.java b/dx/src/com/android/jack/dx/dex/file/StringIdsSection.java new file mode 100644 index 00000000..d09563e2 --- /dev/null +++ b/dx/src/com/android/jack/dx/dex/file/StringIdsSection.java @@ -0,0 +1,182 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.dex.file; + +import com.android.jack.dx.rop.cst.Constant; +import com.android.jack.dx.rop.cst.CstNat; +import com.android.jack.dx.rop.cst.CstString; +import com.android.jack.dx.util.AnnotatedOutput; +import com.android.jack.dx.util.Hex; + +import java.util.Collection; +import java.util.TreeMap; + +/** + * Strings list section of a {@code .dex} file. + */ +public final class StringIdsSection + extends UniformItemSection { + /** + * {@code non-null;} map from string constants to {@link + * StringIdItem} instances + */ + private final TreeMap strings; + + /** + * Constructs an instance. The file offset is initially unknown. + * + * @param file {@code non-null;} file that this instance is part of + */ + public StringIdsSection(DexFile file) { + super("string_ids", file, 4); + + strings = new TreeMap(); + } + + /** {@inheritDoc} */ + @Override + public Collection items() { + return strings.values(); + } + + /** {@inheritDoc} */ + @Override + public IndexedItem get(Constant cst) { + if (cst == null) { + throw new NullPointerException("cst == null"); + } + + throwIfNotPrepared(); + + IndexedItem result = strings.get((CstString) cst); + + if (result == null) { + throw new IllegalArgumentException("not found"); + } + + return result; + } + + /** + * Writes the portion of the file header that refers to this instance. + * + * @param out {@code non-null;} where to write + */ + public void writeHeaderPart(AnnotatedOutput out) { + throwIfNotPrepared(); + + int sz = strings.size(); + int offset = (sz == 0) ? 0 : getFileOffset(); + + if (out.annotates()) { + out.annotate(4, "string_ids_size: " + Hex.u4(sz)); + out.annotate(4, "string_ids_off: " + Hex.u4(offset)); + } + + out.writeInt(sz); + out.writeInt(offset); + } + + /** + * Interns an element into this instance. + * + * @param string {@code non-null;} the string to intern, as a regular Java + * {@code String} + * @return {@code non-null;} the interned string + */ + public StringIdItem intern(String string) { + return intern(new StringIdItem(new CstString(string))); + } + + /** + * Interns an element into this instance. + * + * @param string {@code non-null;} the string to intern, as a constant + * @return {@code non-null;} the interned string + */ + public StringIdItem intern(CstString string) { + return intern(new StringIdItem(string)); + } + + /** + * Interns an element into this instance. + * + * @param string {@code non-null;} the string to intern + * @return {@code non-null;} the interned string + */ + public StringIdItem intern(StringIdItem string) { + if (string == null) { + throw new NullPointerException("string == null"); + } + + throwIfPrepared(); + + CstString value = string.getValue(); + StringIdItem already = strings.get(value); + + if (already != null) { + return already; + } + + strings.put(value, string); + return string; + } + + /** + * Interns the components of a name-and-type into this instance. + * + * @param nat {@code non-null;} the name-and-type + */ + public void intern(CstNat nat) { + intern(nat.getName()); + intern(nat.getDescriptor()); + } + + /** + * Gets the index of the given string, which must have been added + * to this instance. + * + * @param string {@code non-null;} the string to look up + * @return {@code >= 0;} the string's index + */ + public int indexOf(CstString string) { + if (string == null) { + throw new NullPointerException("string == null"); + } + + throwIfNotPrepared(); + + StringIdItem s = strings.get(string); + + if (s == null) { + throw new IllegalArgumentException("not found"); + } + + return s.getIndex(); + } + + /** {@inheritDoc} */ + @Override + protected void orderItems() { + int idx = 0; + + for (StringIdItem s : strings.values()) { + s.setIndex(idx); + idx++; + } + } +} diff --git a/dx/src/com/android/jack/dx/dex/file/TypeIdItem.java b/dx/src/com/android/jack/dx/dex/file/TypeIdItem.java new file mode 100644 index 00000000..cdbfb44b --- /dev/null +++ b/dx/src/com/android/jack/dx/dex/file/TypeIdItem.java @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.dex.file; + +import com.android.jack.dx.dex.SizeOf; +import com.android.jack.dx.rop.cst.CstString; +import com.android.jack.dx.rop.cst.CstType; +import com.android.jack.dx.util.AnnotatedOutput; +import com.android.jack.dx.util.Hex; + +/** + * Representation of a type reference inside a Dalvik file. + */ +public final class TypeIdItem extends IdItem { + /** + * Constructs an instance. + * + * @param type {@code non-null;} the constant for the type + */ + public TypeIdItem(CstType type) { + super(type); + } + + /** {@inheritDoc} */ + @Override + public ItemType itemType() { + return ItemType.TYPE_TYPE_ID_ITEM; + } + + /** {@inheritDoc} */ + @Override + public int writeSize() { + return SizeOf.TYPE_ID_ITEM; + } + + /** {@inheritDoc} */ + @Override + public void addContents(DexFile file) { + file.getStringIds().intern(getDefiningClass().getDescriptor()); + } + + /** {@inheritDoc} */ + @Override + public void writeTo(DexFile file, AnnotatedOutput out) { + CstType type = getDefiningClass(); + CstString descriptor = type.getDescriptor(); + int idx = file.getStringIds().indexOf(descriptor); + + if (out.annotates()) { + out.annotate(0, indexString() + ' ' + descriptor.toHuman()); + out.annotate(4, " descriptor_idx: " + Hex.u4(idx)); + } + + out.writeInt(idx); + } +} diff --git a/dx/src/com/android/jack/dx/dex/file/TypeIdsSection.java b/dx/src/com/android/jack/dx/dex/file/TypeIdsSection.java new file mode 100644 index 00000000..fede94e8 --- /dev/null +++ b/dx/src/com/android/jack/dx/dex/file/TypeIdsSection.java @@ -0,0 +1,192 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.dex.file; + +import com.android.jack.dx.rop.cst.Constant; +import com.android.jack.dx.rop.cst.CstType; +import com.android.jack.dx.rop.type.Type; +import com.android.jack.dx.util.AnnotatedOutput; +import com.android.jack.dx.util.Hex; + +import java.util.Collection; +import java.util.TreeMap; + +/** + * Type identifiers list section of a {@code .dex} file. + */ +public final class TypeIdsSection extends UniformItemSection { + /** + * {@code non-null;} map from types to {@link TypeIdItem} instances + */ + private final TreeMap typeIds; + + /** + * Constructs an instance. The file offset is initially unknown. + * + * @param file {@code non-null;} file that this instance is part of + */ + public TypeIdsSection(DexFile file) { + super("type_ids", file, 4); + + typeIds = new TreeMap(); + } + + /** {@inheritDoc} */ + @Override + public Collection items() { + return typeIds.values(); + } + + /** {@inheritDoc} */ + @Override + public IndexedItem get(Constant cst) { + if (cst == null) { + throw new NullPointerException("cst == null"); + } + + throwIfNotPrepared(); + + Type type = ((CstType) cst).getClassType(); + IndexedItem result = typeIds.get(type); + + if (result == null) { + throw new IllegalArgumentException("not found: " + cst); + } + + return result; + } + + /** + * Writes the portion of the file header that refers to this instance. + * + * @param out {@code non-null;} where to write + */ + public void writeHeaderPart(AnnotatedOutput out) { + throwIfNotPrepared(); + + int sz = typeIds.size(); + int offset = (sz == 0) ? 0 : getFileOffset(); + + if (sz > 65536) { + throw new UnsupportedOperationException("too many type ids"); + } + + if (out.annotates()) { + out.annotate(4, "type_ids_size: " + Hex.u4(sz)); + out.annotate(4, "type_ids_off: " + Hex.u4(offset)); + } + + out.writeInt(sz); + out.writeInt(offset); + } + + /** + * Interns an element into this instance. + * + * @param type {@code non-null;} the type to intern + * @return {@code non-null;} the interned reference + */ + public TypeIdItem intern(Type type) { + if (type == null) { + throw new NullPointerException("type == null"); + } + + throwIfPrepared(); + + TypeIdItem result = typeIds.get(type); + + if (result == null) { + result = new TypeIdItem(new CstType(type)); + typeIds.put(type, result); + } + + return result; + } + + /** + * Interns an element into this instance. + * + * @param type {@code non-null;} the type to intern + * @return {@code non-null;} the interned reference + */ + public TypeIdItem intern(CstType type) { + if (type == null) { + throw new NullPointerException("type == null"); + } + + throwIfPrepared(); + + Type typePerSe = type.getClassType(); + TypeIdItem result = typeIds.get(typePerSe); + + if (result == null) { + result = new TypeIdItem(type); + typeIds.put(typePerSe, result); + } + + return result; + } + + /** + * Gets the index of the given type, which must have + * been added to this instance. + * + * @param type {@code non-null;} the type to look up + * @return {@code >= 0;} the reference's index + */ + public int indexOf(Type type) { + if (type == null) { + throw new NullPointerException("type == null"); + } + + throwIfNotPrepared(); + + TypeIdItem item = typeIds.get(type); + + if (item == null) { + throw new IllegalArgumentException("not found: " + type); + } + + return item.getIndex(); + } + + /** + * Gets the index of the given type, which must have + * been added to this instance. + * + * @param type {@code non-null;} the type to look up + * @return {@code >= 0;} the reference's index + */ + public int indexOf(CstType type) { + if (type == null) { + throw new NullPointerException("type == null"); + } + + return indexOf(type.getClassType()); + } + + /** {@inheritDoc} */ + @Override + protected void orderItems() { + int idx = 0; + + for (Object i : items()) { + ((TypeIdItem) i).setIndex(idx); + idx++; + } + } +} diff --git a/dx/src/com/android/jack/dx/dex/file/TypeListItem.java b/dx/src/com/android/jack/dx/dex/file/TypeListItem.java new file mode 100644 index 00000000..42c1c5ad --- /dev/null +++ b/dx/src/com/android/jack/dx/dex/file/TypeListItem.java @@ -0,0 +1,122 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.dex.file; + +import com.android.jack.dx.rop.cst.CstType; +import com.android.jack.dx.rop.type.StdTypeList; +import com.android.jack.dx.rop.type.Type; +import com.android.jack.dx.rop.type.TypeList; +import com.android.jack.dx.util.AnnotatedOutput; +import com.android.jack.dx.util.Hex; + +/** + * Representation of a list of class references. + */ +public final class TypeListItem extends OffsettedItem { + /** alignment requirement */ + private static final int ALIGNMENT = 4; + + /** element size in bytes */ + private static final int ELEMENT_SIZE = 2; + + /** header size in bytes */ + private static final int HEADER_SIZE = 4; + + /** {@code non-null;} the actual list */ + private final TypeList list; + + /** + * Constructs an instance. + * + * @param list {@code non-null;} the actual list + */ + public TypeListItem(TypeList list) { + super(ALIGNMENT, (list.size() * ELEMENT_SIZE) + HEADER_SIZE); + + this.list = list; + } + + /** {@inheritDoc} */ + @Override + public int hashCode() { + return StdTypeList.hashContents(list); + } + + /** {@inheritDoc} */ + @Override + public ItemType itemType() { + return ItemType.TYPE_TYPE_LIST; + } + + /** {@inheritDoc} */ + public void addContents(DexFile file) { + TypeIdsSection typeIds = file.getTypeIds(); + int sz = list.size(); + + for (int i = 0; i < sz; i++) { + typeIds.intern(list.getType(i)); + } + } + + /** {@inheritDoc} */ + @Override + public String toHuman() { + throw new RuntimeException("unsupported"); + } + + /** + * Gets the underlying list. + * + * @return {@code non-null;} the list + */ + public TypeList getList() { + return list; + } + + /** {@inheritDoc} */ + @Override + protected void writeTo0(DexFile file, AnnotatedOutput out) { + TypeIdsSection typeIds = file.getTypeIds(); + int sz = list.size(); + + if (out.annotates()) { + out.annotate(0, offsetString() + " type_list"); + out.annotate(HEADER_SIZE, " size: " + Hex.u4(sz)); + for (int i = 0; i < sz; i++) { + Type one = list.getType(i); + int idx = typeIds.indexOf(one); + out.annotate(ELEMENT_SIZE, + " " + Hex.u2(idx) + " // " + one.toHuman()); + } + } + + out.writeInt(sz); + + for (int i = 0; i < sz; i++) { + out.writeShort(typeIds.indexOf(list.getType(i))); + } + } + + /** {@inheritDoc} */ + @Override + protected int compareTo0(OffsettedItem other) { + TypeList thisList = this.list; + TypeList otherList = ((TypeListItem) other).list; + + return StdTypeList.compareContents(thisList, otherList); + } +} diff --git a/dx/src/com/android/jack/dx/dex/file/UniformItemSection.java b/dx/src/com/android/jack/dx/dex/file/UniformItemSection.java new file mode 100644 index 00000000..0e4502d7 --- /dev/null +++ b/dx/src/com/android/jack/dx/dex/file/UniformItemSection.java @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.dex.file; + +import com.android.jack.dx.rop.cst.Constant; +import com.android.jack.dx.util.AnnotatedOutput; + +import java.util.Collection; + +/** + * A section of a {@code .dex} file which consists of a sequence of + * {@link Item} objects. Each of the items must have the same size in + * the output. + */ +public abstract class UniformItemSection extends Section { + /** + * Constructs an instance. The file offset is initially unknown. + * + * @param name {@code null-ok;} the name of this instance, for annotation + * purposes + * @param file {@code non-null;} file that this instance is part of + * @param alignment {@code > 0;} alignment requirement for the final output; + * must be a power of 2 + */ + public UniformItemSection(String name, DexFile file, int alignment) { + super(name, file, alignment); + } + + /** {@inheritDoc} */ + @Override + public final int writeSize() { + Collection items = items(); + int sz = items.size(); + + if (sz == 0) { + return 0; + } + + // Since each item has to be the same size, we can pick any. + return sz * items.iterator().next().writeSize(); + } + + /** + * Gets the item corresponding to the given {@link Constant}. This + * will throw an exception if the constant is not found, including + * if this instance isn't the sort that maps constants to {@link + * IndexedItem} instances. + * + * @param cst {@code non-null;} constant to look for + * @return {@code non-null;} the corresponding item found in this instance + */ + public abstract IndexedItem get(Constant cst); + + /** {@inheritDoc} */ + @Override + protected final void prepare0() { + DexFile file = getFile(); + + orderItems(); + + for (Item one : items()) { + one.addContents(file); + } + } + + /** {@inheritDoc} */ + @Override + protected final void writeTo0(AnnotatedOutput out) { + DexFile file = getFile(); + int alignment = getAlignment(); + + for (Item one : items()) { + one.writeTo(file, out); + out.alignTo(alignment); + } + } + + /** {@inheritDoc} */ + @Override + public final int getAbsoluteItemOffset(Item item) { + /* + * Since all items must be the same size, we can use the size + * of the one we're given to calculate its offset. + */ + IndexedItem ii = (IndexedItem) item; + int relativeOffset = ii.getIndex() * ii.writeSize(); + + return getAbsoluteOffset(relativeOffset); + } + + /** + * Alters or picks the order for items in this instance if desired, + * so that subsequent calls to {@link #items} will yield a + * so-ordered collection. If the items in this instance are indexed, + * then this method should also assign indices. + */ + protected abstract void orderItems(); +} diff --git a/dx/src/com/android/jack/dx/dex/file/UniformListItem.java b/dx/src/com/android/jack/dx/dex/file/UniformListItem.java new file mode 100644 index 00000000..1046c197 --- /dev/null +++ b/dx/src/com/android/jack/dx/dex/file/UniformListItem.java @@ -0,0 +1,216 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.dex.file; + +import com.android.jack.dx.util.AnnotatedOutput; +import com.android.jack.dx.util.Hex; + +import java.util.HashMap; +import java.util.List; + +/** + * Class that represents a contiguous list of uniform items. Each + * item in the list, in particular, must have the same write size and + * alignment. + * + *

This class inherits its alignment from its items, bumped up to + * {@code 4} if the items have a looser alignment requirement. If + * it is more than {@code 4}, then there will be a gap after the + * output list size (which is four bytes) and before the first item.

+ * + * @param type of element contained in an instance + */ +public final class UniformListItem + extends OffsettedItem { + /** the size of the list header */ + private static final int HEADER_SIZE = 4; + + /** {@code non-null;} the item type */ + private final ItemType itemType; + + /** {@code non-null;} the contents */ + private final List items; + + /** + * Constructs an instance. It is illegal to modify the given list once + * it is used to construct an instance of this class. + * + * @param itemType {@code non-null;} the type of the item + * @param items {@code non-null and non-empty;} list of items to represent + */ + public UniformListItem(ItemType itemType, List items) { + super(getAlignment(items), writeSize(items)); + + if (itemType == null) { + throw new NullPointerException("itemType == null"); + } + + this.items = items; + this.itemType = itemType; + } + + /** + * Helper for {@link #UniformListItem}, which returns the alignment + * requirement implied by the given list. See the header comment for + * more details. + * + * @param items {@code non-null;} list of items being represented + * @return {@code >= 4;} the alignment requirement + */ + private static int getAlignment(List items) { + try { + // Since they all must have the same alignment, any one will do. + return Math.max(HEADER_SIZE, items.get(0).getAlignment()); + } catch (IndexOutOfBoundsException ex) { + // Translate the exception. + throw new IllegalArgumentException("items.size() == 0"); + } catch (NullPointerException ex) { + // Translate the exception. + throw new NullPointerException("items == null"); + } + } + + /** + * Calculates the write size for the given list. + * + * @param items {@code non-null;} the list in question + * @return {@code >= 0;} the write size + */ + private static int writeSize(List items) { + /* + * This class assumes all included items are the same size, + * an assumption which is verified in place0(). + */ + OffsettedItem first = items.get(0); + return (items.size() * first.writeSize()) + getAlignment(items); + } + + /** {@inheritDoc} */ + @Override + public ItemType itemType() { + return itemType; + } + + /** {@inheritDoc} */ + @Override + public String toString() { + StringBuffer sb = new StringBuffer(100); + + sb.append(getClass().getName()); + sb.append(items); + + return sb.toString(); + } + + /** {@inheritDoc} */ + @Override + public void addContents(DexFile file) { + for (OffsettedItem i : items) { + i.addContents(file); + } + } + + /** {@inheritDoc} */ + @Override + public final String toHuman() { + StringBuffer sb = new StringBuffer(100); + boolean first = true; + + sb.append("{"); + + for (OffsettedItem i : items) { + if (first) { + first = false; + } else { + sb.append(", "); + } + sb.append(i.toHuman()); + } + + sb.append("}"); + return sb.toString(); + } + + /** + * Gets the underlying list of items. + * + * @return {@code non-null;} the list + */ + public final List getItems() { + return items; + } + + /** {@inheritDoc} */ + @Override + protected void place0(Section addedTo, int offset) { + offset += headerSize(); + + boolean first = true; + int theSize = -1; + int theAlignment = -1; + + for (OffsettedItem i : items) { + int size = i.writeSize(); + if (first) { + theSize = size; + theAlignment = i.getAlignment(); + first = false; + } else { + if (size != theSize) { + throw new UnsupportedOperationException( + "item size mismatch"); + } + if (i.getAlignment() != theAlignment) { + throw new UnsupportedOperationException( + "item alignment mismatch"); + } + } + + offset = i.place(addedTo, offset) + size; + } + } + + /** {@inheritDoc} */ + @Override + protected void writeTo0(DexFile file, AnnotatedOutput out) { + int size = items.size(); + + if (out.annotates()) { + out.annotate(0, offsetString() + " " + typeName()); + out.annotate(4, " size: " + Hex.u4(size)); + } + + out.writeInt(size); + + for (OffsettedItem i : items) { + i.writeTo(file, out); + } + } + + /** + * Get the size of the header of this list. + * + * @return {@code >= 0;} the header size + */ + private int headerSize() { + /* + * Because of how this instance was set up, this is the same + * as the alignment. + */ + return getAlignment(); + } +} diff --git a/dx/src/com/android/jack/dx/dex/file/ValueEncoder.java b/dx/src/com/android/jack/dx/dex/file/ValueEncoder.java new file mode 100644 index 00000000..c8a80191 --- /dev/null +++ b/dx/src/com/android/jack/dx/dex/file/ValueEncoder.java @@ -0,0 +1,528 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.dex.file; + +import com.android.jack.dx.rop.annotation.Annotation; +import com.android.jack.dx.rop.annotation.NameValuePair; +import com.android.jack.dx.rop.cst.Constant; +import com.android.jack.dx.rop.cst.CstAnnotation; +import com.android.jack.dx.rop.cst.CstArray; +import com.android.jack.dx.rop.cst.CstBoolean; +import com.android.jack.dx.rop.cst.CstByte; +import com.android.jack.dx.rop.cst.CstChar; +import com.android.jack.dx.rop.cst.CstDouble; +import com.android.jack.dx.rop.cst.CstEnumRef; +import com.android.jack.dx.rop.cst.CstFieldRef; +import com.android.jack.dx.rop.cst.CstFloat; +import com.android.jack.dx.rop.cst.CstInteger; +import com.android.jack.dx.rop.cst.CstKnownNull; +import com.android.jack.dx.rop.cst.CstLiteralBits; +import com.android.jack.dx.rop.cst.CstLong; +import com.android.jack.dx.rop.cst.CstMethodRef; +import com.android.jack.dx.rop.cst.CstShort; +import com.android.jack.dx.rop.cst.CstString; +import com.android.jack.dx.rop.cst.CstType; +import com.android.jack.dx.util.AnnotatedOutput; +import com.android.jack.dx.util.Hex; + +import java.util.Collection; + +/** + * Handler for writing out {@code encoded_values} and parts + * thereof. + */ +public final class ValueEncoder { + /** annotation value type constant: {@code byte} */ + private static final int VALUE_BYTE = 0x00; + + /** annotation value type constant: {@code short} */ + private static final int VALUE_SHORT = 0x02; + + /** annotation value type constant: {@code char} */ + private static final int VALUE_CHAR = 0x03; + + /** annotation value type constant: {@code int} */ + private static final int VALUE_INT = 0x04; + + /** annotation value type constant: {@code long} */ + private static final int VALUE_LONG = 0x06; + + /** annotation value type constant: {@code float} */ + private static final int VALUE_FLOAT = 0x10; + + /** annotation value type constant: {@code double} */ + private static final int VALUE_DOUBLE = 0x11; + + /** annotation value type constant: {@code string} */ + private static final int VALUE_STRING = 0x17; + + /** annotation value type constant: {@code type} */ + private static final int VALUE_TYPE = 0x18; + + /** annotation value type constant: {@code field} */ + private static final int VALUE_FIELD = 0x19; + + /** annotation value type constant: {@code method} */ + private static final int VALUE_METHOD = 0x1a; + + /** annotation value type constant: {@code enum} */ + private static final int VALUE_ENUM = 0x1b; + + /** annotation value type constant: {@code array} */ + private static final int VALUE_ARRAY = 0x1c; + + /** annotation value type constant: {@code annotation} */ + private static final int VALUE_ANNOTATION = 0x1d; + + /** annotation value type constant: {@code null} */ + private static final int VALUE_NULL = 0x1e; + + /** annotation value type constant: {@code boolean} */ + private static final int VALUE_BOOLEAN = 0x1f; + + /** {@code non-null;} file being written */ + private final DexFile file; + + /** {@code non-null;} output stream to write to */ + private final AnnotatedOutput out; + + /** + * Construct an instance. + * + * @param file {@code non-null;} file being written + * @param out {@code non-null;} output stream to write to + */ + public ValueEncoder(DexFile file, AnnotatedOutput out) { + if (file == null) { + throw new NullPointerException("file == null"); + } + + if (out == null) { + throw new NullPointerException("out == null"); + } + + this.file = file; + this.out = out; + } + + /** + * Writes out the encoded form of the given constant. + * + * @param cst {@code non-null;} the constant to write + */ + public void writeConstant(Constant cst) { + int type = constantToValueType(cst); + int arg; + + switch (type) { + case VALUE_BYTE: + case VALUE_SHORT: + case VALUE_INT: + case VALUE_LONG: { + long value = ((CstLiteralBits) cst).getLongBits(); + writeSignedIntegralValue(type, value); + break; + } + case VALUE_CHAR: { + long value = ((CstLiteralBits) cst).getLongBits(); + writeUnsignedIntegralValue(type, value); + break; + } + case VALUE_FLOAT: { + // Shift value left 32 so that right-zero-extension works. + long value = ((CstFloat) cst).getLongBits() << 32; + writeRightZeroExtendedValue(type, value); + break; + } + case VALUE_DOUBLE: { + long value = ((CstDouble) cst).getLongBits(); + writeRightZeroExtendedValue(type, value); + break; + } + case VALUE_STRING: { + int index = file.getStringIds().indexOf((CstString) cst); + writeUnsignedIntegralValue(type, (long) index); + break; + } + case VALUE_TYPE: { + int index = file.getTypeIds().indexOf((CstType) cst); + writeUnsignedIntegralValue(type, (long) index); + break; + } + case VALUE_FIELD: { + int index = file.getFieldIds().indexOf((CstFieldRef) cst); + writeUnsignedIntegralValue(type, (long) index); + break; + } + case VALUE_METHOD: { + int index = file.getMethodIds().indexOf((CstMethodRef) cst); + writeUnsignedIntegralValue(type, (long) index); + break; + } + case VALUE_ENUM: { + CstFieldRef fieldRef = ((CstEnumRef) cst).getFieldRef(); + int index = file.getFieldIds().indexOf(fieldRef); + writeUnsignedIntegralValue(type, (long) index); + break; + } + case VALUE_ARRAY: { + out.writeByte(type); + writeArray((CstArray) cst, false); + break; + } + case VALUE_ANNOTATION: { + out.writeByte(type); + writeAnnotation(((CstAnnotation) cst).getAnnotation(), + false); + break; + } + case VALUE_NULL: { + out.writeByte(type); + break; + } + case VALUE_BOOLEAN: { + int value = ((CstBoolean) cst).getIntBits(); + out.writeByte(type | (value << 5)); + break; + } + default: { + throw new RuntimeException("Shouldn't happen"); + } + } + } + + /** + * Gets the value type for the given constant. + * + * @param cst {@code non-null;} the constant + * @return the value type; one of the {@code VALUE_*} constants + * defined by this class + */ + private static int constantToValueType(Constant cst) { + /* + * TODO: Constant should probable have an associated enum, so this + * can be a switch(). + */ + if (cst instanceof CstByte) { + return VALUE_BYTE; + } else if (cst instanceof CstShort) { + return VALUE_SHORT; + } else if (cst instanceof CstChar) { + return VALUE_CHAR; + } else if (cst instanceof CstInteger) { + return VALUE_INT; + } else if (cst instanceof CstLong) { + return VALUE_LONG; + } else if (cst instanceof CstFloat) { + return VALUE_FLOAT; + } else if (cst instanceof CstDouble) { + return VALUE_DOUBLE; + } else if (cst instanceof CstString) { + return VALUE_STRING; + } else if (cst instanceof CstType) { + return VALUE_TYPE; + } else if (cst instanceof CstFieldRef) { + return VALUE_FIELD; + } else if (cst instanceof CstMethodRef) { + return VALUE_METHOD; + } else if (cst instanceof CstEnumRef) { + return VALUE_ENUM; + } else if (cst instanceof CstArray) { + return VALUE_ARRAY; + } else if (cst instanceof CstAnnotation) { + return VALUE_ANNOTATION; + } else if (cst instanceof CstKnownNull) { + return VALUE_NULL; + } else if (cst instanceof CstBoolean) { + return VALUE_BOOLEAN; + } else { + throw new RuntimeException("Shouldn't happen"); + } + } + + /** + * Writes out the encoded form of the given array, that is, as + * an {@code encoded_array} and not including a + * {@code value_type} prefix. If the output stream keeps + * (debugging) annotations and {@code topLevel} is + * {@code true}, then this method will write (debugging) + * annotations. + * + * @param array {@code non-null;} array instance to write + * @param topLevel {@code true} iff the given annotation is the + * top-level annotation or {@code false} if it is a sub-annotation + * of some other annotation + */ + public void writeArray(CstArray array, boolean topLevel) { + boolean annotates = topLevel && out.annotates(); + CstArray.List list = ((CstArray) array).getList(); + int size = list.size(); + + if (annotates) { + out.annotate(" size: " + Hex.u4(size)); + } + + out.writeUleb128(size); + + for (int i = 0; i < size; i++) { + Constant cst = list.get(i); + if (annotates) { + out.annotate(" [" + Integer.toHexString(i) + "] " + + constantToHuman(cst)); + } + writeConstant(cst); + } + + if (annotates) { + out.endAnnotation(); + } + } + + /** + * Writes out the encoded form of the given annotation, that is, + * as an {@code encoded_annotation} and not including a + * {@code value_type} prefix. If the output stream keeps + * (debugging) annotations and {@code topLevel} is + * {@code true}, then this method will write (debugging) + * annotations. + * + * @param annotation {@code non-null;} annotation instance to write + * @param topLevel {@code true} iff the given annotation is the + * top-level annotation or {@code false} if it is a sub-annotation + * of some other annotation + */ + public void writeAnnotation(Annotation annotation, boolean topLevel) { + boolean annotates = topLevel && out.annotates(); + StringIdsSection stringIds = file.getStringIds(); + TypeIdsSection typeIds = file.getTypeIds(); + + CstType type = annotation.getType(); + int typeIdx = typeIds.indexOf(type); + + if (annotates) { + out.annotate(" type_idx: " + Hex.u4(typeIdx) + " // " + + type.toHuman()); + } + + out.writeUleb128(typeIds.indexOf(annotation.getType())); + + Collection pairs = annotation.getNameValuePairs(); + int size = pairs.size(); + + if (annotates) { + out.annotate(" size: " + Hex.u4(size)); + } + + out.writeUleb128(size); + + int at = 0; + for (NameValuePair pair : pairs) { + CstString name = pair.getName(); + int nameIdx = stringIds.indexOf(name); + Constant value = pair.getValue(); + + if (annotates) { + out.annotate(0, " elements[" + at + "]:"); + at++; + out.annotate(" name_idx: " + Hex.u4(nameIdx) + " // " + + name.toHuman()); + } + + out.writeUleb128(nameIdx); + + if (annotates) { + out.annotate(" value: " + constantToHuman(value)); + } + + writeConstant(value); + } + + if (annotates) { + out.endAnnotation(); + } + } + + /** + * Gets the colloquial type name and human form of the type of the + * given constant, when used as an encoded value. + * + * @param cst {@code non-null;} the constant + * @return {@code non-null;} its type name and human form + */ + public static String constantToHuman(Constant cst) { + int type = constantToValueType(cst); + + if (type == VALUE_NULL) { + return "null"; + } + + StringBuilder sb = new StringBuilder(); + + sb.append(cst.typeName()); + sb.append(' '); + sb.append(cst.toHuman()); + + return sb.toString(); + } + + /** + * Helper for {@link #writeConstant}, which writes out the value + * for any signed integral type. + * + * @param type the type constant + * @param value {@code long} bits of the value + */ + private void writeSignedIntegralValue(int type, long value) { + /* + * Figure out how many bits are needed to represent the value, + * including a sign bit: The bit count is subtracted from 65 + * and not 64 to account for the sign bit. The xor operation + * has the effect of leaving non-negative values alone and + * unary complementing negative values (so that a leading zero + * count always returns a useful number for our present + * purpose). + */ + int requiredBits = + 65 - Long.numberOfLeadingZeros(value ^ (value >> 63)); + + // Round up the requiredBits to a number of bytes. + int requiredBytes = (requiredBits + 0x07) >> 3; + + /* + * Write the header byte, which includes the type and + * requiredBytes - 1. + */ + out.writeByte(type | ((requiredBytes - 1) << 5)); + + // Write the value, per se. + while (requiredBytes > 0) { + out.writeByte((byte) value); + value >>= 8; + requiredBytes--; + } + } + + /** + * Helper for {@link #writeConstant}, which writes out the value + * for any unsigned integral type. + * + * @param type the type constant + * @param value {@code long} bits of the value + */ + private void writeUnsignedIntegralValue(int type, long value) { + // Figure out how many bits are needed to represent the value. + int requiredBits = 64 - Long.numberOfLeadingZeros(value); + if (requiredBits == 0) { + requiredBits = 1; + } + + // Round up the requiredBits to a number of bytes. + int requiredBytes = (requiredBits + 0x07) >> 3; + + /* + * Write the header byte, which includes the type and + * requiredBytes - 1. + */ + out.writeByte(type | ((requiredBytes - 1) << 5)); + + // Write the value, per se. + while (requiredBytes > 0) { + out.writeByte((byte) value); + value >>= 8; + requiredBytes--; + } + } + + /** + * Helper for {@link #writeConstant}, which writes out a + * right-zero-extended value. + * + * @param type the type constant + * @param value {@code long} bits of the value + */ + private void writeRightZeroExtendedValue(int type, long value) { + // Figure out how many bits are needed to represent the value. + int requiredBits = 64 - Long.numberOfTrailingZeros(value); + if (requiredBits == 0) { + requiredBits = 1; + } + + // Round up the requiredBits to a number of bytes. + int requiredBytes = (requiredBits + 0x07) >> 3; + + // Scootch the first bits to be written down to the low-order bits. + value >>= 64 - (requiredBytes * 8); + + /* + * Write the header byte, which includes the type and + * requiredBytes - 1. + */ + out.writeByte(type | ((requiredBytes - 1) << 5)); + + // Write the value, per se. + while (requiredBytes > 0) { + out.writeByte((byte) value); + value >>= 8; + requiredBytes--; + } + } + + + /** + * Helper for {@code addContents()} methods, which adds + * contents for a particular {@link Annotation}, calling itself + * recursively should it encounter a nested annotation. + * + * @param file {@code non-null;} the file to add to + * @param annotation {@code non-null;} the annotation to add contents for + */ + public static void addContents(DexFile file, Annotation annotation) { + TypeIdsSection typeIds = file.getTypeIds(); + StringIdsSection stringIds = file.getStringIds(); + + typeIds.intern(annotation.getType()); + + for (NameValuePair pair : annotation.getNameValuePairs()) { + stringIds.intern(pair.getName()); + addContents(file, pair.getValue()); + } + } + + /** + * Helper for {@code addContents()} methods, which adds + * contents for a particular constant, calling itself recursively + * should it encounter a {@link CstArray} and calling {@link + * #addContents(DexFile,Annotation)} recursively should it + * encounter a {@link CstAnnotation}. + * + * @param file {@code non-null;} the file to add to + * @param cst {@code non-null;} the constant to add contents for + */ + public static void addContents(DexFile file, Constant cst) { + if (cst instanceof CstAnnotation) { + addContents(file, ((CstAnnotation) cst).getAnnotation()); + } else if (cst instanceof CstArray) { + CstArray.List list = ((CstArray) cst).getList(); + int size = list.size(); + for (int i = 0; i < size; i++) { + addContents(file, list.get(i)); + } + } else { + file.internIfAppropriate(cst); + } + } +} diff --git a/dx/src/com/android/jack/dx/io/Annotation.java b/dx/src/com/android/jack/dx/io/Annotation.java new file mode 100644 index 00000000..47987b1e --- /dev/null +++ b/dx/src/com/android/jack/dx/io/Annotation.java @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.io; + +import com.android.jack.dx.util.Unsigned; + +/** + * An annotation. + */ +public final class Annotation implements Comparable { + private final DexBuffer buffer; + private final byte visibility; + private final int typeIndex; + private final int[] names; + private final EncodedValue[] values; + + public Annotation(DexBuffer buffer, byte visibility, int typeIndex, int[] names, + EncodedValue[] values) { + this.buffer = buffer; + this.visibility = visibility; + this.typeIndex = typeIndex; + this.names = names; + this.values = values; + } + + public byte getVisibility() { + return visibility; + } + + public int getTypeIndex() { + return typeIndex; + } + + public int[] getNames() { + return names; + } + + public EncodedValue[] getValues() { + return values; + } + + public void writeTo(DexBuffer.Section out) { + out.writeByte(visibility); + out.writeUleb128(typeIndex); + out.writeUleb128(names.length); + for (int i = 0; i < names.length; i++) { + out.writeUleb128(names[i]); + values[i].writeTo(out); + } + } + + @Override public int compareTo(Annotation other) { + if (typeIndex != other.typeIndex) { + return Unsigned.compare(typeIndex, other.typeIndex); + } + int size = Math.min(names.length, other.names.length); + for (int i = 0; i < size; i++) { + if (names[i] != other.names[i]) { + return Unsigned.compare(names[i], other.names[i]); + } + int compare = values[i].compareTo(other.values[i]); + if (compare != 0) { + return compare; + } + } + return names.length - other.names.length; + } + + @Override public String toString() { + if (buffer == null) { + return visibility + " " + typeIndex; + } + + StringBuilder result = new StringBuilder(); + result.append(visibility); + result.append(" "); + result.append(buffer.typeNames().get(typeIndex)); + result.append("["); + for (int i = 0; i < names.length; i++) { + if (i > 0) { + result.append(", "); + } + result.append(buffer.strings().get(names[i])); + result.append("="); + result.append(values[i]); + } + result.append("]"); + return result.toString(); + } +} diff --git a/dx/src/com/android/jack/dx/io/ClassData.java b/dx/src/com/android/jack/dx/io/ClassData.java new file mode 100644 index 00000000..c51a6510 --- /dev/null +++ b/dx/src/com/android/jack/dx/io/ClassData.java @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.io; + +public final class ClassData { + private final Field[] staticFields; + private final Field[] instanceFields; + private final Method[] directMethods; + private final Method[] virtualMethods; + + public ClassData(Field[] staticFields, Field[] instanceFields, + Method[] directMethods, Method[] virtualMethods) { + this.staticFields = staticFields; + this.instanceFields = instanceFields; + this.directMethods = directMethods; + this.virtualMethods = virtualMethods; + } + + public Field[] getStaticFields() { + return staticFields; + } + + public Field[] getInstanceFields() { + return instanceFields; + } + + public Method[] getDirectMethods() { + return directMethods; + } + + public Method[] getVirtualMethods() { + return virtualMethods; + } + + public Field[] allFields() { + Field[] result = new Field[staticFields.length + instanceFields.length]; + System.arraycopy(staticFields, 0, result, 0, staticFields.length); + System.arraycopy(instanceFields, 0, result, staticFields.length, instanceFields.length); + return result; + } + + public Method[] allMethods() { + Method[] result = new Method[directMethods.length + virtualMethods.length]; + System.arraycopy(directMethods, 0, result, 0, directMethods.length); + System.arraycopy(virtualMethods, 0, result, directMethods.length, virtualMethods.length); + return result; + } + + public static class Field { + private final int fieldIndex; + private final int accessFlags; + + public Field(int fieldIndex, int accessFlags) { + this.fieldIndex = fieldIndex; + this.accessFlags = accessFlags; + } + + public int getFieldIndex() { + return fieldIndex; + } + + public int getAccessFlags() { + return accessFlags; + } + } + + public static class Method { + private final int methodIndex; + private final int accessFlags; + private final int codeOffset; + + public Method(int methodIndex, int accessFlags, int codeOffset) { + this.methodIndex = methodIndex; + this.accessFlags = accessFlags; + this.codeOffset = codeOffset; + } + + public int getMethodIndex() { + return methodIndex; + } + + public int getAccessFlags() { + return accessFlags; + } + + public int getCodeOffset() { + return codeOffset; + } + } +} diff --git a/dx/src/com/android/jack/dx/io/ClassDef.java b/dx/src/com/android/jack/dx/io/ClassDef.java new file mode 100644 index 00000000..60f15e35 --- /dev/null +++ b/dx/src/com/android/jack/dx/io/ClassDef.java @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.io; + +/** + * A type definition. + */ +public final class ClassDef { + public static final int NO_INDEX = -1; + private final DexBuffer buffer; + private final int offset; + private final int typeIndex; + private final int accessFlags; + private final int supertypeIndex; + private final int interfacesOffset; + private final int sourceFileIndex; + private final int annotationsOffset; + private final int classDataOffset; + private final int staticValuesOffset; + + public ClassDef(DexBuffer buffer, int offset, int typeIndex, int accessFlags, + int supertypeIndex, int interfacesOffset, int sourceFileIndex, + int annotationsOffset, int classDataOffset, int staticValuesOffset) { + this.buffer = buffer; + this.offset = offset; + this.typeIndex = typeIndex; + this.accessFlags = accessFlags; + this.supertypeIndex = supertypeIndex; + this.interfacesOffset = interfacesOffset; + this.sourceFileIndex = sourceFileIndex; + this.annotationsOffset = annotationsOffset; + this.classDataOffset = classDataOffset; + this.staticValuesOffset = staticValuesOffset; + } + + public int getOffset() { + return offset; + } + + public int getTypeIndex() { + return typeIndex; + } + + public int getSupertypeIndex() { + return supertypeIndex; + } + + public int getInterfacesOffset() { + return interfacesOffset; + } + + public short[] getInterfaces() { + return buffer.readTypeList(interfacesOffset).getTypes(); + } + + public int getAccessFlags() { + return accessFlags; + } + + public int getSourceFileIndex() { + return sourceFileIndex; + } + + public int getAnnotationsOffset() { + return annotationsOffset; + } + + public int getClassDataOffset() { + return classDataOffset; + } + + public int getStaticValuesOffset() { + return staticValuesOffset; + } + + @Override public String toString() { + if (buffer == null) { + return typeIndex + " " + supertypeIndex; + } + + StringBuilder result = new StringBuilder(); + result.append(buffer.typeNames().get(typeIndex)); + if (supertypeIndex != NO_INDEX) { + result.append(" extends ").append(buffer.typeNames().get(supertypeIndex)); + } + return result.toString(); + } +} diff --git a/dx/src/com/android/jack/dx/io/Code.java b/dx/src/com/android/jack/dx/io/Code.java new file mode 100644 index 00000000..865369b6 --- /dev/null +++ b/dx/src/com/android/jack/dx/io/Code.java @@ -0,0 +1,124 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.io; + +public final class Code { + private final int registersSize; + private final int insSize; + private final int outsSize; + private final int debugInfoOffset; + private final short[] instructions; + private final Try[] tries; + private final CatchHandler[] catchHandlers; + + public Code(int registersSize, int insSize, int outsSize, int debugInfoOffset, + short[] instructions, Try[] tries, CatchHandler[] catchHandlers) { + this.registersSize = registersSize; + this.insSize = insSize; + this.outsSize = outsSize; + this.debugInfoOffset = debugInfoOffset; + this.instructions = instructions; + this.tries = tries; + this.catchHandlers = catchHandlers; + } + + public int getRegistersSize() { + return registersSize; + } + + public int getInsSize() { + return insSize; + } + + public int getOutsSize() { + return outsSize; + } + + public int getDebugInfoOffset() { + return debugInfoOffset; + } + + public short[] getInstructions() { + return instructions; + } + + public Try[] getTries() { + return tries; + } + + public CatchHandler[] getCatchHandlers() { + return catchHandlers; + } + + public static class Try { + final int startAddress; + final int instructionCount; + final int catchHandlerIndex; + + Try(int startAddress, int instructionCount, int catchHandlerIndex) { + this.startAddress = startAddress; + this.instructionCount = instructionCount; + this.catchHandlerIndex = catchHandlerIndex; + } + + public int getStartAddress() { + return startAddress; + } + + public int getInstructionCount() { + return instructionCount; + } + + /** + * Returns this try's catch handler index. Note that + * this is distinct from the its catch handler offset. + */ + public int getCatchHandlerIndex() { + return catchHandlerIndex; + } + } + + public static class CatchHandler { + final int[] typeIndexes; + final int[] addresses; + final int catchAllAddress; + final int offset; + + public CatchHandler(int[] typeIndexes, int[] addresses, int catchAllAddress, int offset) { + this.typeIndexes = typeIndexes; + this.addresses = addresses; + this.catchAllAddress = catchAllAddress; + this.offset = offset; + } + + public int[] getTypeIndexes() { + return typeIndexes; + } + + public int[] getAddresses() { + return addresses; + } + + public int getCatchAllAddress() { + return catchAllAddress; + } + + public int getOffset() { + return offset; + } + } +} diff --git a/dx/src/com/android/jack/dx/io/CodeReader.java b/dx/src/com/android/jack/dx/io/CodeReader.java new file mode 100644 index 00000000..0b791b1c --- /dev/null +++ b/dx/src/com/android/jack/dx/io/CodeReader.java @@ -0,0 +1,121 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.io; + +import com.android.jack.dx.io.instructions.DecodedInstruction; +import com.android.jack.dx.util.DexException; + +/** + * Walks through a block of code and calls visitor call backs. + */ +public final class CodeReader { + private Visitor fallbackVisitor = null; + private Visitor stringVisitor = null; + private Visitor typeVisitor = null; + private Visitor fieldVisitor = null; + private Visitor methodVisitor = null; + + /** + * Sets {@code visitor} as the visitor for all instructions. + */ + public void setAllVisitors(Visitor visitor) { + fallbackVisitor = visitor; + stringVisitor = visitor; + typeVisitor = visitor; + fieldVisitor = visitor; + methodVisitor = visitor; + } + + /** + * Sets {@code visitor} as the visitor for all instructions not + * otherwise handled. + */ + public void setFallbackVisitor(Visitor visitor) { + fallbackVisitor = visitor; + } + + /** + * Sets {@code visitor} as the visitor for all string instructions. + */ + public void setStringVisitor(Visitor visitor) { + stringVisitor = visitor; + } + + /** + * Sets {@code visitor} as the visitor for all type instructions. + */ + public void setTypeVisitor(Visitor visitor) { + typeVisitor = visitor; + } + + /** + * Sets {@code visitor} as the visitor for all field instructions. + */ + public void setFieldVisitor(Visitor visitor) { + fieldVisitor = visitor; + } + + /** + * Sets {@code visitor} as the visitor for all method instructions. + */ + public void setMethodVisitor(Visitor visitor) { + methodVisitor = visitor; + } + + public void visitAll(DecodedInstruction[] decodedInstructions) + throws DexException { + int size = decodedInstructions.length; + + for (int i = 0; i < size; i++) { + DecodedInstruction one = decodedInstructions[i]; + if (one == null) { + continue; + } + + callVisit(decodedInstructions, one); + } + } + + public void visitAll(short[] encodedInstructions) throws DexException { + DecodedInstruction[] decodedInstructions = + DecodedInstruction.decodeAll(encodedInstructions); + visitAll(decodedInstructions); + } + + private void callVisit(DecodedInstruction[] all, DecodedInstruction one) { + Visitor visitor = null; + + switch (OpcodeInfo.getIndexType(one.getOpcode())) { + case STRING_REF: visitor = stringVisitor; break; + case TYPE_REF: visitor = typeVisitor; break; + case FIELD_REF: visitor = fieldVisitor; break; + case METHOD_REF: visitor = methodVisitor; break; + } + + if (visitor == null) { + visitor = fallbackVisitor; + } + + if (visitor != null) { + visitor.visit(all, one); + } + } + + public interface Visitor { + void visit(DecodedInstruction[] all, DecodedInstruction one); + } +} diff --git a/dx/src/com/android/jack/dx/io/DexBuffer.java b/dx/src/com/android/jack/dx/io/DexBuffer.java new file mode 100644 index 00000000..2e8299c4 --- /dev/null +++ b/dx/src/com/android/jack/dx/io/DexBuffer.java @@ -0,0 +1,710 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.io; + +import com.android.jack.dx.dex.DexFormat; +import com.android.jack.dx.dex.SizeOf; +import com.android.jack.dx.dex.TableOfContents; +import com.android.jack.dx.io.Code.CatchHandler; +import com.android.jack.dx.io.Code.Try; +import com.android.jack.dx.merge.TypeList; +import com.android.jack.dx.util.ByteInput; +import com.android.jack.dx.util.ByteOutput; +import com.android.jack.dx.util.DexException; +import com.android.jack.dx.util.FileUtils; +import com.android.jack.dx.util.Leb128Utils; +import com.android.jack.dx.util.Mutf8; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.UTFDataFormatException; +import java.util.AbstractList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.NoSuchElementException; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; + +/** + * The bytes of a dex file in memory for reading and writing. All int offsets + * are unsigned. + */ +public final class DexBuffer { + private byte[] data; + private final TableOfContents tableOfContents = new TableOfContents(); + private int length = 0; + + private final List strings = new AbstractList() { + @Override public String get(int index) { + checkBounds(index, tableOfContents.stringIds.size); + return open(tableOfContents.stringIds.off + (index * SizeOf.STRING_ID_ITEM)) + .readString(); + } + @Override public int size() { + return tableOfContents.stringIds.size; + } + }; + + private final List typeIds = new AbstractList() { + @Override public Integer get(int index) { + checkBounds(index, tableOfContents.typeIds.size); + return open(tableOfContents.typeIds.off + (index * SizeOf.TYPE_ID_ITEM)).readInt(); + } + @Override public int size() { + return tableOfContents.typeIds.size; + } + }; + + private final List typeNames = new AbstractList() { + @Override public String get(int index) { + checkBounds(index, tableOfContents.typeIds.size); + return strings.get(typeIds.get(index)); + } + @Override public int size() { + return tableOfContents.typeIds.size; + } + }; + + private final List protoIds = new AbstractList() { + @Override public ProtoId get(int index) { + checkBounds(index, tableOfContents.protoIds.size); + return open(tableOfContents.protoIds.off + (SizeOf.PROTO_ID_ITEM * index)) + .readProtoId(); + } + @Override public int size() { + return tableOfContents.protoIds.size; + } + }; + + private final List fieldIds = new AbstractList() { + @Override public FieldId get(int index) { + checkBounds(index, tableOfContents.fieldIds.size); + return open(tableOfContents.fieldIds.off + (SizeOf.MEMBER_ID_ITEM * index)) + .readFieldId(); + } + @Override public int size() { + return tableOfContents.fieldIds.size; + } + }; + + private final List methodIds = new AbstractList() { + @Override public MethodId get(int index) { + checkBounds(index, tableOfContents.methodIds.size); + return open(tableOfContents.methodIds.off + (SizeOf.MEMBER_ID_ITEM * index)) + .readMethodId(); + } + @Override public int size() { + return tableOfContents.methodIds.size; + } + }; + + /** + * Creates a new dex buffer defining no classes. + */ + public DexBuffer() { + this.data = new byte[0]; + } + + /** + * Creates a new dex buffer that reads from {@code data}. It is an error to + * modify {@code data} after using it to create a dex buffer. + */ + public DexBuffer(byte[] data) throws IOException { + this.data = data; + this.length = data.length; + this.tableOfContents.readFrom(this); + } + + /** + * Creates a new dex buffer of the dex in {@code in}, and closes {@code in}. + */ + public DexBuffer(InputStream in) throws IOException { + loadFrom(in); + } + + /** + * Creates a new dex buffer from the dex file {@code file}. + */ + public DexBuffer(File file) throws IOException { + if (FileUtils.hasArchiveSuffix(file.getName())) { + ZipFile zipFile = new ZipFile(file); + ZipEntry entry = zipFile.getEntry(DexFormat.DEX_IN_JAR_NAME); + if (entry != null) { + loadFrom(zipFile.getInputStream(entry)); + zipFile.close(); + } else { + throw new DexException("Expected " + DexFormat.DEX_IN_JAR_NAME + " in " + file); + } + } else if (file.getName().endsWith(".dex")) { + loadFrom(new FileInputStream(file)); + } else { + throw new DexException("unknown output extension: " + file); + } + } + + private void loadFrom(InputStream in) throws IOException { + ByteArrayOutputStream bytesOut = new ByteArrayOutputStream(); + byte[] buffer = new byte[8192]; + + int count; + while ((count = in.read(buffer)) != -1) { + bytesOut.write(buffer, 0, count); + } + in.close(); + + this.data = bytesOut.toByteArray(); + this.length = data.length; + this.tableOfContents.readFrom(this); + } + + private static void checkBounds(int index, int length) { + if (index < 0 || index >= length) { + throw new IndexOutOfBoundsException("index:" + index + ", length=" + length); + } + } + + public void writeTo(OutputStream out) throws IOException { + out.write(data); + } + + public void writeTo(File dexOut) throws IOException { + OutputStream out = new FileOutputStream(dexOut); + writeTo(out); + out.close(); + } + + public TableOfContents getTableOfContents() { + return tableOfContents; + } + + public Section open(int position) { + if (position < 0 || position > length) { + throw new IllegalArgumentException("position=" + position + " length=" + length); + } + return new Section(position); + } + + public Section appendSection(int maxByteCount, String name) { + int limit = fourByteAlign(length + maxByteCount); + Section result = new Section(name, length, limit); + length = limit; + return result; + } + + public void noMoreSections() { + data = new byte[length]; + } + + public int getLength() { + return length; + } + + public static int fourByteAlign(int position) { + return (position + 3) & ~3; + } + + public byte[] getBytes() { + return data; + } + + public List strings() { + return strings; + } + + public List typeIds() { + return typeIds; + } + + public List typeNames() { + return typeNames; + } + + public List protoIds() { + return protoIds; + } + + public List fieldIds() { + return fieldIds; + } + + public List methodIds() { + return methodIds; + } + + public Iterable classDefs() { + return new Iterable() { + public Iterator iterator() { + if (!tableOfContents.classDefs.exists()) { + return Collections.emptySet().iterator(); + } + return new Iterator() { + private DexBuffer.Section in = open(tableOfContents.classDefs.off); + private int count = 0; + + public boolean hasNext() { + return count < tableOfContents.classDefs.size; + } + public ClassDef next() { + if (!hasNext()) { + throw new NoSuchElementException(); + } + count++; + return in.readClassDef(); + } + public void remove() { + throw new UnsupportedOperationException(); + } + }; + } + }; + } + + public TypeList readTypeList(int offset) { + if (offset == 0) { + return TypeList.EMPTY; + } + return open(offset).readTypeList(); + } + + public ClassData readClassData(ClassDef classDef) { + int offset = classDef.getClassDataOffset(); + if (offset == 0) { + throw new IllegalArgumentException("offset == 0"); + } + return open(offset).readClassData(); + } + + public Code readCode(ClassData.Method method) { + int offset = method.getCodeOffset(); + if (offset == 0) { + throw new IllegalArgumentException("offset == 0"); + } + return open(offset).readCode(); + } + + public final class Section implements ByteInput, ByteOutput { + private final String name; + private int position; + private final int limit; + private final int initialPosition; + + private Section(String name, int position, int limit) { + this.name = name; + this.position = this.initialPosition = position; + this.limit = limit; + } + + private Section(int position) { + this("section", position, data.length); + } + + public int getPosition() { + return position; + } + + public int readInt() { + int result = (data[position] & 0xff) + | (data[position + 1] & 0xff) << 8 + | (data[position + 2] & 0xff) << 16 + | (data[position + 3] & 0xff) << 24; + position += 4; + return result; + } + + public short readShort() { + int result = (data[position] & 0xff) + | (data[position + 1] & 0xff) << 8; + position += 2; + return (short) result; + } + + public int readUnsignedShort() { + return readShort() & 0xffff; + } + + public byte readByte() { + return (byte) (data[position++] & 0xff); + } + + public byte[] readByteArray(int length) { + byte[] result = Arrays.copyOfRange(data, position, position + length); + position += length; + return result; + } + + public short[] readShortArray(int length) { + short[] result = new short[length]; + for (int i = 0; i < length; i++) { + result[i] = readShort(); + } + return result; + } + + public int readUleb128() { + return Leb128Utils.readUnsignedLeb128(this); + } + + public int readUleb128p1() { + return Leb128Utils.readUnsignedLeb128(this) - 1; + } + + public int readSleb128() { + return Leb128Utils.readSignedLeb128(this); + } + + public TypeList readTypeList() { + assertFourByteAligned(); + int size = readInt(); + short[] types = new short[size]; + for (int i = 0; i < size; i++) { + types[i] = readShort(); + } + position = DexBuffer.fourByteAlign(position); + return new TypeList(DexBuffer.this, types); + } + + public String readString() { + int offset = readInt(); + int savedPosition = position; + position = offset; + try { + int expectedLength = readUleb128(); + String result = Mutf8.decode(this, new char[expectedLength]); + if (result.length() != expectedLength) { + throw new DexException("Declared length " + expectedLength + + " doesn't match decoded length of " + result.length()); + } + return result; + } catch (UTFDataFormatException e) { + throw new DexException(e); + } finally { + position = savedPosition; + } + } + + public FieldId readFieldId() { + int declaringClassIndex = readUnsignedShort(); + int typeIndex = readUnsignedShort(); + int nameIndex = readInt(); + return new FieldId(DexBuffer.this, declaringClassIndex, typeIndex, nameIndex); + } + + public MethodId readMethodId() { + int declaringClassIndex = readUnsignedShort(); + int protoIndex = readUnsignedShort(); + int nameIndex = readInt(); + return new MethodId(DexBuffer.this, declaringClassIndex, protoIndex, nameIndex); + } + + public ProtoId readProtoId() { + int shortyIndex = readInt(); + int returnTypeIndex = readInt(); + int parametersOffset = readInt(); + return new ProtoId(DexBuffer.this, shortyIndex, returnTypeIndex, parametersOffset); + } + + public ClassDef readClassDef() { + int offset = getPosition(); + int type = readInt(); + int accessFlags = readInt(); + int supertype = readInt(); + int interfacesOffset = readInt(); + int sourceFileIndex = readInt(); + int annotationsOffset = readInt(); + int classDataOffset = readInt(); + int staticValuesOffset = readInt(); + return new ClassDef(DexBuffer.this, offset, type, accessFlags, supertype, + interfacesOffset, sourceFileIndex, annotationsOffset, classDataOffset, + staticValuesOffset); + } + + private Code readCode() { + int registersSize = readUnsignedShort(); + int insSize = readUnsignedShort(); + int outsSize = readUnsignedShort(); + int triesSize = readUnsignedShort(); + int debugInfoOffset = readInt(); + int instructionsSize = readInt(); + short[] instructions = readShortArray(instructionsSize); + Try[] tries; + CatchHandler[] catchHandlers; + if (triesSize > 0) { + if (instructions.length % 2 == 1) { + readShort(); // padding + } + + /* + * We can't read the tries until we've read the catch handlers. + * Unfortunately they're in the opposite order in the dex file + * so we need to read them out-of-order. + */ + Section triesSection = open(position); + skip(triesSize * SizeOf.TRY_ITEM); + catchHandlers = readCatchHandlers(); + tries = triesSection.readTries(triesSize, catchHandlers); + } else { + tries = new Try[0]; + catchHandlers = new CatchHandler[0]; + } + return new Code(registersSize, insSize, outsSize, debugInfoOffset, instructions, + tries, catchHandlers); + } + + private CatchHandler[] readCatchHandlers() { + int baseOffset = position; + int catchHandlersSize = readUleb128(); + CatchHandler[] result = new CatchHandler[catchHandlersSize]; + for (int i = 0; i < catchHandlersSize; i++) { + int offset = position - baseOffset; + result[i] = readCatchHandler(offset); + } + return result; + } + + private Try[] readTries(int triesSize, CatchHandler[] catchHandlers) { + Try[] result = new Try[triesSize]; + for (int i = 0; i < triesSize; i++) { + int startAddress = readInt(); + int instructionCount = readUnsignedShort(); + int handlerOffset = readUnsignedShort(); + int catchHandlerIndex = findCatchHandlerIndex(catchHandlers, handlerOffset); + result[i] = new Try(startAddress, instructionCount, catchHandlerIndex); + } + return result; + } + + private int findCatchHandlerIndex(CatchHandler[] catchHandlers, int offset) { + for (int i = 0; i < catchHandlers.length; i++) { + CatchHandler catchHandler = catchHandlers[i]; + if (catchHandler.getOffset() == offset) { + return i; + } + } + throw new IllegalArgumentException(); + } + + private CatchHandler readCatchHandler(int offset) { + int size = readSleb128(); + int handlersCount = Math.abs(size); + int[] typeIndexes = new int[handlersCount]; + int[] addresses = new int[handlersCount]; + for (int i = 0; i < handlersCount; i++) { + typeIndexes[i] = readUleb128(); + addresses[i] = readUleb128(); + } + int catchAllAddress = size <= 0 ? readUleb128() : -1; + return new CatchHandler(typeIndexes, addresses, catchAllAddress, offset); + } + + private ClassData readClassData() { + int staticFieldsSize = readUleb128(); + int instanceFieldsSize = readUleb128(); + int directMethodsSize = readUleb128(); + int virtualMethodsSize = readUleb128(); + ClassData.Field[] staticFields = readFields(staticFieldsSize); + ClassData.Field[] instanceFields = readFields(instanceFieldsSize); + ClassData.Method[] directMethods = readMethods(directMethodsSize); + ClassData.Method[] virtualMethods = readMethods(virtualMethodsSize); + return new ClassData(staticFields, instanceFields, directMethods, virtualMethods); + } + + private ClassData.Field[] readFields(int count) { + ClassData.Field[] result = new ClassData.Field[count]; + int fieldIndex = 0; + for (int i = 0; i < count; i++) { + fieldIndex += readUleb128(); // field index diff + int accessFlags = readUleb128(); + result[i] = new ClassData.Field(fieldIndex, accessFlags); + } + return result; + } + + private ClassData.Method[] readMethods(int count) { + ClassData.Method[] result = new ClassData.Method[count]; + int methodIndex = 0; + for (int i = 0; i < count; i++) { + methodIndex += readUleb128(); // method index diff + int accessFlags = readUleb128(); + int codeOff = readUleb128(); + result[i] = new ClassData.Method(methodIndex, accessFlags, codeOff); + } + return result; + } + + public Annotation readAnnotation() { + byte visibility = readByte(); + int typeIndex = readUleb128(); + int size = readUleb128(); + int[] names = new int[size]; + EncodedValue[] values = new EncodedValue[size]; + for (int i = 0; i < size; i++) { + names[i] = readUleb128(); + values[i] = readEncodedValue(); + } + return new Annotation(DexBuffer.this, visibility, typeIndex, names, values); + } + + public EncodedValue readEncodedValue() { + int start = position; + new EncodedValueReader(this).readValue(); + int end = position; + return new EncodedValue(Arrays.copyOfRange(data, start, end)); + } + + public EncodedValue readEncodedArray() { + int start = position; + new EncodedValueReader(this).readArray(); + int end = position; + return new EncodedValue(Arrays.copyOfRange(data, start, end)); + } + + private void ensureCapacity(int size) { + if (position + size > limit) { + throw new DexException("Section limit " + limit + " exceeded by " + name); + } + } + + public void skip(int count) { + if (count < 0) { + throw new IllegalArgumentException(); + } + ensureCapacity(count); + position += count; + } + + /** + * Writes 0x00 until the position is aligned to a multiple of 4. + */ + public void alignToFourBytes() { + int unalignedCount = position; + position = DexBuffer.fourByteAlign(position); + for (int i = unalignedCount; i < position; i++) { + data[i] = 0; + } + } + + public void assertFourByteAligned() { + if ((position & 3) != 0) { + throw new IllegalStateException("Not four byte aligned!"); + } + } + + public void write(byte[] bytes) { + ensureCapacity(bytes.length); + System.arraycopy(bytes, 0, data, position, bytes.length); + position += bytes.length; + } + + public void writeByte(int b) { + ensureCapacity(1); + data[position++] = (byte) b; + } + + public void writeShort(short i) { + ensureCapacity(2); + data[position ] = (byte) i; + data[position + 1] = (byte) (i >>> 8); + position += 2; + } + + public void writeUnsignedShort(int i) { + short s = (short) i; + if (i != (s & 0xffff)) { + throw new IllegalArgumentException("Expected an unsigned short: " + i); + } + writeShort(s); + } + + public void write(short[] shorts) { + for (short s : shorts) { + writeShort(s); + } + } + + public void writeInt(int i) { + ensureCapacity(4); + data[position ] = (byte) i; + data[position + 1] = (byte) (i >>> 8); + data[position + 2] = (byte) (i >>> 16); + data[position + 3] = (byte) (i >>> 24); + position += 4; + } + + public void writeUleb128(int i) { + try { + Leb128Utils.writeUnsignedLeb128(this, i); + ensureCapacity(0); + } catch (ArrayIndexOutOfBoundsException e) { + throw new DexException("Section limit " + limit + " exceeded by " + name); + } + } + + public void writeUleb128p1(int i) { + writeUleb128(i + 1); + } + + public void writeSleb128(int i) { + try { + Leb128Utils.writeSignedLeb128(this, i); + ensureCapacity(0); + } catch (ArrayIndexOutOfBoundsException e) { + throw new DexException("Section limit " + limit + " exceeded by " + name); + } + } + + public void writeStringData(String value) { + try { + int length = value.length(); + writeUleb128(length); + write(Mutf8.encode(value)); + writeByte(0); + } catch (UTFDataFormatException e) { + throw new AssertionError(); + } + } + + public void writeTypeList(TypeList typeList) { + short[] types = typeList.getTypes(); + writeInt(types.length); + for (short type : types) { + writeShort(type); + } + alignToFourBytes(); + } + + /** + * Returns the number of bytes remaining in this section. + */ + public int remaining() { + return limit - position; + } + + /** + * Returns the number of bytes used by this section. + */ + public int used () { + return position - initialPosition; + } + } +} diff --git a/dx/src/com/android/jack/dx/io/DexHasher.java b/dx/src/com/android/jack/dx/io/DexHasher.java new file mode 100644 index 00000000..301dd377 --- /dev/null +++ b/dx/src/com/android/jack/dx/io/DexHasher.java @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.io; + +import java.io.IOException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.zip.Adler32; + +/** + * Generates and stores the checksum and signature of a dex file. + */ +public final class DexHasher { + private static final int CHECKSUM_OFFSET = 8; + private static final int CHECKSUM_SIZE = 4; + private static final int SIGNATURE_OFFSET = CHECKSUM_OFFSET + CHECKSUM_SIZE; + private static final int SIGNATURE_SIZE = 20; + + /** + * Returns the signature of all but the first 32 bytes of {@code dex}. The + * first 32 bytes of dex files are not specified to be included in the + * signature. + */ + public byte[] computeSignature(DexBuffer dex) throws IOException { + MessageDigest digest; + try { + digest = MessageDigest.getInstance("SHA-1"); + } catch (NoSuchAlgorithmException e) { + throw new AssertionError(); + } + int offset = SIGNATURE_OFFSET + SIGNATURE_SIZE; + + byte[] bytes = dex.getBytes(); + digest.update(bytes, offset, bytes.length - offset); + return digest.digest(); + } + + /** + * Returns the checksum of all but the first 12 bytes of {@code dex}. + */ + public int computeChecksum(DexBuffer dex) throws IOException { + Adler32 adler32 = new Adler32(); + int offset = CHECKSUM_OFFSET + CHECKSUM_SIZE; + + byte[] bytes = dex.getBytes(); + adler32.update(bytes, offset, bytes.length - offset); + return (int) adler32.getValue(); + } + + /** + * Generates the signature and checksum of the dex file {@code out} and + * writes them to the file. + */ + public void writeHashes(DexBuffer dex) throws IOException { + byte[] signature = computeSignature(dex); + dex.open(SIGNATURE_OFFSET).write(signature); + + int checksum = computeChecksum(dex); + dex.open(CHECKSUM_OFFSET).writeInt(checksum); + } +} diff --git a/dx/src/com/android/jack/dx/io/DexIndexPrinter.java b/dx/src/com/android/jack/dx/io/DexIndexPrinter.java new file mode 100644 index 00000000..952649cd --- /dev/null +++ b/dx/src/com/android/jack/dx/io/DexIndexPrinter.java @@ -0,0 +1,125 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.io; + +import com.android.jack.dx.dex.TableOfContents; + +import java.io.File; +import java.io.IOException; + +/** + * Executable that prints all indices of a dex file. + */ +public final class DexIndexPrinter { + private final DexBuffer dexBuffer; + private final TableOfContents tableOfContents; + + public DexIndexPrinter(File file) throws IOException { + this.dexBuffer = new DexBuffer(file); + this.tableOfContents = dexBuffer.getTableOfContents(); + } + + private void printMap() { + for (TableOfContents.Section section : tableOfContents.sections) { + if (section.off != -1) { + System.out.println("section " + Integer.toHexString(section.type) + + " off=" + Integer.toHexString(section.off) + + " size=" + Integer.toHexString(section.size) + + " byteCount=" + Integer.toHexString(section.byteCount)); + } + } + } + + private void printStrings() throws IOException { + int index = 0; + for (String string : dexBuffer.strings()) { + System.out.println("string " + index + ": " + string); + index++; + } + } + + private void printTypeIds() throws IOException { + int index = 0; + for (Integer type : dexBuffer.typeIds()) { + System.out.println("type " + index + ": " + dexBuffer.strings().get(type)); + index++; + } + } + + private void printProtoIds() throws IOException { + int index = 0; + for (ProtoId protoId : dexBuffer.protoIds()) { + System.out.println("proto " + index + ": " + protoId); + index++; + } + } + + private void printFieldIds() throws IOException { + int index = 0; + for (FieldId fieldId : dexBuffer.fieldIds()) { + System.out.println("field " + index + ": " + fieldId); + index++; + } + } + + private void printMethodIds() throws IOException { + int index = 0; + for (MethodId methodId : dexBuffer.methodIds()) { + System.out.println("methodId " + index + ": " + methodId); + index++; + } + } + + private void printTypeLists() throws IOException { + if (tableOfContents.typeLists.off == -1) { + System.out.println("No type lists"); + return; + } + DexBuffer.Section in = dexBuffer.open(tableOfContents.typeLists.off); + for (int i = 0; i < tableOfContents.typeLists.size; i++) { + int size = in.readInt(); + System.out.print("Type list i=" + i + ", size=" + size + ", elements="); + for (int t = 0; t < size; t++) { + System.out.print(" " + dexBuffer.typeNames().get((int) in.readShort())); + } + if (size % 2 == 1) { + in.readShort(); // retain alignment + } + System.out.println(); + } + } + + private void printClassDefs() { + int index = 0; + for (ClassDef classDef : dexBuffer.classDefs()) { + System.out.println("class def " + index + ": " + classDef); + index++; + } + } + + public static void main(String[] args) throws IOException { + DexIndexPrinter indexPrinter = new DexIndexPrinter(new File(args[0])); + indexPrinter.printMap(); + indexPrinter.printStrings(); + indexPrinter.printTypeIds(); + indexPrinter.printProtoIds(); + indexPrinter.printFieldIds(); + indexPrinter.printMethodIds(); + indexPrinter.printTypeLists(); + indexPrinter.printClassDefs(); + } +} diff --git a/dx/src/com/android/jack/dx/io/EncodedValue.java b/dx/src/com/android/jack/dx/io/EncodedValue.java new file mode 100644 index 00000000..861dfcf4 --- /dev/null +++ b/dx/src/com/android/jack/dx/io/EncodedValue.java @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.io; + +import com.android.jack.dx.util.ByteArrayByteInput; +import com.android.jack.dx.util.ByteInput; + +/** + * An encoded value or array. + */ +public final class EncodedValue implements Comparable { + private final byte[] data; + + public EncodedValue(byte[] data) { + this.data = data; + } + + public ByteInput asByteInput() { + return new ByteArrayByteInput(data); + } + + public byte[] getBytes() { + return data; + } + + public void writeTo(DexBuffer.Section out) { + out.write(data); + } + + @Override public int compareTo(EncodedValue other) { + int size = Math.min(data.length, other.data.length); + for (int i = 0; i < size; i++) { + if (data[i] != other.data[i]) { + return (data[i] & 0xff) - (other.data[i] & 0xff); + } + } + return data.length - other.data.length; + } + + @Override public String toString() { + return Integer.toHexString(data[0] & 0xff) + "...(" + data.length + ")"; + } +} diff --git a/dx/src/com/android/jack/dx/io/EncodedValueReader.java b/dx/src/com/android/jack/dx/io/EncodedValueReader.java new file mode 100644 index 00000000..694d7365 --- /dev/null +++ b/dx/src/com/android/jack/dx/io/EncodedValueReader.java @@ -0,0 +1,146 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.io; + +import com.android.jack.dx.util.ByteInput; +import com.android.jack.dx.util.Leb128Utils; + +/** + * SAX-style reader for encoded values. + * TODO: convert this to a pull-style reader + */ +public class EncodedValueReader { + public static final int ENCODED_BYTE = 0x00; + public static final int ENCODED_SHORT = 0x02; + public static final int ENCODED_CHAR = 0x03; + public static final int ENCODED_INT = 0x04; + public static final int ENCODED_LONG = 0x06; + public static final int ENCODED_FLOAT = 0x10; + public static final int ENCODED_DOUBLE = 0x11; + public static final int ENCODED_STRING = 0x17; + public static final int ENCODED_TYPE = 0x18; + public static final int ENCODED_FIELD = 0x19; + public static final int ENCODED_ENUM = 0x1b; + public static final int ENCODED_METHOD = 0x1a; + public static final int ENCODED_ARRAY = 0x1c; + public static final int ENCODED_ANNOTATION = 0x1d; + public static final int ENCODED_NULL = 0x1e; + public static final int ENCODED_BOOLEAN = 0x1f; + + protected final ByteInput in; + + public EncodedValueReader(ByteInput in) { + this.in = in; + } + + public EncodedValueReader(EncodedValue in) { + this(in.asByteInput()); + } + + public final void readArray() { + int size = Leb128Utils.readUnsignedLeb128(in); + visitArray(size); + + for (int i = 0; i < size; i++) { + readValue(); + } + } + + public final void readAnnotation() { + int typeIndex = Leb128Utils.readUnsignedLeb128(in); + int size = Leb128Utils.readUnsignedLeb128(in); + visitAnnotation(typeIndex, size); + + for (int i = 0; i < size; i++) { + visitAnnotationName(Leb128Utils.readUnsignedLeb128(in)); + readValue(); + } + } + + public final void readValue() { + int argAndType = in.readByte() & 0xff; + int type = argAndType & 0x1f; + int arg = (argAndType & 0xe0) >> 5; + int size = arg + 1; + + switch (type) { + case ENCODED_BYTE: + case ENCODED_SHORT: + case ENCODED_CHAR: + case ENCODED_INT: + case ENCODED_LONG: + case ENCODED_FLOAT: + case ENCODED_DOUBLE: + visitPrimitive(argAndType, type, arg, size); + break; + case ENCODED_STRING: + visitString(type, readIndex(in, size)); + break; + case ENCODED_TYPE: + visitType(type, readIndex(in, size)); + break; + case ENCODED_FIELD: + case ENCODED_ENUM: + visitField(type, readIndex(in, size)); + break; + case ENCODED_METHOD: + visitMethod(type, readIndex(in, size)); + break; + case ENCODED_ARRAY: + visitArrayValue(argAndType); + readArray(); + break; + case ENCODED_ANNOTATION: + visitAnnotationValue(argAndType); + readAnnotation(); + break; + case ENCODED_NULL: + visitEncodedNull(argAndType); + break; + case ENCODED_BOOLEAN: + visitEncodedBoolean(argAndType); + break; + } + } + + protected void visitArray(int size) {} + protected void visitAnnotation(int typeIndex, int size) {} + protected void visitAnnotationName(int nameIndex) {} + protected void visitPrimitive(int argAndType, int type, int arg, int size) { + for (int i = 0; i < size; i++) { + in.readByte(); + } + } + protected void visitString(int type, int index) {} + protected void visitType(int type, int index) {} + protected void visitField(int type, int index) {} + protected void visitMethod(int type, int index) {} + protected void visitArrayValue(int argAndType) {} + protected void visitAnnotationValue(int argAndType) {} + protected void visitEncodedBoolean(int argAndType) {} + protected void visitEncodedNull(int argAndType) {} + + private int readIndex(ByteInput in, int byteCount) { + int result = 0; + int shift = 0; + for (int i = 0; i < byteCount; i++) { + result += (in.readByte() & 0xff) << shift; + shift += 8; + } + return result; + } +} diff --git a/dx/src/com/android/jack/dx/io/FieldId.java b/dx/src/com/android/jack/dx/io/FieldId.java new file mode 100644 index 00000000..73c749c9 --- /dev/null +++ b/dx/src/com/android/jack/dx/io/FieldId.java @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.io; + +import com.android.jack.dx.util.Unsigned; + +public final class FieldId implements Comparable { + private final DexBuffer buffer; + private final int declaringClassIndex; + private final int typeIndex; + private final int nameIndex; + + public FieldId(DexBuffer buffer, int declaringClassIndex, int typeIndex, int nameIndex) { + this.buffer = buffer; + this.declaringClassIndex = declaringClassIndex; + this.typeIndex = typeIndex; + this.nameIndex = nameIndex; + } + + public int getDeclaringClassIndex() { + return declaringClassIndex; + } + + public int getTypeIndex() { + return typeIndex; + } + + public int getNameIndex() { + return nameIndex; + } + + public int compareTo(FieldId other) { + if (declaringClassIndex != other.declaringClassIndex) { + return Unsigned.compare(declaringClassIndex, other.declaringClassIndex); + } + if (nameIndex != other.nameIndex) { + return Unsigned.compare(nameIndex, other.nameIndex); + } + return Unsigned.compare(typeIndex, other.typeIndex); // should always be 0 + } + + public void writeTo(DexBuffer.Section out) { + out.writeUnsignedShort(declaringClassIndex); + out.writeUnsignedShort(typeIndex); + out.writeInt(nameIndex); + } + + @Override public String toString() { + if (buffer == null) { + return declaringClassIndex + " " + typeIndex + " " + nameIndex; + } + return buffer.typeNames().get(typeIndex) + "." + buffer.strings().get(nameIndex); + } +} diff --git a/dx/src/com/android/jack/dx/io/IndexType.java b/dx/src/com/android/jack/dx/io/IndexType.java new file mode 100644 index 00000000..05389669 --- /dev/null +++ b/dx/src/com/android/jack/dx/io/IndexType.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.io; + +/** + * The various types that an index in a Dalvik instruction might refer to. + */ +public enum IndexType { + /** "Unknown." Used for undefined opcodes. */ + UNKNOWN, + + /** no index used */ + NONE, + + /** "It depends." Used for {@code throw-verification-error}. */ + VARIES, + + /** type reference index */ + TYPE_REF, + + /** string reference index */ + STRING_REF, + + /** method reference index */ + METHOD_REF, + + /** field reference index */ + FIELD_REF, + + /** inline method index (for inline linked method invocations) */ + INLINE_METHOD, + + /** direct vtable offset (for static linked method invocations) */ + VTABLE_OFFSET, + + /** direct field offset (for static linked field accesses) */ + FIELD_OFFSET; +} diff --git a/dx/src/com/android/jack/dx/io/MethodId.java b/dx/src/com/android/jack/dx/io/MethodId.java new file mode 100644 index 00000000..172c02f0 --- /dev/null +++ b/dx/src/com/android/jack/dx/io/MethodId.java @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.io; + +import com.android.jack.dx.util.Unsigned; + +public final class MethodId implements Comparable { + private final DexBuffer buffer; + private final int declaringClassIndex; + private final int protoIndex; + private final int nameIndex; + + public MethodId(DexBuffer buffer, int declaringClassIndex, int protoIndex, int nameIndex) { + this.buffer = buffer; + this.declaringClassIndex = declaringClassIndex; + this.protoIndex = protoIndex; + this.nameIndex = nameIndex; + } + + public int getDeclaringClassIndex() { + return declaringClassIndex; + } + + public int getProtoIndex() { + return protoIndex; + } + + public int getNameIndex() { + return nameIndex; + } + + public int compareTo(MethodId other) { + if (declaringClassIndex != other.declaringClassIndex) { + return Unsigned.compare(declaringClassIndex, other.declaringClassIndex); + } + if (nameIndex != other.nameIndex) { + return Unsigned.compare(nameIndex, other.nameIndex); + } + return Unsigned.compare(protoIndex, other.protoIndex); + } + + public void writeTo(DexBuffer.Section out) { + out.writeUnsignedShort(declaringClassIndex); + out.writeUnsignedShort(protoIndex); + out.writeInt(nameIndex); + } + + @Override public String toString() { + if (buffer == null) { + return declaringClassIndex + " " + protoIndex + " " + nameIndex; + } + return buffer.typeNames().get(declaringClassIndex) + + "." + buffer.strings().get(nameIndex) + + buffer.readTypeList(buffer.protoIds().get(protoIndex).getParametersOffset()); + } +} diff --git a/dx/src/com/android/jack/dx/io/OpcodeInfo.java b/dx/src/com/android/jack/dx/io/OpcodeInfo.java new file mode 100644 index 00000000..069784aa --- /dev/null +++ b/dx/src/com/android/jack/dx/io/OpcodeInfo.java @@ -0,0 +1,1265 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.io; + +import com.android.jack.dx.io.instructions.InstructionCodec; +import com.android.jack.dx.util.Hex; + +/** + * Information about each Dalvik opcode. + */ +public final class OpcodeInfo { + /* + * TODO: Merge at least most of the info from the Dops class into + * this one. + */ + + /** non-null; array containing all the information */ + private static final Info[] INFO; + + /** + * pseudo-opcode used for nonstandard formatted "instructions" + * (which are mostly not actually instructions, though they do + * appear in instruction lists). TODO: Retire the usage of this + * constant. + */ + public static final Info SPECIAL_FORMAT = + new Info(Opcodes.SPECIAL_FORMAT, "", + InstructionCodec.FORMAT_00X, IndexType.NONE); + + // TODO: These payload opcodes should be generated by opcode-gen. + + public static final Info PACKED_SWITCH_PAYLOAD = + new Info(Opcodes.PACKED_SWITCH_PAYLOAD, "packed-switch-payload", + InstructionCodec.FORMAT_PACKED_SWITCH_PAYLOAD, + IndexType.NONE); + + public static final Info SPARSE_SWITCH_PAYLOAD = + new Info(Opcodes.SPARSE_SWITCH_PAYLOAD, "sparse-switch-payload", + InstructionCodec.FORMAT_SPARSE_SWITCH_PAYLOAD, + IndexType.NONE); + + public static final Info FILL_ARRAY_DATA_PAYLOAD = + new Info(Opcodes.FILL_ARRAY_DATA_PAYLOAD, "fill-array-data-payload", + InstructionCodec.FORMAT_FILL_ARRAY_DATA_PAYLOAD, + IndexType.NONE); + + // BEGIN(opcode-info-defs); GENERATED AUTOMATICALLY BY opcode-gen + public static final Info NOP = + new Info(Opcodes.NOP, "nop", + InstructionCodec.FORMAT_10X, IndexType.NONE); + + public static final Info MOVE = + new Info(Opcodes.MOVE, "move", + InstructionCodec.FORMAT_12X, IndexType.NONE); + + public static final Info MOVE_FROM16 = + new Info(Opcodes.MOVE_FROM16, "move/from16", + InstructionCodec.FORMAT_22X, IndexType.NONE); + + public static final Info MOVE_16 = + new Info(Opcodes.MOVE_16, "move/16", + InstructionCodec.FORMAT_32X, IndexType.NONE); + + public static final Info MOVE_WIDE = + new Info(Opcodes.MOVE_WIDE, "move-wide", + InstructionCodec.FORMAT_12X, IndexType.NONE); + + public static final Info MOVE_WIDE_FROM16 = + new Info(Opcodes.MOVE_WIDE_FROM16, "move-wide/from16", + InstructionCodec.FORMAT_22X, IndexType.NONE); + + public static final Info MOVE_WIDE_16 = + new Info(Opcodes.MOVE_WIDE_16, "move-wide/16", + InstructionCodec.FORMAT_32X, IndexType.NONE); + + public static final Info MOVE_OBJECT = + new Info(Opcodes.MOVE_OBJECT, "move-object", + InstructionCodec.FORMAT_12X, IndexType.NONE); + + public static final Info MOVE_OBJECT_FROM16 = + new Info(Opcodes.MOVE_OBJECT_FROM16, "move-object/from16", + InstructionCodec.FORMAT_22X, IndexType.NONE); + + public static final Info MOVE_OBJECT_16 = + new Info(Opcodes.MOVE_OBJECT_16, "move-object/16", + InstructionCodec.FORMAT_32X, IndexType.NONE); + + public static final Info MOVE_RESULT = + new Info(Opcodes.MOVE_RESULT, "move-result", + InstructionCodec.FORMAT_11X, IndexType.NONE); + + public static final Info MOVE_RESULT_WIDE = + new Info(Opcodes.MOVE_RESULT_WIDE, "move-result-wide", + InstructionCodec.FORMAT_11X, IndexType.NONE); + + public static final Info MOVE_RESULT_OBJECT = + new Info(Opcodes.MOVE_RESULT_OBJECT, "move-result-object", + InstructionCodec.FORMAT_11X, IndexType.NONE); + + public static final Info MOVE_EXCEPTION = + new Info(Opcodes.MOVE_EXCEPTION, "move-exception", + InstructionCodec.FORMAT_11X, IndexType.NONE); + + public static final Info RETURN_VOID = + new Info(Opcodes.RETURN_VOID, "return-void", + InstructionCodec.FORMAT_10X, IndexType.NONE); + + public static final Info RETURN = + new Info(Opcodes.RETURN, "return", + InstructionCodec.FORMAT_11X, IndexType.NONE); + + public static final Info RETURN_WIDE = + new Info(Opcodes.RETURN_WIDE, "return-wide", + InstructionCodec.FORMAT_11X, IndexType.NONE); + + public static final Info RETURN_OBJECT = + new Info(Opcodes.RETURN_OBJECT, "return-object", + InstructionCodec.FORMAT_11X, IndexType.NONE); + + public static final Info CONST_4 = + new Info(Opcodes.CONST_4, "const/4", + InstructionCodec.FORMAT_11N, IndexType.NONE); + + public static final Info CONST_16 = + new Info(Opcodes.CONST_16, "const/16", + InstructionCodec.FORMAT_21S, IndexType.NONE); + + public static final Info CONST = + new Info(Opcodes.CONST, "const", + InstructionCodec.FORMAT_31I, IndexType.NONE); + + public static final Info CONST_HIGH16 = + new Info(Opcodes.CONST_HIGH16, "const/high16", + InstructionCodec.FORMAT_21H, IndexType.NONE); + + public static final Info CONST_WIDE_16 = + new Info(Opcodes.CONST_WIDE_16, "const-wide/16", + InstructionCodec.FORMAT_21S, IndexType.NONE); + + public static final Info CONST_WIDE_32 = + new Info(Opcodes.CONST_WIDE_32, "const-wide/32", + InstructionCodec.FORMAT_31I, IndexType.NONE); + + public static final Info CONST_WIDE = + new Info(Opcodes.CONST_WIDE, "const-wide", + InstructionCodec.FORMAT_51L, IndexType.NONE); + + public static final Info CONST_WIDE_HIGH16 = + new Info(Opcodes.CONST_WIDE_HIGH16, "const-wide/high16", + InstructionCodec.FORMAT_21H, IndexType.NONE); + + public static final Info CONST_STRING = + new Info(Opcodes.CONST_STRING, "const-string", + InstructionCodec.FORMAT_21C, IndexType.STRING_REF); + + public static final Info CONST_STRING_JUMBO = + new Info(Opcodes.CONST_STRING_JUMBO, "const-string/jumbo", + InstructionCodec.FORMAT_31C, IndexType.STRING_REF); + + public static final Info CONST_CLASS = + new Info(Opcodes.CONST_CLASS, "const-class", + InstructionCodec.FORMAT_21C, IndexType.TYPE_REF); + + public static final Info MONITOR_ENTER = + new Info(Opcodes.MONITOR_ENTER, "monitor-enter", + InstructionCodec.FORMAT_11X, IndexType.NONE); + + public static final Info MONITOR_EXIT = + new Info(Opcodes.MONITOR_EXIT, "monitor-exit", + InstructionCodec.FORMAT_11X, IndexType.NONE); + + public static final Info CHECK_CAST = + new Info(Opcodes.CHECK_CAST, "check-cast", + InstructionCodec.FORMAT_21C, IndexType.TYPE_REF); + + public static final Info INSTANCE_OF = + new Info(Opcodes.INSTANCE_OF, "instance-of", + InstructionCodec.FORMAT_22C, IndexType.TYPE_REF); + + public static final Info ARRAY_LENGTH = + new Info(Opcodes.ARRAY_LENGTH, "array-length", + InstructionCodec.FORMAT_12X, IndexType.NONE); + + public static final Info NEW_INSTANCE = + new Info(Opcodes.NEW_INSTANCE, "new-instance", + InstructionCodec.FORMAT_21C, IndexType.TYPE_REF); + + public static final Info NEW_ARRAY = + new Info(Opcodes.NEW_ARRAY, "new-array", + InstructionCodec.FORMAT_22C, IndexType.TYPE_REF); + + public static final Info FILLED_NEW_ARRAY = + new Info(Opcodes.FILLED_NEW_ARRAY, "filled-new-array", + InstructionCodec.FORMAT_35C, IndexType.TYPE_REF); + + public static final Info FILLED_NEW_ARRAY_RANGE = + new Info(Opcodes.FILLED_NEW_ARRAY_RANGE, "filled-new-array/range", + InstructionCodec.FORMAT_3RC, IndexType.TYPE_REF); + + public static final Info FILL_ARRAY_DATA = + new Info(Opcodes.FILL_ARRAY_DATA, "fill-array-data", + InstructionCodec.FORMAT_31T, IndexType.NONE); + + public static final Info THROW = + new Info(Opcodes.THROW, "throw", + InstructionCodec.FORMAT_11X, IndexType.NONE); + + public static final Info GOTO = + new Info(Opcodes.GOTO, "goto", + InstructionCodec.FORMAT_10T, IndexType.NONE); + + public static final Info GOTO_16 = + new Info(Opcodes.GOTO_16, "goto/16", + InstructionCodec.FORMAT_20T, IndexType.NONE); + + public static final Info GOTO_32 = + new Info(Opcodes.GOTO_32, "goto/32", + InstructionCodec.FORMAT_30T, IndexType.NONE); + + public static final Info PACKED_SWITCH = + new Info(Opcodes.PACKED_SWITCH, "packed-switch", + InstructionCodec.FORMAT_31T, IndexType.NONE); + + public static final Info SPARSE_SWITCH = + new Info(Opcodes.SPARSE_SWITCH, "sparse-switch", + InstructionCodec.FORMAT_31T, IndexType.NONE); + + public static final Info CMPL_FLOAT = + new Info(Opcodes.CMPL_FLOAT, "cmpl-float", + InstructionCodec.FORMAT_23X, IndexType.NONE); + + public static final Info CMPG_FLOAT = + new Info(Opcodes.CMPG_FLOAT, "cmpg-float", + InstructionCodec.FORMAT_23X, IndexType.NONE); + + public static final Info CMPL_DOUBLE = + new Info(Opcodes.CMPL_DOUBLE, "cmpl-double", + InstructionCodec.FORMAT_23X, IndexType.NONE); + + public static final Info CMPG_DOUBLE = + new Info(Opcodes.CMPG_DOUBLE, "cmpg-double", + InstructionCodec.FORMAT_23X, IndexType.NONE); + + public static final Info CMP_LONG = + new Info(Opcodes.CMP_LONG, "cmp-long", + InstructionCodec.FORMAT_23X, IndexType.NONE); + + public static final Info IF_EQ = + new Info(Opcodes.IF_EQ, "if-eq", + InstructionCodec.FORMAT_22T, IndexType.NONE); + + public static final Info IF_NE = + new Info(Opcodes.IF_NE, "if-ne", + InstructionCodec.FORMAT_22T, IndexType.NONE); + + public static final Info IF_LT = + new Info(Opcodes.IF_LT, "if-lt", + InstructionCodec.FORMAT_22T, IndexType.NONE); + + public static final Info IF_GE = + new Info(Opcodes.IF_GE, "if-ge", + InstructionCodec.FORMAT_22T, IndexType.NONE); + + public static final Info IF_GT = + new Info(Opcodes.IF_GT, "if-gt", + InstructionCodec.FORMAT_22T, IndexType.NONE); + + public static final Info IF_LE = + new Info(Opcodes.IF_LE, "if-le", + InstructionCodec.FORMAT_22T, IndexType.NONE); + + public static final Info IF_EQZ = + new Info(Opcodes.IF_EQZ, "if-eqz", + InstructionCodec.FORMAT_21T, IndexType.NONE); + + public static final Info IF_NEZ = + new Info(Opcodes.IF_NEZ, "if-nez", + InstructionCodec.FORMAT_21T, IndexType.NONE); + + public static final Info IF_LTZ = + new Info(Opcodes.IF_LTZ, "if-ltz", + InstructionCodec.FORMAT_21T, IndexType.NONE); + + public static final Info IF_GEZ = + new Info(Opcodes.IF_GEZ, "if-gez", + InstructionCodec.FORMAT_21T, IndexType.NONE); + + public static final Info IF_GTZ = + new Info(Opcodes.IF_GTZ, "if-gtz", + InstructionCodec.FORMAT_21T, IndexType.NONE); + + public static final Info IF_LEZ = + new Info(Opcodes.IF_LEZ, "if-lez", + InstructionCodec.FORMAT_21T, IndexType.NONE); + + public static final Info AGET = + new Info(Opcodes.AGET, "aget", + InstructionCodec.FORMAT_23X, IndexType.NONE); + + public static final Info AGET_WIDE = + new Info(Opcodes.AGET_WIDE, "aget-wide", + InstructionCodec.FORMAT_23X, IndexType.NONE); + + public static final Info AGET_OBJECT = + new Info(Opcodes.AGET_OBJECT, "aget-object", + InstructionCodec.FORMAT_23X, IndexType.NONE); + + public static final Info AGET_BOOLEAN = + new Info(Opcodes.AGET_BOOLEAN, "aget-boolean", + InstructionCodec.FORMAT_23X, IndexType.NONE); + + public static final Info AGET_BYTE = + new Info(Opcodes.AGET_BYTE, "aget-byte", + InstructionCodec.FORMAT_23X, IndexType.NONE); + + public static final Info AGET_CHAR = + new Info(Opcodes.AGET_CHAR, "aget-char", + InstructionCodec.FORMAT_23X, IndexType.NONE); + + public static final Info AGET_SHORT = + new Info(Opcodes.AGET_SHORT, "aget-short", + InstructionCodec.FORMAT_23X, IndexType.NONE); + + public static final Info APUT = + new Info(Opcodes.APUT, "aput", + InstructionCodec.FORMAT_23X, IndexType.NONE); + + public static final Info APUT_WIDE = + new Info(Opcodes.APUT_WIDE, "aput-wide", + InstructionCodec.FORMAT_23X, IndexType.NONE); + + public static final Info APUT_OBJECT = + new Info(Opcodes.APUT_OBJECT, "aput-object", + InstructionCodec.FORMAT_23X, IndexType.NONE); + + public static final Info APUT_BOOLEAN = + new Info(Opcodes.APUT_BOOLEAN, "aput-boolean", + InstructionCodec.FORMAT_23X, IndexType.NONE); + + public static final Info APUT_BYTE = + new Info(Opcodes.APUT_BYTE, "aput-byte", + InstructionCodec.FORMAT_23X, IndexType.NONE); + + public static final Info APUT_CHAR = + new Info(Opcodes.APUT_CHAR, "aput-char", + InstructionCodec.FORMAT_23X, IndexType.NONE); + + public static final Info APUT_SHORT = + new Info(Opcodes.APUT_SHORT, "aput-short", + InstructionCodec.FORMAT_23X, IndexType.NONE); + + public static final Info IGET = + new Info(Opcodes.IGET, "iget", + InstructionCodec.FORMAT_22C, IndexType.FIELD_REF); + + public static final Info IGET_WIDE = + new Info(Opcodes.IGET_WIDE, "iget-wide", + InstructionCodec.FORMAT_22C, IndexType.FIELD_REF); + + public static final Info IGET_OBJECT = + new Info(Opcodes.IGET_OBJECT, "iget-object", + InstructionCodec.FORMAT_22C, IndexType.FIELD_REF); + + public static final Info IGET_BOOLEAN = + new Info(Opcodes.IGET_BOOLEAN, "iget-boolean", + InstructionCodec.FORMAT_22C, IndexType.FIELD_REF); + + public static final Info IGET_BYTE = + new Info(Opcodes.IGET_BYTE, "iget-byte", + InstructionCodec.FORMAT_22C, IndexType.FIELD_REF); + + public static final Info IGET_CHAR = + new Info(Opcodes.IGET_CHAR, "iget-char", + InstructionCodec.FORMAT_22C, IndexType.FIELD_REF); + + public static final Info IGET_SHORT = + new Info(Opcodes.IGET_SHORT, "iget-short", + InstructionCodec.FORMAT_22C, IndexType.FIELD_REF); + + public static final Info IPUT = + new Info(Opcodes.IPUT, "iput", + InstructionCodec.FORMAT_22C, IndexType.FIELD_REF); + + public static final Info IPUT_WIDE = + new Info(Opcodes.IPUT_WIDE, "iput-wide", + InstructionCodec.FORMAT_22C, IndexType.FIELD_REF); + + public static final Info IPUT_OBJECT = + new Info(Opcodes.IPUT_OBJECT, "iput-object", + InstructionCodec.FORMAT_22C, IndexType.FIELD_REF); + + public static final Info IPUT_BOOLEAN = + new Info(Opcodes.IPUT_BOOLEAN, "iput-boolean", + InstructionCodec.FORMAT_22C, IndexType.FIELD_REF); + + public static final Info IPUT_BYTE = + new Info(Opcodes.IPUT_BYTE, "iput-byte", + InstructionCodec.FORMAT_22C, IndexType.FIELD_REF); + + public static final Info IPUT_CHAR = + new Info(Opcodes.IPUT_CHAR, "iput-char", + InstructionCodec.FORMAT_22C, IndexType.FIELD_REF); + + public static final Info IPUT_SHORT = + new Info(Opcodes.IPUT_SHORT, "iput-short", + InstructionCodec.FORMAT_22C, IndexType.FIELD_REF); + + public static final Info SGET = + new Info(Opcodes.SGET, "sget", + InstructionCodec.FORMAT_21C, IndexType.FIELD_REF); + + public static final Info SGET_WIDE = + new Info(Opcodes.SGET_WIDE, "sget-wide", + InstructionCodec.FORMAT_21C, IndexType.FIELD_REF); + + public static final Info SGET_OBJECT = + new Info(Opcodes.SGET_OBJECT, "sget-object", + InstructionCodec.FORMAT_21C, IndexType.FIELD_REF); + + public static final Info SGET_BOOLEAN = + new Info(Opcodes.SGET_BOOLEAN, "sget-boolean", + InstructionCodec.FORMAT_21C, IndexType.FIELD_REF); + + public static final Info SGET_BYTE = + new Info(Opcodes.SGET_BYTE, "sget-byte", + InstructionCodec.FORMAT_21C, IndexType.FIELD_REF); + + public static final Info SGET_CHAR = + new Info(Opcodes.SGET_CHAR, "sget-char", + InstructionCodec.FORMAT_21C, IndexType.FIELD_REF); + + public static final Info SGET_SHORT = + new Info(Opcodes.SGET_SHORT, "sget-short", + InstructionCodec.FORMAT_21C, IndexType.FIELD_REF); + + public static final Info SPUT = + new Info(Opcodes.SPUT, "sput", + InstructionCodec.FORMAT_21C, IndexType.FIELD_REF); + + public static final Info SPUT_WIDE = + new Info(Opcodes.SPUT_WIDE, "sput-wide", + InstructionCodec.FORMAT_21C, IndexType.FIELD_REF); + + public static final Info SPUT_OBJECT = + new Info(Opcodes.SPUT_OBJECT, "sput-object", + InstructionCodec.FORMAT_21C, IndexType.FIELD_REF); + + public static final Info SPUT_BOOLEAN = + new Info(Opcodes.SPUT_BOOLEAN, "sput-boolean", + InstructionCodec.FORMAT_21C, IndexType.FIELD_REF); + + public static final Info SPUT_BYTE = + new Info(Opcodes.SPUT_BYTE, "sput-byte", + InstructionCodec.FORMAT_21C, IndexType.FIELD_REF); + + public static final Info SPUT_CHAR = + new Info(Opcodes.SPUT_CHAR, "sput-char", + InstructionCodec.FORMAT_21C, IndexType.FIELD_REF); + + public static final Info SPUT_SHORT = + new Info(Opcodes.SPUT_SHORT, "sput-short", + InstructionCodec.FORMAT_21C, IndexType.FIELD_REF); + + public static final Info INVOKE_VIRTUAL = + new Info(Opcodes.INVOKE_VIRTUAL, "invoke-virtual", + InstructionCodec.FORMAT_35C, IndexType.METHOD_REF); + + public static final Info INVOKE_SUPER = + new Info(Opcodes.INVOKE_SUPER, "invoke-super", + InstructionCodec.FORMAT_35C, IndexType.METHOD_REF); + + public static final Info INVOKE_DIRECT = + new Info(Opcodes.INVOKE_DIRECT, "invoke-direct", + InstructionCodec.FORMAT_35C, IndexType.METHOD_REF); + + public static final Info INVOKE_STATIC = + new Info(Opcodes.INVOKE_STATIC, "invoke-static", + InstructionCodec.FORMAT_35C, IndexType.METHOD_REF); + + public static final Info INVOKE_INTERFACE = + new Info(Opcodes.INVOKE_INTERFACE, "invoke-interface", + InstructionCodec.FORMAT_35C, IndexType.METHOD_REF); + + public static final Info INVOKE_VIRTUAL_RANGE = + new Info(Opcodes.INVOKE_VIRTUAL_RANGE, "invoke-virtual/range", + InstructionCodec.FORMAT_3RC, IndexType.METHOD_REF); + + public static final Info INVOKE_SUPER_RANGE = + new Info(Opcodes.INVOKE_SUPER_RANGE, "invoke-super/range", + InstructionCodec.FORMAT_3RC, IndexType.METHOD_REF); + + public static final Info INVOKE_DIRECT_RANGE = + new Info(Opcodes.INVOKE_DIRECT_RANGE, "invoke-direct/range", + InstructionCodec.FORMAT_3RC, IndexType.METHOD_REF); + + public static final Info INVOKE_STATIC_RANGE = + new Info(Opcodes.INVOKE_STATIC_RANGE, "invoke-static/range", + InstructionCodec.FORMAT_3RC, IndexType.METHOD_REF); + + public static final Info INVOKE_INTERFACE_RANGE = + new Info(Opcodes.INVOKE_INTERFACE_RANGE, "invoke-interface/range", + InstructionCodec.FORMAT_3RC, IndexType.METHOD_REF); + + public static final Info NEG_INT = + new Info(Opcodes.NEG_INT, "neg-int", + InstructionCodec.FORMAT_12X, IndexType.NONE); + + public static final Info NOT_INT = + new Info(Opcodes.NOT_INT, "not-int", + InstructionCodec.FORMAT_12X, IndexType.NONE); + + public static final Info NEG_LONG = + new Info(Opcodes.NEG_LONG, "neg-long", + InstructionCodec.FORMAT_12X, IndexType.NONE); + + public static final Info NOT_LONG = + new Info(Opcodes.NOT_LONG, "not-long", + InstructionCodec.FORMAT_12X, IndexType.NONE); + + public static final Info NEG_FLOAT = + new Info(Opcodes.NEG_FLOAT, "neg-float", + InstructionCodec.FORMAT_12X, IndexType.NONE); + + public static final Info NEG_DOUBLE = + new Info(Opcodes.NEG_DOUBLE, "neg-double", + InstructionCodec.FORMAT_12X, IndexType.NONE); + + public static final Info INT_TO_LONG = + new Info(Opcodes.INT_TO_LONG, "int-to-long", + InstructionCodec.FORMAT_12X, IndexType.NONE); + + public static final Info INT_TO_FLOAT = + new Info(Opcodes.INT_TO_FLOAT, "int-to-float", + InstructionCodec.FORMAT_12X, IndexType.NONE); + + public static final Info INT_TO_DOUBLE = + new Info(Opcodes.INT_TO_DOUBLE, "int-to-double", + InstructionCodec.FORMAT_12X, IndexType.NONE); + + public static final Info LONG_TO_INT = + new Info(Opcodes.LONG_TO_INT, "long-to-int", + InstructionCodec.FORMAT_12X, IndexType.NONE); + + public static final Info LONG_TO_FLOAT = + new Info(Opcodes.LONG_TO_FLOAT, "long-to-float", + InstructionCodec.FORMAT_12X, IndexType.NONE); + + public static final Info LONG_TO_DOUBLE = + new Info(Opcodes.LONG_TO_DOUBLE, "long-to-double", + InstructionCodec.FORMAT_12X, IndexType.NONE); + + public static final Info FLOAT_TO_INT = + new Info(Opcodes.FLOAT_TO_INT, "float-to-int", + InstructionCodec.FORMAT_12X, IndexType.NONE); + + public static final Info FLOAT_TO_LONG = + new Info(Opcodes.FLOAT_TO_LONG, "float-to-long", + InstructionCodec.FORMAT_12X, IndexType.NONE); + + public static final Info FLOAT_TO_DOUBLE = + new Info(Opcodes.FLOAT_TO_DOUBLE, "float-to-double", + InstructionCodec.FORMAT_12X, IndexType.NONE); + + public static final Info DOUBLE_TO_INT = + new Info(Opcodes.DOUBLE_TO_INT, "double-to-int", + InstructionCodec.FORMAT_12X, IndexType.NONE); + + public static final Info DOUBLE_TO_LONG = + new Info(Opcodes.DOUBLE_TO_LONG, "double-to-long", + InstructionCodec.FORMAT_12X, IndexType.NONE); + + public static final Info DOUBLE_TO_FLOAT = + new Info(Opcodes.DOUBLE_TO_FLOAT, "double-to-float", + InstructionCodec.FORMAT_12X, IndexType.NONE); + + public static final Info INT_TO_BYTE = + new Info(Opcodes.INT_TO_BYTE, "int-to-byte", + InstructionCodec.FORMAT_12X, IndexType.NONE); + + public static final Info INT_TO_CHAR = + new Info(Opcodes.INT_TO_CHAR, "int-to-char", + InstructionCodec.FORMAT_12X, IndexType.NONE); + + public static final Info INT_TO_SHORT = + new Info(Opcodes.INT_TO_SHORT, "int-to-short", + InstructionCodec.FORMAT_12X, IndexType.NONE); + + public static final Info ADD_INT = + new Info(Opcodes.ADD_INT, "add-int", + InstructionCodec.FORMAT_23X, IndexType.NONE); + + public static final Info SUB_INT = + new Info(Opcodes.SUB_INT, "sub-int", + InstructionCodec.FORMAT_23X, IndexType.NONE); + + public static final Info MUL_INT = + new Info(Opcodes.MUL_INT, "mul-int", + InstructionCodec.FORMAT_23X, IndexType.NONE); + + public static final Info DIV_INT = + new Info(Opcodes.DIV_INT, "div-int", + InstructionCodec.FORMAT_23X, IndexType.NONE); + + public static final Info REM_INT = + new Info(Opcodes.REM_INT, "rem-int", + InstructionCodec.FORMAT_23X, IndexType.NONE); + + public static final Info AND_INT = + new Info(Opcodes.AND_INT, "and-int", + InstructionCodec.FORMAT_23X, IndexType.NONE); + + public static final Info OR_INT = + new Info(Opcodes.OR_INT, "or-int", + InstructionCodec.FORMAT_23X, IndexType.NONE); + + public static final Info XOR_INT = + new Info(Opcodes.XOR_INT, "xor-int", + InstructionCodec.FORMAT_23X, IndexType.NONE); + + public static final Info SHL_INT = + new Info(Opcodes.SHL_INT, "shl-int", + InstructionCodec.FORMAT_23X, IndexType.NONE); + + public static final Info SHR_INT = + new Info(Opcodes.SHR_INT, "shr-int", + InstructionCodec.FORMAT_23X, IndexType.NONE); + + public static final Info USHR_INT = + new Info(Opcodes.USHR_INT, "ushr-int", + InstructionCodec.FORMAT_23X, IndexType.NONE); + + public static final Info ADD_LONG = + new Info(Opcodes.ADD_LONG, "add-long", + InstructionCodec.FORMAT_23X, IndexType.NONE); + + public static final Info SUB_LONG = + new Info(Opcodes.SUB_LONG, "sub-long", + InstructionCodec.FORMAT_23X, IndexType.NONE); + + public static final Info MUL_LONG = + new Info(Opcodes.MUL_LONG, "mul-long", + InstructionCodec.FORMAT_23X, IndexType.NONE); + + public static final Info DIV_LONG = + new Info(Opcodes.DIV_LONG, "div-long", + InstructionCodec.FORMAT_23X, IndexType.NONE); + + public static final Info REM_LONG = + new Info(Opcodes.REM_LONG, "rem-long", + InstructionCodec.FORMAT_23X, IndexType.NONE); + + public static final Info AND_LONG = + new Info(Opcodes.AND_LONG, "and-long", + InstructionCodec.FORMAT_23X, IndexType.NONE); + + public static final Info OR_LONG = + new Info(Opcodes.OR_LONG, "or-long", + InstructionCodec.FORMAT_23X, IndexType.NONE); + + public static final Info XOR_LONG = + new Info(Opcodes.XOR_LONG, "xor-long", + InstructionCodec.FORMAT_23X, IndexType.NONE); + + public static final Info SHL_LONG = + new Info(Opcodes.SHL_LONG, "shl-long", + InstructionCodec.FORMAT_23X, IndexType.NONE); + + public static final Info SHR_LONG = + new Info(Opcodes.SHR_LONG, "shr-long", + InstructionCodec.FORMAT_23X, IndexType.NONE); + + public static final Info USHR_LONG = + new Info(Opcodes.USHR_LONG, "ushr-long", + InstructionCodec.FORMAT_23X, IndexType.NONE); + + public static final Info ADD_FLOAT = + new Info(Opcodes.ADD_FLOAT, "add-float", + InstructionCodec.FORMAT_23X, IndexType.NONE); + + public static final Info SUB_FLOAT = + new Info(Opcodes.SUB_FLOAT, "sub-float", + InstructionCodec.FORMAT_23X, IndexType.NONE); + + public static final Info MUL_FLOAT = + new Info(Opcodes.MUL_FLOAT, "mul-float", + InstructionCodec.FORMAT_23X, IndexType.NONE); + + public static final Info DIV_FLOAT = + new Info(Opcodes.DIV_FLOAT, "div-float", + InstructionCodec.FORMAT_23X, IndexType.NONE); + + public static final Info REM_FLOAT = + new Info(Opcodes.REM_FLOAT, "rem-float", + InstructionCodec.FORMAT_23X, IndexType.NONE); + + public static final Info ADD_DOUBLE = + new Info(Opcodes.ADD_DOUBLE, "add-double", + InstructionCodec.FORMAT_23X, IndexType.NONE); + + public static final Info SUB_DOUBLE = + new Info(Opcodes.SUB_DOUBLE, "sub-double", + InstructionCodec.FORMAT_23X, IndexType.NONE); + + public static final Info MUL_DOUBLE = + new Info(Opcodes.MUL_DOUBLE, "mul-double", + InstructionCodec.FORMAT_23X, IndexType.NONE); + + public static final Info DIV_DOUBLE = + new Info(Opcodes.DIV_DOUBLE, "div-double", + InstructionCodec.FORMAT_23X, IndexType.NONE); + + public static final Info REM_DOUBLE = + new Info(Opcodes.REM_DOUBLE, "rem-double", + InstructionCodec.FORMAT_23X, IndexType.NONE); + + public static final Info ADD_INT_2ADDR = + new Info(Opcodes.ADD_INT_2ADDR, "add-int/2addr", + InstructionCodec.FORMAT_12X, IndexType.NONE); + + public static final Info SUB_INT_2ADDR = + new Info(Opcodes.SUB_INT_2ADDR, "sub-int/2addr", + InstructionCodec.FORMAT_12X, IndexType.NONE); + + public static final Info MUL_INT_2ADDR = + new Info(Opcodes.MUL_INT_2ADDR, "mul-int/2addr", + InstructionCodec.FORMAT_12X, IndexType.NONE); + + public static final Info DIV_INT_2ADDR = + new Info(Opcodes.DIV_INT_2ADDR, "div-int/2addr", + InstructionCodec.FORMAT_12X, IndexType.NONE); + + public static final Info REM_INT_2ADDR = + new Info(Opcodes.REM_INT_2ADDR, "rem-int/2addr", + InstructionCodec.FORMAT_12X, IndexType.NONE); + + public static final Info AND_INT_2ADDR = + new Info(Opcodes.AND_INT_2ADDR, "and-int/2addr", + InstructionCodec.FORMAT_12X, IndexType.NONE); + + public static final Info OR_INT_2ADDR = + new Info(Opcodes.OR_INT_2ADDR, "or-int/2addr", + InstructionCodec.FORMAT_12X, IndexType.NONE); + + public static final Info XOR_INT_2ADDR = + new Info(Opcodes.XOR_INT_2ADDR, "xor-int/2addr", + InstructionCodec.FORMAT_12X, IndexType.NONE); + + public static final Info SHL_INT_2ADDR = + new Info(Opcodes.SHL_INT_2ADDR, "shl-int/2addr", + InstructionCodec.FORMAT_12X, IndexType.NONE); + + public static final Info SHR_INT_2ADDR = + new Info(Opcodes.SHR_INT_2ADDR, "shr-int/2addr", + InstructionCodec.FORMAT_12X, IndexType.NONE); + + public static final Info USHR_INT_2ADDR = + new Info(Opcodes.USHR_INT_2ADDR, "ushr-int/2addr", + InstructionCodec.FORMAT_12X, IndexType.NONE); + + public static final Info ADD_LONG_2ADDR = + new Info(Opcodes.ADD_LONG_2ADDR, "add-long/2addr", + InstructionCodec.FORMAT_12X, IndexType.NONE); + + public static final Info SUB_LONG_2ADDR = + new Info(Opcodes.SUB_LONG_2ADDR, "sub-long/2addr", + InstructionCodec.FORMAT_12X, IndexType.NONE); + + public static final Info MUL_LONG_2ADDR = + new Info(Opcodes.MUL_LONG_2ADDR, "mul-long/2addr", + InstructionCodec.FORMAT_12X, IndexType.NONE); + + public static final Info DIV_LONG_2ADDR = + new Info(Opcodes.DIV_LONG_2ADDR, "div-long/2addr", + InstructionCodec.FORMAT_12X, IndexType.NONE); + + public static final Info REM_LONG_2ADDR = + new Info(Opcodes.REM_LONG_2ADDR, "rem-long/2addr", + InstructionCodec.FORMAT_12X, IndexType.NONE); + + public static final Info AND_LONG_2ADDR = + new Info(Opcodes.AND_LONG_2ADDR, "and-long/2addr", + InstructionCodec.FORMAT_12X, IndexType.NONE); + + public static final Info OR_LONG_2ADDR = + new Info(Opcodes.OR_LONG_2ADDR, "or-long/2addr", + InstructionCodec.FORMAT_12X, IndexType.NONE); + + public static final Info XOR_LONG_2ADDR = + new Info(Opcodes.XOR_LONG_2ADDR, "xor-long/2addr", + InstructionCodec.FORMAT_12X, IndexType.NONE); + + public static final Info SHL_LONG_2ADDR = + new Info(Opcodes.SHL_LONG_2ADDR, "shl-long/2addr", + InstructionCodec.FORMAT_12X, IndexType.NONE); + + public static final Info SHR_LONG_2ADDR = + new Info(Opcodes.SHR_LONG_2ADDR, "shr-long/2addr", + InstructionCodec.FORMAT_12X, IndexType.NONE); + + public static final Info USHR_LONG_2ADDR = + new Info(Opcodes.USHR_LONG_2ADDR, "ushr-long/2addr", + InstructionCodec.FORMAT_12X, IndexType.NONE); + + public static final Info ADD_FLOAT_2ADDR = + new Info(Opcodes.ADD_FLOAT_2ADDR, "add-float/2addr", + InstructionCodec.FORMAT_12X, IndexType.NONE); + + public static final Info SUB_FLOAT_2ADDR = + new Info(Opcodes.SUB_FLOAT_2ADDR, "sub-float/2addr", + InstructionCodec.FORMAT_12X, IndexType.NONE); + + public static final Info MUL_FLOAT_2ADDR = + new Info(Opcodes.MUL_FLOAT_2ADDR, "mul-float/2addr", + InstructionCodec.FORMAT_12X, IndexType.NONE); + + public static final Info DIV_FLOAT_2ADDR = + new Info(Opcodes.DIV_FLOAT_2ADDR, "div-float/2addr", + InstructionCodec.FORMAT_12X, IndexType.NONE); + + public static final Info REM_FLOAT_2ADDR = + new Info(Opcodes.REM_FLOAT_2ADDR, "rem-float/2addr", + InstructionCodec.FORMAT_12X, IndexType.NONE); + + public static final Info ADD_DOUBLE_2ADDR = + new Info(Opcodes.ADD_DOUBLE_2ADDR, "add-double/2addr", + InstructionCodec.FORMAT_12X, IndexType.NONE); + + public static final Info SUB_DOUBLE_2ADDR = + new Info(Opcodes.SUB_DOUBLE_2ADDR, "sub-double/2addr", + InstructionCodec.FORMAT_12X, IndexType.NONE); + + public static final Info MUL_DOUBLE_2ADDR = + new Info(Opcodes.MUL_DOUBLE_2ADDR, "mul-double/2addr", + InstructionCodec.FORMAT_12X, IndexType.NONE); + + public static final Info DIV_DOUBLE_2ADDR = + new Info(Opcodes.DIV_DOUBLE_2ADDR, "div-double/2addr", + InstructionCodec.FORMAT_12X, IndexType.NONE); + + public static final Info REM_DOUBLE_2ADDR = + new Info(Opcodes.REM_DOUBLE_2ADDR, "rem-double/2addr", + InstructionCodec.FORMAT_12X, IndexType.NONE); + + public static final Info ADD_INT_LIT16 = + new Info(Opcodes.ADD_INT_LIT16, "add-int/lit16", + InstructionCodec.FORMAT_22S, IndexType.NONE); + + public static final Info RSUB_INT = + new Info(Opcodes.RSUB_INT, "rsub-int", + InstructionCodec.FORMAT_22S, IndexType.NONE); + + public static final Info MUL_INT_LIT16 = + new Info(Opcodes.MUL_INT_LIT16, "mul-int/lit16", + InstructionCodec.FORMAT_22S, IndexType.NONE); + + public static final Info DIV_INT_LIT16 = + new Info(Opcodes.DIV_INT_LIT16, "div-int/lit16", + InstructionCodec.FORMAT_22S, IndexType.NONE); + + public static final Info REM_INT_LIT16 = + new Info(Opcodes.REM_INT_LIT16, "rem-int/lit16", + InstructionCodec.FORMAT_22S, IndexType.NONE); + + public static final Info AND_INT_LIT16 = + new Info(Opcodes.AND_INT_LIT16, "and-int/lit16", + InstructionCodec.FORMAT_22S, IndexType.NONE); + + public static final Info OR_INT_LIT16 = + new Info(Opcodes.OR_INT_LIT16, "or-int/lit16", + InstructionCodec.FORMAT_22S, IndexType.NONE); + + public static final Info XOR_INT_LIT16 = + new Info(Opcodes.XOR_INT_LIT16, "xor-int/lit16", + InstructionCodec.FORMAT_22S, IndexType.NONE); + + public static final Info ADD_INT_LIT8 = + new Info(Opcodes.ADD_INT_LIT8, "add-int/lit8", + InstructionCodec.FORMAT_22B, IndexType.NONE); + + public static final Info RSUB_INT_LIT8 = + new Info(Opcodes.RSUB_INT_LIT8, "rsub-int/lit8", + InstructionCodec.FORMAT_22B, IndexType.NONE); + + public static final Info MUL_INT_LIT8 = + new Info(Opcodes.MUL_INT_LIT8, "mul-int/lit8", + InstructionCodec.FORMAT_22B, IndexType.NONE); + + public static final Info DIV_INT_LIT8 = + new Info(Opcodes.DIV_INT_LIT8, "div-int/lit8", + InstructionCodec.FORMAT_22B, IndexType.NONE); + + public static final Info REM_INT_LIT8 = + new Info(Opcodes.REM_INT_LIT8, "rem-int/lit8", + InstructionCodec.FORMAT_22B, IndexType.NONE); + + public static final Info AND_INT_LIT8 = + new Info(Opcodes.AND_INT_LIT8, "and-int/lit8", + InstructionCodec.FORMAT_22B, IndexType.NONE); + + public static final Info OR_INT_LIT8 = + new Info(Opcodes.OR_INT_LIT8, "or-int/lit8", + InstructionCodec.FORMAT_22B, IndexType.NONE); + + public static final Info XOR_INT_LIT8 = + new Info(Opcodes.XOR_INT_LIT8, "xor-int/lit8", + InstructionCodec.FORMAT_22B, IndexType.NONE); + + public static final Info SHL_INT_LIT8 = + new Info(Opcodes.SHL_INT_LIT8, "shl-int/lit8", + InstructionCodec.FORMAT_22B, IndexType.NONE); + + public static final Info SHR_INT_LIT8 = + new Info(Opcodes.SHR_INT_LIT8, "shr-int/lit8", + InstructionCodec.FORMAT_22B, IndexType.NONE); + + public static final Info USHR_INT_LIT8 = + new Info(Opcodes.USHR_INT_LIT8, "ushr-int/lit8", + InstructionCodec.FORMAT_22B, IndexType.NONE); + + // END(opcode-info-defs) + + // Static initialization. + static { + INFO = new Info[Opcodes.MAX_VALUE - Opcodes.MIN_VALUE + 1]; + + // TODO: Stop using this constant. + set(SPECIAL_FORMAT); + + // TODO: These payload opcodes should be generated by opcode-gen. + set(PACKED_SWITCH_PAYLOAD); + set(SPARSE_SWITCH_PAYLOAD); + set(FILL_ARRAY_DATA_PAYLOAD); + + // BEGIN(opcode-info-init); GENERATED AUTOMATICALLY BY opcode-gen + set(NOP); + set(MOVE); + set(MOVE_FROM16); + set(MOVE_16); + set(MOVE_WIDE); + set(MOVE_WIDE_FROM16); + set(MOVE_WIDE_16); + set(MOVE_OBJECT); + set(MOVE_OBJECT_FROM16); + set(MOVE_OBJECT_16); + set(MOVE_RESULT); + set(MOVE_RESULT_WIDE); + set(MOVE_RESULT_OBJECT); + set(MOVE_EXCEPTION); + set(RETURN_VOID); + set(RETURN); + set(RETURN_WIDE); + set(RETURN_OBJECT); + set(CONST_4); + set(CONST_16); + set(CONST); + set(CONST_HIGH16); + set(CONST_WIDE_16); + set(CONST_WIDE_32); + set(CONST_WIDE); + set(CONST_WIDE_HIGH16); + set(CONST_STRING); + set(CONST_STRING_JUMBO); + set(CONST_CLASS); + set(MONITOR_ENTER); + set(MONITOR_EXIT); + set(CHECK_CAST); + set(INSTANCE_OF); + set(ARRAY_LENGTH); + set(NEW_INSTANCE); + set(NEW_ARRAY); + set(FILLED_NEW_ARRAY); + set(FILLED_NEW_ARRAY_RANGE); + set(FILL_ARRAY_DATA); + set(THROW); + set(GOTO); + set(GOTO_16); + set(GOTO_32); + set(PACKED_SWITCH); + set(SPARSE_SWITCH); + set(CMPL_FLOAT); + set(CMPG_FLOAT); + set(CMPL_DOUBLE); + set(CMPG_DOUBLE); + set(CMP_LONG); + set(IF_EQ); + set(IF_NE); + set(IF_LT); + set(IF_GE); + set(IF_GT); + set(IF_LE); + set(IF_EQZ); + set(IF_NEZ); + set(IF_LTZ); + set(IF_GEZ); + set(IF_GTZ); + set(IF_LEZ); + set(AGET); + set(AGET_WIDE); + set(AGET_OBJECT); + set(AGET_BOOLEAN); + set(AGET_BYTE); + set(AGET_CHAR); + set(AGET_SHORT); + set(APUT); + set(APUT_WIDE); + set(APUT_OBJECT); + set(APUT_BOOLEAN); + set(APUT_BYTE); + set(APUT_CHAR); + set(APUT_SHORT); + set(IGET); + set(IGET_WIDE); + set(IGET_OBJECT); + set(IGET_BOOLEAN); + set(IGET_BYTE); + set(IGET_CHAR); + set(IGET_SHORT); + set(IPUT); + set(IPUT_WIDE); + set(IPUT_OBJECT); + set(IPUT_BOOLEAN); + set(IPUT_BYTE); + set(IPUT_CHAR); + set(IPUT_SHORT); + set(SGET); + set(SGET_WIDE); + set(SGET_OBJECT); + set(SGET_BOOLEAN); + set(SGET_BYTE); + set(SGET_CHAR); + set(SGET_SHORT); + set(SPUT); + set(SPUT_WIDE); + set(SPUT_OBJECT); + set(SPUT_BOOLEAN); + set(SPUT_BYTE); + set(SPUT_CHAR); + set(SPUT_SHORT); + set(INVOKE_VIRTUAL); + set(INVOKE_SUPER); + set(INVOKE_DIRECT); + set(INVOKE_STATIC); + set(INVOKE_INTERFACE); + set(INVOKE_VIRTUAL_RANGE); + set(INVOKE_SUPER_RANGE); + set(INVOKE_DIRECT_RANGE); + set(INVOKE_STATIC_RANGE); + set(INVOKE_INTERFACE_RANGE); + set(NEG_INT); + set(NOT_INT); + set(NEG_LONG); + set(NOT_LONG); + set(NEG_FLOAT); + set(NEG_DOUBLE); + set(INT_TO_LONG); + set(INT_TO_FLOAT); + set(INT_TO_DOUBLE); + set(LONG_TO_INT); + set(LONG_TO_FLOAT); + set(LONG_TO_DOUBLE); + set(FLOAT_TO_INT); + set(FLOAT_TO_LONG); + set(FLOAT_TO_DOUBLE); + set(DOUBLE_TO_INT); + set(DOUBLE_TO_LONG); + set(DOUBLE_TO_FLOAT); + set(INT_TO_BYTE); + set(INT_TO_CHAR); + set(INT_TO_SHORT); + set(ADD_INT); + set(SUB_INT); + set(MUL_INT); + set(DIV_INT); + set(REM_INT); + set(AND_INT); + set(OR_INT); + set(XOR_INT); + set(SHL_INT); + set(SHR_INT); + set(USHR_INT); + set(ADD_LONG); + set(SUB_LONG); + set(MUL_LONG); + set(DIV_LONG); + set(REM_LONG); + set(AND_LONG); + set(OR_LONG); + set(XOR_LONG); + set(SHL_LONG); + set(SHR_LONG); + set(USHR_LONG); + set(ADD_FLOAT); + set(SUB_FLOAT); + set(MUL_FLOAT); + set(DIV_FLOAT); + set(REM_FLOAT); + set(ADD_DOUBLE); + set(SUB_DOUBLE); + set(MUL_DOUBLE); + set(DIV_DOUBLE); + set(REM_DOUBLE); + set(ADD_INT_2ADDR); + set(SUB_INT_2ADDR); + set(MUL_INT_2ADDR); + set(DIV_INT_2ADDR); + set(REM_INT_2ADDR); + set(AND_INT_2ADDR); + set(OR_INT_2ADDR); + set(XOR_INT_2ADDR); + set(SHL_INT_2ADDR); + set(SHR_INT_2ADDR); + set(USHR_INT_2ADDR); + set(ADD_LONG_2ADDR); + set(SUB_LONG_2ADDR); + set(MUL_LONG_2ADDR); + set(DIV_LONG_2ADDR); + set(REM_LONG_2ADDR); + set(AND_LONG_2ADDR); + set(OR_LONG_2ADDR); + set(XOR_LONG_2ADDR); + set(SHL_LONG_2ADDR); + set(SHR_LONG_2ADDR); + set(USHR_LONG_2ADDR); + set(ADD_FLOAT_2ADDR); + set(SUB_FLOAT_2ADDR); + set(MUL_FLOAT_2ADDR); + set(DIV_FLOAT_2ADDR); + set(REM_FLOAT_2ADDR); + set(ADD_DOUBLE_2ADDR); + set(SUB_DOUBLE_2ADDR); + set(MUL_DOUBLE_2ADDR); + set(DIV_DOUBLE_2ADDR); + set(REM_DOUBLE_2ADDR); + set(ADD_INT_LIT16); + set(RSUB_INT); + set(MUL_INT_LIT16); + set(DIV_INT_LIT16); + set(REM_INT_LIT16); + set(AND_INT_LIT16); + set(OR_INT_LIT16); + set(XOR_INT_LIT16); + set(ADD_INT_LIT8); + set(RSUB_INT_LIT8); + set(MUL_INT_LIT8); + set(DIV_INT_LIT8); + set(REM_INT_LIT8); + set(AND_INT_LIT8); + set(OR_INT_LIT8); + set(XOR_INT_LIT8); + set(SHL_INT_LIT8); + set(SHR_INT_LIT8); + set(USHR_INT_LIT8); + // END(opcode-info-init) + } + + /** + * This class is uninstantiable. + */ + private OpcodeInfo() { + // This space intentionally left blank. + } + + /** + * Gets the {@link @Info} for the given opcode value. + * + * @param opcode {@code Opcodes.MIN_VALUE..Opcodes.MAX_VALUE;} the + * opcode value + * @return non-null; the associated opcode information instance + */ + public static Info get(int opcode) { + int idx = opcode - Opcodes.MIN_VALUE; + + try { + Info result = INFO[idx]; + if (result != null) { + return result; + } + } catch (ArrayIndexOutOfBoundsException ex) { + // Fall through. + } + + throw new IllegalArgumentException("bogus opcode: " + + Hex.u2or4(opcode)); + } + + /** + * Gets the name of the given opcode. + */ + public static String getName(int opcode) { + return get(opcode).getName(); + } + + /** + * Gets the format (an {@link InstructionCodec}) for the given opcode + * value. + */ + public static InstructionCodec getFormat(int opcode) { + return get(opcode).getFormat(); + } + + /** + * Gets the {@link IndexType} for the given opcode value. + */ + public static IndexType getIndexType(int opcode) { + return get(opcode).getIndexType(); + } + + /** + * Puts the given opcode into the table of all ops. + * + * @param opcode non-null; the opcode + */ + private static void set(Info opcode) { + int idx = opcode.getOpcode() - Opcodes.MIN_VALUE; + INFO[idx] = opcode; + } + + /** + * Information about an opcode. + */ + public static class Info { + private final int opcode; + private final String name; + private final InstructionCodec format; + private final IndexType indexType; + + public Info(int opcode, String name, InstructionCodec format, + IndexType indexType) { + this.opcode = opcode; + this.name = name; + this.format = format; + this.indexType = indexType; + } + + public int getOpcode() { + return opcode; + } + + public String getName() { + return name; + } + + public InstructionCodec getFormat() { + return format; + } + + public IndexType getIndexType() { + return indexType; + } + } +} diff --git a/dx/src/com/android/jack/dx/io/Opcodes.java b/dx/src/com/android/jack/dx/io/Opcodes.java new file mode 100644 index 00000000..77b5d8e9 --- /dev/null +++ b/dx/src/com/android/jack/dx/io/Opcodes.java @@ -0,0 +1,346 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.io; + +/** + * All the Dalvik opcode value constants. See the related spec + * document for the meaning and instruction format of each opcode. + */ +public final class Opcodes { + /** + * pseudo-opcode used for nonstandard format payload "instructions". TODO: + * Retire this concept, and start treating the payload instructions + * more like the rest. + */ + public static final int SPECIAL_FORMAT = -1; + + /** + * pseudo-opcode used to indicate there is no next opcode; used + * in opcode chaining lists + */ + public static final int NO_NEXT = -1; + + /** minimum valid opcode value */ + public static final int MIN_VALUE = -1; + + /** maximum valid opcode value */ + public static final int MAX_VALUE = 0xffff; + + // BEGIN(opcodes); GENERATED AUTOMATICALLY BY opcode-gen + public static final int NOP = 0x00; + public static final int MOVE = 0x01; + public static final int MOVE_FROM16 = 0x02; + public static final int MOVE_16 = 0x03; + public static final int MOVE_WIDE = 0x04; + public static final int MOVE_WIDE_FROM16 = 0x05; + public static final int MOVE_WIDE_16 = 0x06; + public static final int MOVE_OBJECT = 0x07; + public static final int MOVE_OBJECT_FROM16 = 0x08; + public static final int MOVE_OBJECT_16 = 0x09; + public static final int MOVE_RESULT = 0x0a; + public static final int MOVE_RESULT_WIDE = 0x0b; + public static final int MOVE_RESULT_OBJECT = 0x0c; + public static final int MOVE_EXCEPTION = 0x0d; + public static final int RETURN_VOID = 0x0e; + public static final int RETURN = 0x0f; + public static final int RETURN_WIDE = 0x10; + public static final int RETURN_OBJECT = 0x11; + public static final int CONST_4 = 0x12; + public static final int CONST_16 = 0x13; + public static final int CONST = 0x14; + public static final int CONST_HIGH16 = 0x15; + public static final int CONST_WIDE_16 = 0x16; + public static final int CONST_WIDE_32 = 0x17; + public static final int CONST_WIDE = 0x18; + public static final int CONST_WIDE_HIGH16 = 0x19; + public static final int CONST_STRING = 0x1a; + public static final int CONST_STRING_JUMBO = 0x1b; + public static final int CONST_CLASS = 0x1c; + public static final int MONITOR_ENTER = 0x1d; + public static final int MONITOR_EXIT = 0x1e; + public static final int CHECK_CAST = 0x1f; + public static final int INSTANCE_OF = 0x20; + public static final int ARRAY_LENGTH = 0x21; + public static final int NEW_INSTANCE = 0x22; + public static final int NEW_ARRAY = 0x23; + public static final int FILLED_NEW_ARRAY = 0x24; + public static final int FILLED_NEW_ARRAY_RANGE = 0x25; + public static final int FILL_ARRAY_DATA = 0x26; + public static final int THROW = 0x27; + public static final int GOTO = 0x28; + public static final int GOTO_16 = 0x29; + public static final int GOTO_32 = 0x2a; + public static final int PACKED_SWITCH = 0x2b; + public static final int SPARSE_SWITCH = 0x2c; + public static final int CMPL_FLOAT = 0x2d; + public static final int CMPG_FLOAT = 0x2e; + public static final int CMPL_DOUBLE = 0x2f; + public static final int CMPG_DOUBLE = 0x30; + public static final int CMP_LONG = 0x31; + public static final int IF_EQ = 0x32; + public static final int IF_NE = 0x33; + public static final int IF_LT = 0x34; + public static final int IF_GE = 0x35; + public static final int IF_GT = 0x36; + public static final int IF_LE = 0x37; + public static final int IF_EQZ = 0x38; + public static final int IF_NEZ = 0x39; + public static final int IF_LTZ = 0x3a; + public static final int IF_GEZ = 0x3b; + public static final int IF_GTZ = 0x3c; + public static final int IF_LEZ = 0x3d; + public static final int AGET = 0x44; + public static final int AGET_WIDE = 0x45; + public static final int AGET_OBJECT = 0x46; + public static final int AGET_BOOLEAN = 0x47; + public static final int AGET_BYTE = 0x48; + public static final int AGET_CHAR = 0x49; + public static final int AGET_SHORT = 0x4a; + public static final int APUT = 0x4b; + public static final int APUT_WIDE = 0x4c; + public static final int APUT_OBJECT = 0x4d; + public static final int APUT_BOOLEAN = 0x4e; + public static final int APUT_BYTE = 0x4f; + public static final int APUT_CHAR = 0x50; + public static final int APUT_SHORT = 0x51; + public static final int IGET = 0x52; + public static final int IGET_WIDE = 0x53; + public static final int IGET_OBJECT = 0x54; + public static final int IGET_BOOLEAN = 0x55; + public static final int IGET_BYTE = 0x56; + public static final int IGET_CHAR = 0x57; + public static final int IGET_SHORT = 0x58; + public static final int IPUT = 0x59; + public static final int IPUT_WIDE = 0x5a; + public static final int IPUT_OBJECT = 0x5b; + public static final int IPUT_BOOLEAN = 0x5c; + public static final int IPUT_BYTE = 0x5d; + public static final int IPUT_CHAR = 0x5e; + public static final int IPUT_SHORT = 0x5f; + public static final int SGET = 0x60; + public static final int SGET_WIDE = 0x61; + public static final int SGET_OBJECT = 0x62; + public static final int SGET_BOOLEAN = 0x63; + public static final int SGET_BYTE = 0x64; + public static final int SGET_CHAR = 0x65; + public static final int SGET_SHORT = 0x66; + public static final int SPUT = 0x67; + public static final int SPUT_WIDE = 0x68; + public static final int SPUT_OBJECT = 0x69; + public static final int SPUT_BOOLEAN = 0x6a; + public static final int SPUT_BYTE = 0x6b; + public static final int SPUT_CHAR = 0x6c; + public static final int SPUT_SHORT = 0x6d; + public static final int INVOKE_VIRTUAL = 0x6e; + public static final int INVOKE_SUPER = 0x6f; + public static final int INVOKE_DIRECT = 0x70; + public static final int INVOKE_STATIC = 0x71; + public static final int INVOKE_INTERFACE = 0x72; + public static final int INVOKE_VIRTUAL_RANGE = 0x74; + public static final int INVOKE_SUPER_RANGE = 0x75; + public static final int INVOKE_DIRECT_RANGE = 0x76; + public static final int INVOKE_STATIC_RANGE = 0x77; + public static final int INVOKE_INTERFACE_RANGE = 0x78; + public static final int NEG_INT = 0x7b; + public static final int NOT_INT = 0x7c; + public static final int NEG_LONG = 0x7d; + public static final int NOT_LONG = 0x7e; + public static final int NEG_FLOAT = 0x7f; + public static final int NEG_DOUBLE = 0x80; + public static final int INT_TO_LONG = 0x81; + public static final int INT_TO_FLOAT = 0x82; + public static final int INT_TO_DOUBLE = 0x83; + public static final int LONG_TO_INT = 0x84; + public static final int LONG_TO_FLOAT = 0x85; + public static final int LONG_TO_DOUBLE = 0x86; + public static final int FLOAT_TO_INT = 0x87; + public static final int FLOAT_TO_LONG = 0x88; + public static final int FLOAT_TO_DOUBLE = 0x89; + public static final int DOUBLE_TO_INT = 0x8a; + public static final int DOUBLE_TO_LONG = 0x8b; + public static final int DOUBLE_TO_FLOAT = 0x8c; + public static final int INT_TO_BYTE = 0x8d; + public static final int INT_TO_CHAR = 0x8e; + public static final int INT_TO_SHORT = 0x8f; + public static final int ADD_INT = 0x90; + public static final int SUB_INT = 0x91; + public static final int MUL_INT = 0x92; + public static final int DIV_INT = 0x93; + public static final int REM_INT = 0x94; + public static final int AND_INT = 0x95; + public static final int OR_INT = 0x96; + public static final int XOR_INT = 0x97; + public static final int SHL_INT = 0x98; + public static final int SHR_INT = 0x99; + public static final int USHR_INT = 0x9a; + public static final int ADD_LONG = 0x9b; + public static final int SUB_LONG = 0x9c; + public static final int MUL_LONG = 0x9d; + public static final int DIV_LONG = 0x9e; + public static final int REM_LONG = 0x9f; + public static final int AND_LONG = 0xa0; + public static final int OR_LONG = 0xa1; + public static final int XOR_LONG = 0xa2; + public static final int SHL_LONG = 0xa3; + public static final int SHR_LONG = 0xa4; + public static final int USHR_LONG = 0xa5; + public static final int ADD_FLOAT = 0xa6; + public static final int SUB_FLOAT = 0xa7; + public static final int MUL_FLOAT = 0xa8; + public static final int DIV_FLOAT = 0xa9; + public static final int REM_FLOAT = 0xaa; + public static final int ADD_DOUBLE = 0xab; + public static final int SUB_DOUBLE = 0xac; + public static final int MUL_DOUBLE = 0xad; + public static final int DIV_DOUBLE = 0xae; + public static final int REM_DOUBLE = 0xaf; + public static final int ADD_INT_2ADDR = 0xb0; + public static final int SUB_INT_2ADDR = 0xb1; + public static final int MUL_INT_2ADDR = 0xb2; + public static final int DIV_INT_2ADDR = 0xb3; + public static final int REM_INT_2ADDR = 0xb4; + public static final int AND_INT_2ADDR = 0xb5; + public static final int OR_INT_2ADDR = 0xb6; + public static final int XOR_INT_2ADDR = 0xb7; + public static final int SHL_INT_2ADDR = 0xb8; + public static final int SHR_INT_2ADDR = 0xb9; + public static final int USHR_INT_2ADDR = 0xba; + public static final int ADD_LONG_2ADDR = 0xbb; + public static final int SUB_LONG_2ADDR = 0xbc; + public static final int MUL_LONG_2ADDR = 0xbd; + public static final int DIV_LONG_2ADDR = 0xbe; + public static final int REM_LONG_2ADDR = 0xbf; + public static final int AND_LONG_2ADDR = 0xc0; + public static final int OR_LONG_2ADDR = 0xc1; + public static final int XOR_LONG_2ADDR = 0xc2; + public static final int SHL_LONG_2ADDR = 0xc3; + public static final int SHR_LONG_2ADDR = 0xc4; + public static final int USHR_LONG_2ADDR = 0xc5; + public static final int ADD_FLOAT_2ADDR = 0xc6; + public static final int SUB_FLOAT_2ADDR = 0xc7; + public static final int MUL_FLOAT_2ADDR = 0xc8; + public static final int DIV_FLOAT_2ADDR = 0xc9; + public static final int REM_FLOAT_2ADDR = 0xca; + public static final int ADD_DOUBLE_2ADDR = 0xcb; + public static final int SUB_DOUBLE_2ADDR = 0xcc; + public static final int MUL_DOUBLE_2ADDR = 0xcd; + public static final int DIV_DOUBLE_2ADDR = 0xce; + public static final int REM_DOUBLE_2ADDR = 0xcf; + public static final int ADD_INT_LIT16 = 0xd0; + public static final int RSUB_INT = 0xd1; + public static final int MUL_INT_LIT16 = 0xd2; + public static final int DIV_INT_LIT16 = 0xd3; + public static final int REM_INT_LIT16 = 0xd4; + public static final int AND_INT_LIT16 = 0xd5; + public static final int OR_INT_LIT16 = 0xd6; + public static final int XOR_INT_LIT16 = 0xd7; + public static final int ADD_INT_LIT8 = 0xd8; + public static final int RSUB_INT_LIT8 = 0xd9; + public static final int MUL_INT_LIT8 = 0xda; + public static final int DIV_INT_LIT8 = 0xdb; + public static final int REM_INT_LIT8 = 0xdc; + public static final int AND_INT_LIT8 = 0xdd; + public static final int OR_INT_LIT8 = 0xde; + public static final int XOR_INT_LIT8 = 0xdf; + public static final int SHL_INT_LIT8 = 0xe0; + public static final int SHR_INT_LIT8 = 0xe1; + public static final int USHR_INT_LIT8 = 0xe2; + // END(opcodes) + + // TODO: Generate these payload opcodes with opcode-gen. + + /** + * special pseudo-opcode value for packed-switch data payload + * instructions + */ + public static final int PACKED_SWITCH_PAYLOAD = 0x100; + + /** special pseudo-opcode value for packed-switch data payload + * instructions + */ + public static final int SPARSE_SWITCH_PAYLOAD = 0x200; + + /** special pseudo-opcode value for fill-array-data data payload + * instructions + */ + public static final int FILL_ARRAY_DATA_PAYLOAD = 0x300; + + /** + * This class is uninstantiable. + */ + private Opcodes() { + // This space intentionally left blank. + } + + /** + * Determines if the given opcode has the right "shape" to be + * valid. This includes the range {@code 0x01..0xfe}, the range + * {@code 0x00ff..0xffff} where the low-order byte is either + * {@code 0} or {@code 0xff}, and the special opcode values {@code + * SPECIAL_FORMAT} and {@code NO_NEXT}. Note that not all of the + * opcode values that pass this test are in fact used. This method + * is meant to perform a quick check to reject blatantly wrong + * values (e.g. when validating arguments). + * + * @param opcode the opcode value + * @return {@code true} iff the value has the right "shape" to be + * possibly valid + */ + public static boolean isValidShape(int opcode) { + /* + * Note: This method bakes in knowledge that all opcodes are + * one of the forms: + * + * * single byte in range 0x01..0xfe -- normal opcodes + * * (byteValue << 8) -- nop and data payload opcodes + * * ((byteValue << 8) | 0xff) -- 16-bit extended opcodes + * * SPECIAL_FORMAT or NO_NEXT -- pseudo-opcodes + */ + + // Note: SPECIAL_FORMAT == NO_NEXT. + if (opcode < SPECIAL_FORMAT) { + return false; + } else if (opcode == SPECIAL_FORMAT) { + return true; + } + + int lowByte = opcode & 0xff; + if ((lowByte == 0) || (lowByte == 0xff)) { + return true; + } + + return (opcode & 0xff00) == 0; + } + + /** + * Gets the opcode out of an opcode unit, the latter of which may also + * include one or more argument values. + * + * @param opcodeUnit the opcode-containing code unit + * @return the extracted opcode + */ + public static int extractOpcodeFromUnit(int opcodeUnit) { + /* + * Note: This method bakes in knowledge that all opcodes are + * either single-byte or of the forms (byteValue << 8) or + * ((byteValue << 8) | 0xff). + */ + + int lowByte = opcodeUnit & 0xff; + return ((lowByte == 0) || (lowByte == 0xff)) ? opcodeUnit : lowByte; + } +} diff --git a/dx/src/com/android/jack/dx/io/ProtoId.java b/dx/src/com/android/jack/dx/io/ProtoId.java new file mode 100644 index 00000000..c1bb61ae --- /dev/null +++ b/dx/src/com/android/jack/dx/io/ProtoId.java @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.io; + +import com.android.jack.dx.util.Unsigned; + +public final class ProtoId implements Comparable { + private final DexBuffer buffer; + private final int shortyIndex; + private final int returnTypeIndex; + private final int parametersOffset; + + public ProtoId(DexBuffer buffer, int shortyIndex, int returnTypeIndex, int parametersOffset) { + this.buffer = buffer; + this.shortyIndex = shortyIndex; + this.returnTypeIndex = returnTypeIndex; + this.parametersOffset = parametersOffset; + } + + public int compareTo(ProtoId other) { + if (returnTypeIndex != other.returnTypeIndex) { + return Unsigned.compare(returnTypeIndex, other.returnTypeIndex); + } + return Unsigned.compare(parametersOffset, other.parametersOffset); + } + + public int getShortyIndex() { + return shortyIndex; + } + + public int getReturnTypeIndex() { + return returnTypeIndex; + } + + public int getParametersOffset() { + return parametersOffset; + } + + public void writeTo(DexBuffer.Section out) { + out.writeInt(shortyIndex); + out.writeInt(returnTypeIndex); + out.writeInt(parametersOffset); + } + + @Override public String toString() { + if (buffer == null) { + return shortyIndex + " " + returnTypeIndex + " " + parametersOffset; + } + + return buffer.strings().get(shortyIndex) + + ": " + buffer.typeNames().get(returnTypeIndex) + + " " + buffer.readTypeList(parametersOffset); + } +} diff --git a/dx/src/com/android/jack/dx/io/instructions/AddressMap.java b/dx/src/com/android/jack/dx/io/instructions/AddressMap.java new file mode 100644 index 00000000..624870b1 --- /dev/null +++ b/dx/src/com/android/jack/dx/io/instructions/AddressMap.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.io.instructions; + +import java.io.EOFException; +import java.util.HashMap; + +/** + * Map from addresses to addresses, where addresses are all + * {@code int}s. + */ +public final class AddressMap { + /** underlying map. TODO: This might be too inefficient. */ + private final HashMap map; + + /** + * Constructs an instance. + */ + public AddressMap() { + map = new HashMap(); + } + + /** + * Gets the value address corresponding to the given key address. Returns + * {@code -1} if there is no mapping. + */ + public int get(int keyAddress) { + Integer value = map.get(keyAddress); + return (value == null) ? -1 : value; + } + + /** + * Sets the value address associated with the given key address. + */ + public void put(int keyAddress, int valueAddress) { + map.put(keyAddress, valueAddress); + } +} diff --git a/dx/src/com/android/jack/dx/io/instructions/BaseCodeCursor.java b/dx/src/com/android/jack/dx/io/instructions/BaseCodeCursor.java new file mode 100644 index 00000000..8d82cae4 --- /dev/null +++ b/dx/src/com/android/jack/dx/io/instructions/BaseCodeCursor.java @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.io.instructions; + +import java.io.EOFException; + +/** + * Base implementation of {@link CodeCursor}. + */ +public abstract class BaseCodeCursor implements CodeCursor { + /** base address map */ + private final AddressMap baseAddressMap; + + /** next index within {@link #array} to read from or write to */ + private int cursor; + + /** + * Constructs an instance. + */ + public BaseCodeCursor() { + this.baseAddressMap = new AddressMap(); + this.cursor = 0; + } + + /** @inheritDoc */ + public final int cursor() { + return cursor; + } + + /** @inheritDoc */ + public final int baseAddressForCursor() { + int mapped = baseAddressMap.get(cursor); + return (mapped >= 0) ? mapped : cursor; + } + + /** @inheritDoc */ + public final void setBaseAddress(int targetAddress, int baseAddress) { + baseAddressMap.put(targetAddress, baseAddress); + } + + /** + * Advance the cursor by the indicated amount. + */ + protected final void advance(int amount) { + cursor += amount; + } +} diff --git a/dx/src/com/android/jack/dx/io/instructions/CodeCursor.java b/dx/src/com/android/jack/dx/io/instructions/CodeCursor.java new file mode 100644 index 00000000..b9dbbbe1 --- /dev/null +++ b/dx/src/com/android/jack/dx/io/instructions/CodeCursor.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.io.instructions; + +/** + * Cursor over code units, for reading or writing out Dalvik bytecode. + */ +public interface CodeCursor { + /** + * Gets the cursor. The cursor is the offset in code units from + * the start of the input of the next code unit to be read or + * written, where the input generally consists of the code for a + * single method. + */ + public int cursor(); + + /** + * Gets the base address associated with the current cursor. This + * differs from the cursor value when explicitly set (by {@link + * #setBaseAddress). This is used, in particular, to convey base + * addresses to switch data payload instructions, whose relative + * addresses are relative to the address of a dependant switch + * instruction. + */ + public int baseAddressForCursor(); + + /** + * Sets the base address for the given target address to be as indicated. + * + * @see #baseAddressForCursor + */ + public void setBaseAddress(int targetAddress, int baseAddress); +} diff --git a/dx/src/com/android/jack/dx/io/instructions/CodeInput.java b/dx/src/com/android/jack/dx/io/instructions/CodeInput.java new file mode 100644 index 00000000..9327dc48 --- /dev/null +++ b/dx/src/com/android/jack/dx/io/instructions/CodeInput.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.io.instructions; + +import java.io.EOFException; + +/** + * Input stream of code units, for reading in Dalvik bytecode. + */ +public interface CodeInput extends CodeCursor { + /** + * Returns whether there are any more code units to read. This + * is analogous to {@code hasNext()} on an interator. + */ + public boolean hasMore(); + + /** + * Reads a code unit. + */ + public int read() throws EOFException; + + /** + * Reads two code units, treating them as a little-endian {@code int}. + */ + public int readInt() throws EOFException; + + /** + * Reads four code units, treating them as a little-endian {@code long}. + */ + public long readLong() throws EOFException; +} diff --git a/dx/src/com/android/jack/dx/io/instructions/CodeOutput.java b/dx/src/com/android/jack/dx/io/instructions/CodeOutput.java new file mode 100644 index 00000000..76f4d146 --- /dev/null +++ b/dx/src/com/android/jack/dx/io/instructions/CodeOutput.java @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.io.instructions; + +/** + * Output stream of code units, for writing out Dalvik bytecode. + */ +public interface CodeOutput extends CodeCursor { + /** + * Writes a code unit. + */ + public void write(short codeUnit); + + /** + * Writes two code units. + */ + public void write(short u0, short u1); + + /** + * Writes three code units. + */ + public void write(short u0, short u1, short u2); + + /** + * Writes four code units. + */ + public void write(short u0, short u1, short u2, short u3); + + /** + * Writes five code units. + */ + public void write(short u0, short u1, short u2, short u3, short u4); + + /** + * Writes an {@code int}, little-endian. + */ + public void writeInt(int value); + + /** + * Writes a {@code long}, little-endian. + */ + public void writeLong(long value); + + /** + * Writes the contents of the given array. + */ + public void write(byte[] data); + + /** + * Writes the contents of the given array. + */ + public void write(short[] data); + + /** + * Writes the contents of the given array. + */ + public void write(int[] data); + + /** + * Writes the contents of the given array. + */ + public void write(long[] data); +} diff --git a/dx/src/com/android/jack/dx/io/instructions/DecodedInstruction.java b/dx/src/com/android/jack/dx/io/instructions/DecodedInstruction.java new file mode 100644 index 00000000..efcdec67 --- /dev/null +++ b/dx/src/com/android/jack/dx/io/instructions/DecodedInstruction.java @@ -0,0 +1,479 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.io.instructions; + +import com.android.jack.dx.io.IndexType; +import com.android.jack.dx.io.OpcodeInfo; +import com.android.jack.dx.io.Opcodes; +import com.android.jack.dx.util.DexException; +import com.android.jack.dx.util.Hex; + +import java.io.EOFException; + +/** + * A decoded Dalvik instruction. This consists of a format codec, a + * numeric opcode, an optional index type, and any additional + * arguments of the instruction. The additional arguments (if any) are + * represented as uninterpreted data. + * + *

Note: The names of the arguments are not meant to + * match the names given in the Dalvik instruction format + * specification, specification which just names fields (somewhat) + * arbitrarily alphabetically from A. In this class, non-register + * fields are given descriptive names and register fields are + * consistently named alphabetically.

+ */ +public abstract class DecodedInstruction { + /** non-null; instruction format / codec */ + private final InstructionCodec format; + + /** opcode number */ + private final int opcode; + + /** constant index argument */ + private final int index; + + /** null-ok; index type */ + private final IndexType indexType; + + /** + * target address argument. This is an absolute address, not just + * a signed offset. Note: The address is unsigned, even + * though it is stored in an {@code int}. + */ + private final int target; + + /** + * literal value argument; also used for special verification error + * constants (format 20bc) as well as should-be-zero values + * (formats 10x, 20t, 30t, and 32x) + */ + private final long literal; + + /** + * Decodes an instruction from the given input source. + */ + public static DecodedInstruction decode(CodeInput in) throws EOFException { + int opcodeUnit = in.read(); + int opcode = Opcodes.extractOpcodeFromUnit(opcodeUnit); + InstructionCodec format = OpcodeInfo.getFormat(opcode); + + return format.decode(opcodeUnit, in); + } + + /** + * Decodes an array of instructions. The result has non-null + * elements at each offset that represents the start of an + * instruction. + */ + public static DecodedInstruction[] decodeAll(short[] encodedInstructions) { + int size = encodedInstructions.length; + DecodedInstruction[] decoded = new DecodedInstruction[size]; + ShortArrayCodeInput in = new ShortArrayCodeInput(encodedInstructions); + + try { + while (in.hasMore()) { + decoded[in.cursor()] = DecodedInstruction.decode(in); + } + } catch (EOFException ex) { + throw new DexException(ex); + } + + return decoded; + } + + /** + * Constructs an instance. + */ + public DecodedInstruction(InstructionCodec format, int opcode, + int index, IndexType indexType, int target, long literal) { + if (format == null) { + throw new NullPointerException("format == null"); + } + + if (!Opcodes.isValidShape(opcode)) { + throw new IllegalArgumentException("invalid opcode"); + } + + this.format = format; + this.opcode = opcode; + this.index = index; + this.indexType = indexType; + this.target = target; + this.literal = literal; + } + + public final InstructionCodec getFormat() { + return format; + } + + public final int getOpcode() { + return opcode; + } + + /** + * Gets the opcode, as a code unit. + */ + public final short getOpcodeUnit() { + return (short) opcode; + } + + public final int getIndex() { + return index; + } + + /** + * Gets the index, as a code unit. + */ + public final short getIndexUnit() { + return (short) index; + } + + public final IndexType getIndexType() { + return indexType; + } + + /** + * Gets the raw target. + */ + public final int getTarget() { + return target; + } + + /** + * Gets the target as a relative offset from the given address. + */ + public final int getTarget(int baseAddress) { + return target - baseAddress; + } + + /** + * Gets the target as a relative offset from the given base + * address, as a code unit. This will throw if the value is out of + * the range of a signed code unit. + */ + public final short getTargetUnit(int baseAddress) { + int relativeTarget = getTarget(baseAddress); + + if (relativeTarget != (short) relativeTarget) { + throw new DexException("Target out of range: " + + Hex.s4(relativeTarget)); + } + + return (short) relativeTarget; + } + + /** + * Gets the target as a relative offset from the given base + * address, masked to be a byte in size. This will throw if the + * value is out of the range of a signed byte. + */ + public final int getTargetByte(int baseAddress) { + int relativeTarget = getTarget(baseAddress); + + if (relativeTarget != (byte) relativeTarget) { + throw new DexException("Target out of range: " + + Hex.s4(relativeTarget)); + } + + return relativeTarget & 0xff; + } + + public final long getLiteral() { + return literal; + } + + /** + * Gets the literal value, masked to be an int in size. This will + * throw if the value is out of the range of a signed int. + */ + public final int getLiteralInt() { + if (literal != (int) literal) { + throw new DexException("Literal out of range: " + Hex.u8(literal)); + } + + return (int) literal; + } + + /** + * Gets the literal value, as a code unit. This will throw if the + * value is out of the range of a signed code unit. + */ + public final short getLiteralUnit() { + if (literal != (short) literal) { + throw new DexException("Literal out of range: " + Hex.u8(literal)); + } + + return (short) literal; + } + + /** + * Gets the literal value, masked to be a byte in size. This will + * throw if the value is out of the range of a signed byte. + */ + public final int getLiteralByte() { + if (literal != (byte) literal) { + throw new DexException("Literal out of range: " + Hex.u8(literal)); + } + + return (int) literal & 0xff; + } + + /** + * Gets the literal value, masked to be a nibble in size. This + * will throw if the value is out of the range of a signed nibble. + */ + public final int getLiteralNibble() { + if ((literal < -8) || (literal > 7)) { + throw new DexException("Literal out of range: " + Hex.u8(literal)); + } + + return (int) literal & 0xf; + } + + public abstract int getRegisterCount(); + + public int getA() { + return 0; + } + + public int getB() { + return 0; + } + + public int getC() { + return 0; + } + + public int getD() { + return 0; + } + + public int getE() { + return 0; + } + + /** + * Gets the register count, as a code unit. This will throw if the + * value is out of the range of an unsigned code unit. + */ + public final short getRegisterCountUnit() { + int registerCount = getRegisterCount(); + + if ((registerCount & ~0xffff) != 0) { + throw new DexException("Register count out of range: " + + Hex.u8(registerCount)); + } + + return (short) registerCount; + } + + /** + * Gets the A register number, as a code unit. This will throw if the + * value is out of the range of an unsigned code unit. + */ + public final short getAUnit() { + int a = getA(); + + if ((a & ~0xffff) != 0) { + throw new DexException("Register A out of range: " + Hex.u8(a)); + } + + return (short) a; + } + + /** + * Gets the A register number, as a byte. This will throw if the + * value is out of the range of an unsigned byte. + */ + public final short getAByte() { + int a = getA(); + + if ((a & ~0xff) != 0) { + throw new DexException("Register A out of range: " + Hex.u8(a)); + } + + return (short) a; + } + + /** + * Gets the A register number, as a nibble. This will throw if the + * value is out of the range of an unsigned nibble. + */ + public final short getANibble() { + int a = getA(); + + if ((a & ~0xf) != 0) { + throw new DexException("Register A out of range: " + Hex.u8(a)); + } + + return (short) a; + } + + /** + * Gets the B register number, as a code unit. This will throw if the + * value is out of the range of an unsigned code unit. + */ + public final short getBUnit() { + int b = getB(); + + if ((b & ~0xffff) != 0) { + throw new DexException("Register B out of range: " + Hex.u8(b)); + } + + return (short) b; + } + + /** + * Gets the B register number, as a byte. This will throw if the + * value is out of the range of an unsigned byte. + */ + public final short getBByte() { + int b = getB(); + + if ((b & ~0xff) != 0) { + throw new DexException("Register B out of range: " + Hex.u8(b)); + } + + return (short) b; + } + + /** + * Gets the B register number, as a nibble. This will throw if the + * value is out of the range of an unsigned nibble. + */ + public final short getBNibble() { + int b = getB(); + + if ((b & ~0xf) != 0) { + throw new DexException("Register B out of range: " + Hex.u8(b)); + } + + return (short) b; + } + + /** + * Gets the C register number, as a code unit. This will throw if the + * value is out of the range of an unsigned code unit. + */ + public final short getCUnit() { + int c = getC(); + + if ((c & ~0xffff) != 0) { + throw new DexException("Register C out of range: " + Hex.u8(c)); + } + + return (short) c; + } + + /** + * Gets the C register number, as a byte. This will throw if the + * value is out of the range of an unsigned byte. + */ + public final short getCByte() { + int c = getC(); + + if ((c & ~0xff) != 0) { + throw new DexException("Register C out of range: " + Hex.u8(c)); + } + + return (short) c; + } + + /** + * Gets the C register number, as a nibble. This will throw if the + * value is out of the range of an unsigned nibble. + */ + public final short getCNibble() { + int c = getC(); + + if ((c & ~0xf) != 0) { + throw new DexException("Register C out of range: " + Hex.u8(c)); + } + + return (short) c; + } + + /** + * Gets the D register number, as a code unit. This will throw if the + * value is out of the range of an unsigned code unit. + */ + public final short getDUnit() { + int d = getD(); + + if ((d & ~0xffff) != 0) { + throw new DexException("Register D out of range: " + Hex.u8(d)); + } + + return (short) d; + } + + /** + * Gets the D register number, as a byte. This will throw if the + * value is out of the range of an unsigned byte. + */ + public final short getDByte() { + int d = getD(); + + if ((d & ~0xff) != 0) { + throw new DexException("Register D out of range: " + Hex.u8(d)); + } + + return (short) d; + } + + /** + * Gets the D register number, as a nibble. This will throw if the + * value is out of the range of an unsigned nibble. + */ + public final short getDNibble() { + int d = getD(); + + if ((d & ~0xf) != 0) { + throw new DexException("Register D out of range: " + Hex.u8(d)); + } + + return (short) d; + } + + /** + * Gets the E register number, as a nibble. This will throw if the + * value is out of the range of an unsigned nibble. + */ + public final short getENibble() { + int e = getE(); + + if ((e & ~0xf) != 0) { + throw new DexException("Register E out of range: " + Hex.u8(e)); + } + + return (short) e; + } + + /** + * Encodes this instance to the given output. + */ + public final void encode(CodeOutput out) { + format.encode(this, out); + } + + /** + * Returns an instance just like this one, except with the index replaced + * with the given one. + */ + public abstract DecodedInstruction withIndex(int newIndex); +} diff --git a/dx/src/com/android/jack/dx/io/instructions/FillArrayDataPayloadDecodedInstruction.java b/dx/src/com/android/jack/dx/io/instructions/FillArrayDataPayloadDecodedInstruction.java new file mode 100644 index 00000000..4e59342d --- /dev/null +++ b/dx/src/com/android/jack/dx/io/instructions/FillArrayDataPayloadDecodedInstruction.java @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.io.instructions; + +/** + * A decoded Dalvik instruction which contains the payload for + * a {@code packed-switch} instruction. + */ +public final class FillArrayDataPayloadDecodedInstruction + extends DecodedInstruction { + /** data array */ + private final Object data; + + /** number of elements */ + private final int size; + + /** element width */ + private final int elementWidth; + + /** + * Constructs an instance. This private instance doesn't check the + * type of the data array. + */ + private FillArrayDataPayloadDecodedInstruction(InstructionCodec format, + int opcode, Object data, int size, int elementWidth) { + super(format, opcode, 0, null, 0, 0L); + + this.data = data; + this.size = size; + this.elementWidth = elementWidth; + } + + /** + * Constructs an instance. + */ + public FillArrayDataPayloadDecodedInstruction(InstructionCodec format, + int opcode, byte[] data) { + this(format, opcode, data, data.length, 1); + } + + /** + * Constructs an instance. + */ + public FillArrayDataPayloadDecodedInstruction(InstructionCodec format, + int opcode, short[] data) { + this(format, opcode, data, data.length, 2); + } + + /** + * Constructs an instance. + */ + public FillArrayDataPayloadDecodedInstruction(InstructionCodec format, + int opcode, int[] data) { + this(format, opcode, data, data.length, 4); + } + + /** + * Constructs an instance. + */ + public FillArrayDataPayloadDecodedInstruction(InstructionCodec format, + int opcode, long[] data) { + this(format, opcode, data, data.length, 8); + } + + /** @inheritDoc */ + public int getRegisterCount() { + return 0; + } + + public short getElementWidthUnit() { + return (short) elementWidth; + } + + public int getSize() { + return size; + } + + public Object getData() { + return data; + } + + /** @inheritDoc */ + public DecodedInstruction withIndex(int newIndex) { + throw new UnsupportedOperationException("no index in instruction"); + } +} diff --git a/dx/src/com/android/jack/dx/io/instructions/FiveRegisterDecodedInstruction.java b/dx/src/com/android/jack/dx/io/instructions/FiveRegisterDecodedInstruction.java new file mode 100644 index 00000000..0ed17b36 --- /dev/null +++ b/dx/src/com/android/jack/dx/io/instructions/FiveRegisterDecodedInstruction.java @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.io.instructions; + +import com.android.jack.dx.io.IndexType; + +/** + * A decoded Dalvik instruction which has five register arguments. + */ +public final class FiveRegisterDecodedInstruction extends DecodedInstruction { + /** register argument "A" */ + private final int a; + + /** register argument "B" */ + private final int b; + + /** register argument "C" */ + private final int c; + + /** register argument "D" */ + private final int d; + + /** register argument "E" */ + private final int e; + + /** + * Constructs an instance. + */ + public FiveRegisterDecodedInstruction(InstructionCodec format, int opcode, + int index, IndexType indexType, int target, long literal, + int a, int b, int c, int d, int e) { + super(format, opcode, index, indexType, target, literal); + + this.a = a; + this.b = b; + this.c = c; + this.d = d; + this.e = e; + } + + /** @inheritDoc */ + public int getRegisterCount() { + return 5; + } + + /** @inheritDoc */ + public int getA() { + return a; + } + + /** @inheritDoc */ + public int getB() { + return b; + } + + /** @inheritDoc */ + public int getC() { + return c; + } + + /** @inheritDoc */ + public int getD() { + return d; + } + + /** @inheritDoc */ + public int getE() { + return e; + } + + /** @inheritDoc */ + public DecodedInstruction withIndex(int newIndex) { + return new FiveRegisterDecodedInstruction( + getFormat(), getOpcode(), newIndex, getIndexType(), + getTarget(), getLiteral(), a, b, c, d, e); + } +} diff --git a/dx/src/com/android/jack/dx/io/instructions/FourRegisterDecodedInstruction.java b/dx/src/com/android/jack/dx/io/instructions/FourRegisterDecodedInstruction.java new file mode 100644 index 00000000..9d3e7e3d --- /dev/null +++ b/dx/src/com/android/jack/dx/io/instructions/FourRegisterDecodedInstruction.java @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.io.instructions; + +import com.android.jack.dx.io.IndexType; + +/** + * A decoded Dalvik instruction which has five register arguments. + */ +public final class FourRegisterDecodedInstruction extends DecodedInstruction { + /** register argument "A" */ + private final int a; + + /** register argument "B" */ + private final int b; + + /** register argument "C" */ + private final int c; + + /** register argument "D" */ + private final int d; + + /** + * Constructs an instance. + */ + public FourRegisterDecodedInstruction(InstructionCodec format, int opcode, + int index, IndexType indexType, int target, long literal, + int a, int b, int c, int d) { + super(format, opcode, index, indexType, target, literal); + + this.a = a; + this.b = b; + this.c = c; + this.d = d; + } + + /** @inheritDoc */ + public int getRegisterCount() { + return 4; + } + + /** @inheritDoc */ + public int getA() { + return a; + } + + /** @inheritDoc */ + public int getB() { + return b; + } + + /** @inheritDoc */ + public int getC() { + return c; + } + + /** @inheritDoc */ + public int getD() { + return d; + } + + /** @inheritDoc */ + public DecodedInstruction withIndex(int newIndex) { + return new FourRegisterDecodedInstruction( + getFormat(), getOpcode(), newIndex, getIndexType(), + getTarget(), getLiteral(), a, b, c, d); + } +} diff --git a/dx/src/com/android/jack/dx/io/instructions/InstructionCodec.java b/dx/src/com/android/jack/dx/io/instructions/InstructionCodec.java new file mode 100644 index 00000000..d24d930b --- /dev/null +++ b/dx/src/com/android/jack/dx/io/instructions/InstructionCodec.java @@ -0,0 +1,962 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.io.instructions; + +import com.android.jack.dx.io.IndexType; +import com.android.jack.dx.io.OpcodeInfo; +import com.android.jack.dx.io.Opcodes; +import com.android.jack.dx.util.DexException; +import com.android.jack.dx.util.Hex; + +import java.io.EOFException; + +/** + * Representation of an instruction format, which knows how to decode into + * and encode from instances of {@link DecodedInstruction}. + */ +public enum InstructionCodec { + FORMAT_00X() { + @Override public DecodedInstruction decode(int opcodeUnit, + CodeInput in) throws EOFException { + return new ZeroRegisterDecodedInstruction( + this, opcodeUnit, 0, null, + 0, 0L); + } + + @Override public void encode(DecodedInstruction insn, CodeOutput out) { + out.write(insn.getOpcodeUnit()); + } + }, + + FORMAT_10X() { + @Override public DecodedInstruction decode(int opcodeUnit, + CodeInput in) throws EOFException { + int opcode = byte0(opcodeUnit); + int literal = byte1(opcodeUnit); // should be zero + return new ZeroRegisterDecodedInstruction( + this, opcode, 0, null, + 0, literal); + } + + @Override public void encode(DecodedInstruction insn, CodeOutput out) { + out.write(insn.getOpcodeUnit()); + } + }, + + FORMAT_12X() { + @Override public DecodedInstruction decode(int opcodeUnit, + CodeInput in) throws EOFException { + int opcode = byte0(opcodeUnit); + int a = nibble2(opcodeUnit); + int b = nibble3(opcodeUnit); + return new TwoRegisterDecodedInstruction( + this, opcode, 0, null, + 0, 0L, + a, b); + } + + @Override public void encode(DecodedInstruction insn, CodeOutput out) { + out.write( + codeUnit(insn.getOpcodeUnit(), + makeByte(insn.getA(), insn.getB()))); + } + }, + + FORMAT_11N() { + @Override public DecodedInstruction decode(int opcodeUnit, + CodeInput in) throws EOFException { + int opcode = byte0(opcodeUnit); + int a = nibble2(opcodeUnit); + int literal = (nibble3(opcodeUnit) << 28) >> 28; // sign-extend + return new OneRegisterDecodedInstruction( + this, opcode, 0, null, + 0, literal, + a); + } + + @Override public void encode(DecodedInstruction insn, CodeOutput out) { + out.write( + codeUnit(insn.getOpcodeUnit(), + makeByte(insn.getA(), insn.getLiteralNibble()))); + } + }, + + FORMAT_11X() { + @Override public DecodedInstruction decode(int opcodeUnit, + CodeInput in) throws EOFException { + int opcode = byte0(opcodeUnit); + int a = byte1(opcodeUnit); + return new OneRegisterDecodedInstruction( + this, opcode, 0, null, + 0, 0L, + a); + } + + @Override public void encode(DecodedInstruction insn, CodeOutput out) { + out.write(codeUnit(insn.getOpcode(), insn.getA())); + } + }, + + FORMAT_10T() { + @Override public DecodedInstruction decode(int opcodeUnit, + CodeInput in) throws EOFException { + int baseAddress = in.cursor() - 1; + int opcode = byte0(opcodeUnit); + int target = (byte) byte1(opcodeUnit); // sign-extend + return new ZeroRegisterDecodedInstruction( + this, opcode, 0, null, + baseAddress + target, 0L); + } + + @Override public void encode(DecodedInstruction insn, CodeOutput out) { + int relativeTarget = insn.getTargetByte(out.cursor()); + out.write(codeUnit(insn.getOpcode(), relativeTarget)); + } + }, + + FORMAT_20T() { + @Override public DecodedInstruction decode(int opcodeUnit, + CodeInput in) throws EOFException { + int baseAddress = in.cursor() - 1; + int opcode = byte0(opcodeUnit); + int literal = byte1(opcodeUnit); // should be zero + int target = (short) in.read(); // sign-extend + return new ZeroRegisterDecodedInstruction( + this, opcode, 0, null, + baseAddress + target, literal); + } + + @Override public void encode(DecodedInstruction insn, CodeOutput out) { + short relativeTarget = insn.getTargetUnit(out.cursor()); + out.write(insn.getOpcodeUnit(), relativeTarget); + } + }, + + FORMAT_20BC() { + @Override public DecodedInstruction decode(int opcodeUnit, + CodeInput in) throws EOFException { + // Note: We use the literal field to hold the decoded AA value. + int opcode = byte0(opcodeUnit); + int literal = byte1(opcodeUnit); + int index = in.read(); + return new ZeroRegisterDecodedInstruction( + this, opcode, index, IndexType.VARIES, + 0, literal); + } + + @Override public void encode(DecodedInstruction insn, CodeOutput out) { + out.write( + codeUnit(insn.getOpcode(), insn.getLiteralByte()), + insn.getIndexUnit()); + } + }, + + FORMAT_22X() { + @Override public DecodedInstruction decode(int opcodeUnit, + CodeInput in) throws EOFException { + int opcode = byte0(opcodeUnit); + int a = byte1(opcodeUnit); + int b = in.read(); + return new TwoRegisterDecodedInstruction( + this, opcode, 0, null, + 0, 0L, + a, b); + } + + @Override public void encode(DecodedInstruction insn, CodeOutput out) { + out.write( + codeUnit(insn.getOpcode(), insn.getA()), + insn.getBUnit()); + } + }, + + FORMAT_21T() { + @Override public DecodedInstruction decode(int opcodeUnit, + CodeInput in) throws EOFException { + int baseAddress = in.cursor() - 1; + int opcode = byte0(opcodeUnit); + int a = byte1(opcodeUnit); + int target = (short) in.read(); // sign-extend + return new OneRegisterDecodedInstruction( + this, opcode, 0, null, + baseAddress + target, 0L, + a); + } + + @Override public void encode(DecodedInstruction insn, CodeOutput out) { + short relativeTarget = insn.getTargetUnit(out.cursor()); + out.write(codeUnit(insn.getOpcode(), insn.getA()), relativeTarget); + } + }, + + FORMAT_21S() { + @Override public DecodedInstruction decode(int opcodeUnit, + CodeInput in) throws EOFException { + int opcode = byte0(opcodeUnit); + int a = byte1(opcodeUnit); + int literal = (short) in.read(); // sign-extend + return new OneRegisterDecodedInstruction( + this, opcode, 0, null, + 0, literal, + a); + } + + @Override public void encode(DecodedInstruction insn, CodeOutput out) { + out.write( + codeUnit(insn.getOpcode(), insn.getA()), + insn.getLiteralUnit()); + } + }, + + FORMAT_21H() { + @Override public DecodedInstruction decode(int opcodeUnit, + CodeInput in) throws EOFException { + int opcode = byte0(opcodeUnit); + int a = byte1(opcodeUnit); + long literal = (short) in.read(); // sign-extend + + /* + * Format 21h decodes differently depending on the opcode, + * because the "signed hat" might represent either a 32- + * or 64- bit value. + */ + literal <<= (opcode == Opcodes.CONST_HIGH16) ? 16 : 48; + + return new OneRegisterDecodedInstruction( + this, opcode, 0, null, + 0, literal, + a); + } + + @Override public void encode(DecodedInstruction insn, CodeOutput out) { + // See above. + int opcode = insn.getOpcode(); + int shift = (opcode == Opcodes.CONST_HIGH16) ? 16 : 48; + short literal = (short) (insn.getLiteral() >> shift); + + out.write(codeUnit(opcode, insn.getA()), literal); + } + }, + + FORMAT_21C() { + @Override public DecodedInstruction decode(int opcodeUnit, + CodeInput in) throws EOFException { + int opcode = byte0(opcodeUnit); + int a = byte1(opcodeUnit); + int index = in.read(); + IndexType indexType = OpcodeInfo.getIndexType(opcode); + return new OneRegisterDecodedInstruction( + this, opcode, index, indexType, + 0, 0L, + a); + } + + @Override public void encode(DecodedInstruction insn, CodeOutput out) { + out.write( + codeUnit(insn.getOpcode(), insn.getA()), + insn.getIndexUnit()); + } + }, + + FORMAT_23X() { + @Override public DecodedInstruction decode(int opcodeUnit, + CodeInput in) throws EOFException { + int opcode = byte0(opcodeUnit); + int a = byte1(opcodeUnit); + int bc = in.read(); + int b = byte0(bc); + int c = byte1(bc); + return new ThreeRegisterDecodedInstruction( + this, opcode, 0, null, + 0, 0L, + a, b, c); + } + + @Override public void encode(DecodedInstruction insn, CodeOutput out) { + out.write( + codeUnit(insn.getOpcode(), insn.getA()), + codeUnit(insn.getB(), insn.getC())); + } + }, + + FORMAT_22B() { + @Override public DecodedInstruction decode(int opcodeUnit, + CodeInput in) throws EOFException { + int opcode = byte0(opcodeUnit); + int a = byte1(opcodeUnit); + int bc = in.read(); + int b = byte0(bc); + int literal = (byte) byte1(bc); // sign-extend + return new TwoRegisterDecodedInstruction( + this, opcode, 0, null, + 0, literal, + a, b); + } + + @Override public void encode(DecodedInstruction insn, CodeOutput out) { + out.write( + codeUnit(insn.getOpcode(), insn.getA()), + codeUnit(insn.getB(), + insn.getLiteralByte())); + } + }, + + FORMAT_22T() { + @Override public DecodedInstruction decode(int opcodeUnit, + CodeInput in) throws EOFException { + int baseAddress = in.cursor() - 1; + int opcode = byte0(opcodeUnit); + int a = nibble2(opcodeUnit); + int b = nibble3(opcodeUnit); + int target = (short) in.read(); // sign-extend + return new TwoRegisterDecodedInstruction( + this, opcode, 0, null, + baseAddress + target, 0L, + a, b); + } + + @Override public void encode(DecodedInstruction insn, CodeOutput out) { + short relativeTarget = insn.getTargetUnit(out.cursor()); + out.write( + codeUnit(insn.getOpcode(), + makeByte(insn.getA(), insn.getB())), + relativeTarget); + } + }, + + FORMAT_22S() { + @Override public DecodedInstruction decode(int opcodeUnit, + CodeInput in) throws EOFException { + int opcode = byte0(opcodeUnit); + int a = nibble2(opcodeUnit); + int b = nibble3(opcodeUnit); + int literal = (short) in.read(); // sign-extend + return new TwoRegisterDecodedInstruction( + this, opcode, 0, null, + 0, literal, + a, b); + } + + @Override public void encode(DecodedInstruction insn, CodeOutput out) { + out.write( + codeUnit(insn.getOpcode(), + makeByte(insn.getA(), insn.getB())), + insn.getLiteralUnit()); + } + }, + + FORMAT_22C() { + @Override public DecodedInstruction decode(int opcodeUnit, + CodeInput in) throws EOFException { + int opcode = byte0(opcodeUnit); + int a = nibble2(opcodeUnit); + int b = nibble3(opcodeUnit); + int index = in.read(); + IndexType indexType = OpcodeInfo.getIndexType(opcode); + return new TwoRegisterDecodedInstruction( + this, opcode, index, indexType, + 0, 0L, + a, b); + } + + @Override public void encode(DecodedInstruction insn, CodeOutput out) { + out.write( + codeUnit(insn.getOpcode(), + makeByte(insn.getA(), insn.getB())), + insn.getIndexUnit()); + } + }, + + FORMAT_22CS() { + @Override public DecodedInstruction decode(int opcodeUnit, + CodeInput in) throws EOFException { + int opcode = byte0(opcodeUnit); + int a = nibble2(opcodeUnit); + int b = nibble3(opcodeUnit); + int index = in.read(); + return new TwoRegisterDecodedInstruction( + this, opcode, index, IndexType.FIELD_OFFSET, + 0, 0L, + a, b); + } + + @Override public void encode(DecodedInstruction insn, CodeOutput out) { + out.write( + codeUnit(insn.getOpcode(), + makeByte(insn.getA(), insn.getB())), + insn.getIndexUnit()); + } + }, + + FORMAT_30T() { + @Override public DecodedInstruction decode(int opcodeUnit, + CodeInput in) throws EOFException { + int baseAddress = in.cursor() - 1; + int opcode = byte0(opcodeUnit); + int literal = byte1(opcodeUnit); // should be zero + int target = in.readInt(); + return new ZeroRegisterDecodedInstruction( + this, opcode, 0, null, + baseAddress + target, literal); + } + + @Override public void encode(DecodedInstruction insn, CodeOutput out) { + int relativeTarget = insn.getTarget(out.cursor()); + out.write(insn.getOpcodeUnit(), + unit0(relativeTarget), unit1(relativeTarget)); + } + }, + + FORMAT_32X() { + @Override public DecodedInstruction decode(int opcodeUnit, + CodeInput in) throws EOFException { + int opcode = byte0(opcodeUnit); + int literal = byte1(opcodeUnit); // should be zero + int a = in.read(); + int b = in.read(); + return new TwoRegisterDecodedInstruction( + this, opcode, 0, null, + 0, literal, + a, b); + } + + @Override public void encode(DecodedInstruction insn, CodeOutput out) { + out.write(insn.getOpcodeUnit(), insn.getAUnit(), insn.getBUnit()); + } + }, + + FORMAT_31I() { + @Override public DecodedInstruction decode(int opcodeUnit, + CodeInput in) throws EOFException { + int opcode = byte0(opcodeUnit); + int a = byte1(opcodeUnit); + int literal = in.readInt(); + return new OneRegisterDecodedInstruction( + this, opcode, 0, null, + 0, literal, + a); + } + + @Override public void encode(DecodedInstruction insn, CodeOutput out) { + int literal = insn.getLiteralInt(); + out.write( + codeUnit(insn.getOpcode(), insn.getA()), + unit0(literal), + unit1(literal)); + } + }, + + FORMAT_31T() { + @Override public DecodedInstruction decode(int opcodeUnit, + CodeInput in) throws EOFException { + int baseAddress = in.cursor() - 1; + int opcode = byte0(opcodeUnit); + int a = byte1(opcodeUnit); + int target = baseAddress + in.readInt(); + + /* + * Switch instructions need to "forward" their addresses to their + * payload target instructions. + */ + switch (opcode) { + case Opcodes.PACKED_SWITCH: + case Opcodes.SPARSE_SWITCH: { + in.setBaseAddress(target, baseAddress); + break; + } + } + + return new OneRegisterDecodedInstruction( + this, opcode, 0, null, + target, 0L, + a); + } + + @Override public void encode(DecodedInstruction insn, CodeOutput out) { + int relativeTarget = insn.getTarget(out.cursor()); + out.write( + codeUnit(insn.getOpcode(), insn.getA()), + unit0(relativeTarget), unit1(relativeTarget)); + } + }, + + FORMAT_31C() { + @Override public DecodedInstruction decode(int opcodeUnit, + CodeInput in) throws EOFException { + int opcode = byte0(opcodeUnit); + int a = byte1(opcodeUnit); + int index = in.readInt(); + IndexType indexType = OpcodeInfo.getIndexType(opcode); + return new OneRegisterDecodedInstruction( + this, opcode, index, indexType, + 0, 0L, + a); + } + + @Override public void encode(DecodedInstruction insn, CodeOutput out) { + int index = insn.getIndex(); + out.write( + codeUnit(insn.getOpcode(), insn.getA()), + unit0(index), + unit1(index)); + } + }, + + FORMAT_35C() { + @Override public DecodedInstruction decode(int opcodeUnit, + CodeInput in) throws EOFException { + return decodeRegisterList(this, opcodeUnit, in); + } + + @Override public void encode(DecodedInstruction insn, CodeOutput out) { + encodeRegisterList(insn, out); + } + }, + + FORMAT_35MS() { + @Override public DecodedInstruction decode(int opcodeUnit, + CodeInput in) throws EOFException { + return decodeRegisterList(this, opcodeUnit, in); + } + + @Override public void encode(DecodedInstruction insn, CodeOutput out) { + encodeRegisterList(insn, out); + } + }, + + FORMAT_35MI() { + @Override public DecodedInstruction decode(int opcodeUnit, + CodeInput in) throws EOFException { + return decodeRegisterList(this, opcodeUnit, in); + } + + @Override public void encode(DecodedInstruction insn, CodeOutput out) { + encodeRegisterList(insn, out); + } + }, + + FORMAT_3RC() { + @Override public DecodedInstruction decode(int opcodeUnit, + CodeInput in) throws EOFException { + return decodeRegisterRange(this, opcodeUnit, in); + } + + @Override public void encode(DecodedInstruction insn, CodeOutput out) { + encodeRegisterRange(insn, out); + } + }, + + FORMAT_3RMS() { + @Override public DecodedInstruction decode(int opcodeUnit, + CodeInput in) throws EOFException { + return decodeRegisterRange(this, opcodeUnit, in); + } + + @Override public void encode(DecodedInstruction insn, CodeOutput out) { + encodeRegisterRange(insn, out); + } + }, + + FORMAT_3RMI() { + @Override public DecodedInstruction decode(int opcodeUnit, + CodeInput in) throws EOFException { + return decodeRegisterRange(this, opcodeUnit, in); + } + + @Override public void encode(DecodedInstruction insn, CodeOutput out) { + encodeRegisterRange(insn, out); + } + }, + + FORMAT_51L() { + @Override public DecodedInstruction decode(int opcodeUnit, + CodeInput in) throws EOFException { + int opcode = byte0(opcodeUnit); + int a = byte1(opcodeUnit); + long literal = in.readLong(); + return new OneRegisterDecodedInstruction( + this, opcode, 0, null, + 0, literal, + a); + } + + @Override public void encode(DecodedInstruction insn, CodeOutput out) { + long literal = insn.getLiteral(); + out.write( + codeUnit(insn.getOpcode(), insn.getA()), + unit0(literal), + unit1(literal), + unit2(literal), + unit3(literal)); + } + }, + + FORMAT_PACKED_SWITCH_PAYLOAD() { + @Override public DecodedInstruction decode(int opcodeUnit, + CodeInput in) throws EOFException { + int size = in.read(); + int firstKey = in.readInt(); + int[] targets = new int[size]; + + for (int i = 0; i < size; i++) { + targets[i] = in.readInt(); + } + + return new PackedSwitchPayloadDecodedInstruction( + this, opcodeUnit, firstKey, targets); + } + + @Override public void encode(DecodedInstruction insn, CodeOutput out) { + PackedSwitchPayloadDecodedInstruction payload = + (PackedSwitchPayloadDecodedInstruction) insn; + int[] targets = payload.getTargets(); + + out.write(payload.getOpcodeUnit()); + out.write(asUnsignedUnit(targets.length)); + out.writeInt(payload.getFirstKey()); + + for (int target : targets) { + out.writeInt(target); + } + } + }, + + FORMAT_SPARSE_SWITCH_PAYLOAD() { + @Override public DecodedInstruction decode(int opcodeUnit, + CodeInput in) throws EOFException { + int size = in.read(); + int[] keys = new int[size]; + int[] targets = new int[size]; + + for (int i = 0; i < size; i++) { + keys[i] = in.readInt(); + } + + for (int i = 0; i < size; i++) { + targets[i] = in.readInt(); + } + + return new SparseSwitchPayloadDecodedInstruction( + this, opcodeUnit, keys, targets); + } + + @Override public void encode(DecodedInstruction insn, CodeOutput out) { + SparseSwitchPayloadDecodedInstruction payload = + (SparseSwitchPayloadDecodedInstruction) insn; + int[] keys = payload.getKeys(); + int[] targets = payload.getTargets(); + + out.write(payload.getOpcodeUnit()); + out.write(asUnsignedUnit(targets.length)); + + for (int key : keys) { + out.writeInt(key); + } + + for (int target : targets) { + out.writeInt(target); + } + } + }, + + FORMAT_FILL_ARRAY_DATA_PAYLOAD() { + @Override public DecodedInstruction decode(int opcodeUnit, + CodeInput in) throws EOFException { + int elementWidth = in.read(); + int size = in.readInt(); + + switch (elementWidth) { + case 1: { + byte[] array = new byte[size]; + boolean even = true; + for (int i = 0, value = 0; i < size; i++, even = !even) { + if (even) { + value = in.read(); + } + array[i] = (byte) (value & 0xff); + value >>= 8; + } + return new FillArrayDataPayloadDecodedInstruction( + this, opcodeUnit, array); + } + case 2: { + short[] array = new short[size]; + for (int i = 0; i < size; i++) { + array[i] = (short) in.read(); + } + return new FillArrayDataPayloadDecodedInstruction( + this, opcodeUnit, array); + } + case 4: { + int[] array = new int[size]; + for (int i = 0; i < size; i++) { + array[i] = in.readInt(); + } + return new FillArrayDataPayloadDecodedInstruction( + this, opcodeUnit, array); + } + case 8: { + long[] array = new long[size]; + for (int i = 0; i < size; i++) { + array[i] = in.readLong(); + } + return new FillArrayDataPayloadDecodedInstruction( + this, opcodeUnit, array); + } + } + + throw new DexException("bogus element_width: " + + Hex.u2(elementWidth)); + } + + @Override public void encode(DecodedInstruction insn, CodeOutput out) { + FillArrayDataPayloadDecodedInstruction payload = + (FillArrayDataPayloadDecodedInstruction) insn; + short elementWidth = payload.getElementWidthUnit(); + Object data = payload.getData(); + + out.write(payload.getOpcodeUnit()); + out.write(elementWidth); + out.writeInt(payload.getSize()); + + switch (elementWidth) { + case 1: out.write((byte[]) data); break; + case 2: out.write((short[]) data); break; + case 4: out.write((int[]) data); break; + case 8: out.write((long[]) data); break; + default: { + throw new DexException("bogus element_width: " + + Hex.u2(elementWidth)); + } + } + } + }; + + /** + * Decodes an instruction specified by the given opcode unit, reading + * any required additional code units from the given input source. + */ + public abstract DecodedInstruction decode(int opcodeUnit, CodeInput in) + throws EOFException; + + /** + * Encodes the given instruction. + */ + public abstract void encode(DecodedInstruction insn, CodeOutput out); + + /** + * Helper method that decodes any of the register-list formats. + */ + private static DecodedInstruction decodeRegisterList( + InstructionCodec format, int opcodeUnit, CodeInput in) + throws EOFException { + int opcode = byte0(opcodeUnit); + int e = nibble2(opcodeUnit); + int registerCount = nibble3(opcodeUnit); + int index = in.read(); + int abcd = in.read(); + int a = nibble0(abcd); + int b = nibble1(abcd); + int c = nibble2(abcd); + int d = nibble3(abcd); + IndexType indexType = OpcodeInfo.getIndexType(opcode); + + // TODO: Having to switch like this is less than ideal. + switch (registerCount) { + case 0: + return new ZeroRegisterDecodedInstruction( + format, opcode, index, indexType, + 0, 0L); + case 1: + return new OneRegisterDecodedInstruction( + format, opcode, index, indexType, + 0, 0L, + a); + case 2: + return new TwoRegisterDecodedInstruction( + format, opcode, index, indexType, + 0, 0L, + a, b); + case 3: + return new ThreeRegisterDecodedInstruction( + format, opcode, index, indexType, + 0, 0L, + a, b, c); + case 4: + return new FourRegisterDecodedInstruction( + format, opcode, index, indexType, + 0, 0L, + a, b, c, d); + case 5: + return new FiveRegisterDecodedInstruction( + format, opcode, index, indexType, + 0, 0L, + a, b, c, d, e); + } + + throw new DexException("bogus registerCount: " + + Hex.uNibble(registerCount)); + } + + /** + * Helper method that encodes any of the register-list formats. + */ + private static void encodeRegisterList(DecodedInstruction insn, + CodeOutput out) { + out.write(codeUnit(insn.getOpcode(), + makeByte(insn.getE(), insn.getRegisterCount())), + insn.getIndexUnit(), + codeUnit(insn.getA(), insn.getB(), insn.getC(), insn.getD())); + } + + /** + * Helper method that decodes any of the three-unit register-range formats. + */ + private static DecodedInstruction decodeRegisterRange( + InstructionCodec format, int opcodeUnit, CodeInput in) + throws EOFException { + int opcode = byte0(opcodeUnit); + int registerCount = byte1(opcodeUnit); + int index = in.read(); + int a = in.read(); + IndexType indexType = OpcodeInfo.getIndexType(opcode); + return new RegisterRangeDecodedInstruction( + format, opcode, index, indexType, + 0, 0L, + a, registerCount); + } + + /** + * Helper method that encodes any of the three-unit register-range formats. + */ + private static void encodeRegisterRange(DecodedInstruction insn, + CodeOutput out) { + out.write(codeUnit(insn.getOpcode(), insn.getRegisterCount()), + insn.getIndexUnit(), + insn.getAUnit()); + } + + private static short codeUnit(int lowByte, int highByte) { + if ((lowByte & ~0xff) != 0) { + throw new IllegalArgumentException("bogus lowByte"); + } + + if ((highByte & ~0xff) != 0) { + throw new IllegalArgumentException("bogus highByte"); + } + + return (short) (lowByte | (highByte << 8)); + } + + private static short codeUnit(int nibble0, int nibble1, int nibble2, + int nibble3) { + if ((nibble0 & ~0xf) != 0) { + throw new IllegalArgumentException("bogus nibble0"); + } + + if ((nibble1 & ~0xf) != 0) { + throw new IllegalArgumentException("bogus nibble1"); + } + + if ((nibble2 & ~0xf) != 0) { + throw new IllegalArgumentException("bogus nibble2"); + } + + if ((nibble3 & ~0xf) != 0) { + throw new IllegalArgumentException("bogus nibble3"); + } + + return (short) (nibble0 | (nibble1 << 4) + | (nibble2 << 8) | (nibble3 << 12)); + } + + private static int makeByte(int lowNibble, int highNibble) { + if ((lowNibble & ~0xf) != 0) { + throw new IllegalArgumentException("bogus lowNibble"); + } + + if ((highNibble & ~0xf) != 0) { + throw new IllegalArgumentException("bogus highNibble"); + } + + return lowNibble | (highNibble << 4); + } + + private static short asUnsignedUnit(int value) { + if ((value & ~0xffff) != 0) { + throw new IllegalArgumentException("bogus unsigned code unit"); + } + + return (short) value; + } + + private static short unit0(int value) { + return (short) value; + } + + private static short unit1(int value) { + return (short) (value >> 16); + } + + private static short unit0(long value) { + return (short) value; + } + + private static short unit1(long value) { + return (short) (value >> 16); + } + + private static short unit2(long value) { + return (short) (value >> 32); + } + + private static short unit3(long value) { + return (short) (value >> 48); + } + + private static int byte0(int value) { + return value & 0xff; + } + + private static int byte1(int value) { + return (value >> 8) & 0xff; + } + + private static int byte2(int value) { + return (value >> 16) & 0xff; + } + + private static int byte3(int value) { + return value >>> 24; + } + + private static int nibble0(int value) { + return value & 0xf; + } + + private static int nibble1(int value) { + return (value >> 4) & 0xf; + } + + private static int nibble2(int value) { + return (value >> 8) & 0xf; + } + + private static int nibble3(int value) { + return (value >> 12) & 0xf; + } +} diff --git a/dx/src/com/android/jack/dx/io/instructions/OneRegisterDecodedInstruction.java b/dx/src/com/android/jack/dx/io/instructions/OneRegisterDecodedInstruction.java new file mode 100644 index 00000000..acde53a5 --- /dev/null +++ b/dx/src/com/android/jack/dx/io/instructions/OneRegisterDecodedInstruction.java @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.io.instructions; + +import com.android.jack.dx.io.IndexType; + +/** + * A decoded Dalvik instruction which has one register argument. + */ +public final class OneRegisterDecodedInstruction extends DecodedInstruction { + /** register argument "A" */ + private final int a; + + /** + * Constructs an instance. + */ + public OneRegisterDecodedInstruction(InstructionCodec format, int opcode, + int index, IndexType indexType, int target, long literal, + int a) { + super(format, opcode, index, indexType, target, literal); + + this.a = a; + } + + /** @inheritDoc */ + public int getRegisterCount() { + return 1; + } + + /** @inheritDoc */ + public int getA() { + return a; + } + + /** @inheritDoc */ + public DecodedInstruction withIndex(int newIndex) { + return new OneRegisterDecodedInstruction( + getFormat(), getOpcode(), newIndex, getIndexType(), + getTarget(), getLiteral(), a); + } +} diff --git a/dx/src/com/android/jack/dx/io/instructions/PackedSwitchPayloadDecodedInstruction.java b/dx/src/com/android/jack/dx/io/instructions/PackedSwitchPayloadDecodedInstruction.java new file mode 100644 index 00000000..f2b50494 --- /dev/null +++ b/dx/src/com/android/jack/dx/io/instructions/PackedSwitchPayloadDecodedInstruction.java @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.io.instructions; + +/** + * A decoded Dalvik instruction which contains the payload for + * a {@code packed-switch} instruction. + */ +public final class PackedSwitchPayloadDecodedInstruction + extends DecodedInstruction { + /** first key value */ + private final int firstKey; + + /** + * array of target addresses. These are absolute, not relative, + * addresses. + */ + private final int[] targets; + + /** + * Constructs an instance. + */ + public PackedSwitchPayloadDecodedInstruction(InstructionCodec format, + int opcode, int firstKey, int[] targets) { + super(format, opcode, 0, null, 0, 0L); + + this.firstKey = firstKey; + this.targets = targets; + } + + /** @inheritDoc */ + public int getRegisterCount() { + return 0; + } + + public int getFirstKey() { + return firstKey; + } + + public int[] getTargets() { + return targets; + } + + /** @inheritDoc */ + public DecodedInstruction withIndex(int newIndex) { + throw new UnsupportedOperationException("no index in instruction"); + } +} diff --git a/dx/src/com/android/jack/dx/io/instructions/RegisterRangeDecodedInstruction.java b/dx/src/com/android/jack/dx/io/instructions/RegisterRangeDecodedInstruction.java new file mode 100644 index 00000000..30f12422 --- /dev/null +++ b/dx/src/com/android/jack/dx/io/instructions/RegisterRangeDecodedInstruction.java @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.io.instructions; + +import com.android.jack.dx.io.IndexType; + +/** + * A decoded Dalvik instruction which has register range arguments (an + * "A" start register and a register count). + */ +public final class RegisterRangeDecodedInstruction extends DecodedInstruction { + /** register argument "A" */ + private final int a; + + /** register count */ + private final int registerCount; + + /** + * Constructs an instance. + */ + public RegisterRangeDecodedInstruction(InstructionCodec format, int opcode, + int index, IndexType indexType, int target, long literal, + int a, int registerCount) { + super(format, opcode, index, indexType, target, literal); + + this.a = a; + this.registerCount = registerCount; + } + + /** @inheritDoc */ + public int getRegisterCount() { + return registerCount; + } + + /** @inheritDoc */ + public int getA() { + return a; + } + + /** @inheritDoc */ + public DecodedInstruction withIndex(int newIndex) { + return new RegisterRangeDecodedInstruction( + getFormat(), getOpcode(), newIndex, getIndexType(), + getTarget(), getLiteral(), a, registerCount); + } +} diff --git a/dx/src/com/android/jack/dx/io/instructions/ShortArrayCodeInput.java b/dx/src/com/android/jack/dx/io/instructions/ShortArrayCodeInput.java new file mode 100644 index 00000000..c225f833 --- /dev/null +++ b/dx/src/com/android/jack/dx/io/instructions/ShortArrayCodeInput.java @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.io.instructions; + +import java.io.EOFException; + +/** + * Implementation of {@code CodeInput} that reads from a {@code short[]}. + */ +public final class ShortArrayCodeInput extends BaseCodeCursor + implements CodeInput { + /** source array to read from */ + private final short[] array; + + /** + * Constructs an instance. + */ + public ShortArrayCodeInput(short[] array) { + if (array == null) { + throw new NullPointerException("array == null"); + } + + this.array = array; + } + + /** @inheritDoc */ + public boolean hasMore() { + return cursor() < array.length; + } + + /** @inheritDoc */ + public int read() throws EOFException { + try { + int value = array[cursor()]; + advance(1); + return value & 0xffff; + } catch (ArrayIndexOutOfBoundsException ex) { + throw new EOFException(); + } + } + + /** @inheritDoc */ + public int readInt() throws EOFException { + int short0 = read(); + int short1 = read(); + + return short0 | (short1 << 16); + } + + /** @inheritDoc */ + public long readLong() throws EOFException { + long short0 = read(); + long short1 = read(); + long short2 = read(); + long short3 = read(); + + return short0 | (short1 << 16) | (short2 << 32) | (short3 << 48); + } +} diff --git a/dx/src/com/android/jack/dx/io/instructions/ShortArrayCodeOutput.java b/dx/src/com/android/jack/dx/io/instructions/ShortArrayCodeOutput.java new file mode 100644 index 00000000..4de2e71a --- /dev/null +++ b/dx/src/com/android/jack/dx/io/instructions/ShortArrayCodeOutput.java @@ -0,0 +1,146 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.io.instructions; + +/** + * Implementation of {@code CodeOutput} that writes to a {@code short[]}. + */ +public final class ShortArrayCodeOutput extends BaseCodeCursor + implements CodeOutput { + /** array to write to */ + private final short[] array; + + /** + * Constructs an instance. + * + * @param maxSize the maximum number of code units that will be written + */ + public ShortArrayCodeOutput(int maxSize) { + if (maxSize < 0) { + throw new IllegalArgumentException("maxSize < 0"); + } + + this.array = new short[maxSize]; + } + + /** + * Gets the array. The returned array contains exactly the data + * written (e.g. no leftover space at the end). + */ + public short[] getArray() { + int cursor = cursor(); + + if (cursor == array.length) { + return array; + } + + short[] result = new short[cursor]; + System.arraycopy(array, 0, result, 0, cursor); + return result; + } + + /** @inheritDoc */ + public void write(short codeUnit) { + array[cursor()] = codeUnit; + advance(1); + } + + /** @inheritDoc */ + public void write(short u0, short u1) { + write(u0); + write(u1); + } + + /** @inheritDoc */ + public void write(short u0, short u1, short u2) { + write(u0); + write(u1); + write(u2); + } + + /** @inheritDoc */ + public void write(short u0, short u1, short u2, short u3) { + write(u0); + write(u1); + write(u2); + write(u3); + } + + /** @inheritDoc */ + public void write(short u0, short u1, short u2, short u3, short u4) { + write(u0); + write(u1); + write(u2); + write(u3); + write(u4); + } + + /** @inheritDoc */ + public void writeInt(int value) { + write((short) value); + write((short) (value >> 16)); + } + + /** @inheritDoc */ + public void writeLong(long value) { + write((short) value); + write((short) (value >> 16)); + write((short) (value >> 32)); + write((short) (value >> 48)); + } + + /** @inheritDoc */ + public void write(byte[] data) { + int value = 0; + boolean even = true; + for (byte b : data) { + if (even) { + value = b & 0xff; + even = false; + } else { + value |= b << 8; + write((short) value); + even = true; + } + } + + if (!even) { + write((short) value); + } + } + + /** @inheritDoc */ + public void write(short[] data) { + for (short unit : data) { + write(unit); + } + } + + /** @inheritDoc */ + public void write(int[] data) { + for (int i : data) { + writeInt(i); + } + } + + /** @inheritDoc */ + public void write(long[] data) { + for (long l : data) { + writeLong(l); + } + } +} diff --git a/dx/src/com/android/jack/dx/io/instructions/SparseSwitchPayloadDecodedInstruction.java b/dx/src/com/android/jack/dx/io/instructions/SparseSwitchPayloadDecodedInstruction.java new file mode 100644 index 00000000..51559768 --- /dev/null +++ b/dx/src/com/android/jack/dx/io/instructions/SparseSwitchPayloadDecodedInstruction.java @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.io.instructions; + +/** + * A decoded Dalvik instruction which contains the payload for + * a {@code packed-switch} instruction. + */ +public final class SparseSwitchPayloadDecodedInstruction + extends DecodedInstruction { + /** array of key values */ + private final int[] keys; + + /** + * array of target addresses. These are absolute, not relative, + * addresses. + */ + private final int[] targets; + + /** + * Constructs an instance. + */ + public SparseSwitchPayloadDecodedInstruction(InstructionCodec format, + int opcode, int[] keys, int[] targets) { + super(format, opcode, 0, null, 0, 0L); + + if (keys.length != targets.length) { + throw new IllegalArgumentException("keys/targets length mismatch"); + } + + this.keys = keys; + this.targets = targets; + } + + /** @inheritDoc */ + public int getRegisterCount() { + return 0; + } + + public int[] getKeys() { + return keys; + } + + public int[] getTargets() { + return targets; + } + + /** @inheritDoc */ + public DecodedInstruction withIndex(int newIndex) { + throw new UnsupportedOperationException("no index in instruction"); + } +} diff --git a/dx/src/com/android/jack/dx/io/instructions/ThreeRegisterDecodedInstruction.java b/dx/src/com/android/jack/dx/io/instructions/ThreeRegisterDecodedInstruction.java new file mode 100644 index 00000000..3fe93ac0 --- /dev/null +++ b/dx/src/com/android/jack/dx/io/instructions/ThreeRegisterDecodedInstruction.java @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.io.instructions; + +import com.android.jack.dx.io.IndexType; + +/** + * A decoded Dalvik instruction which has three register arguments. + */ +public final class ThreeRegisterDecodedInstruction extends DecodedInstruction { + /** register argument "A" */ + private final int a; + + /** register argument "B" */ + private final int b; + + /** register argument "C" */ + private final int c; + + /** + * Constructs an instance. + */ + public ThreeRegisterDecodedInstruction(InstructionCodec format, int opcode, + int index, IndexType indexType, int target, long literal, + int a, int b, int c) { + super(format, opcode, index, indexType, target, literal); + + this.a = a; + this.b = b; + this.c = c; + } + + /** @inheritDoc */ + public int getRegisterCount() { + return 3; + } + + /** @inheritDoc */ + public int getA() { + return a; + } + + /** @inheritDoc */ + public int getB() { + return b; + } + + /** @inheritDoc */ + public int getC() { + return c; + } + + /** @inheritDoc */ + public DecodedInstruction withIndex(int newIndex) { + return new ThreeRegisterDecodedInstruction( + getFormat(), getOpcode(), newIndex, getIndexType(), + getTarget(), getLiteral(), a, b, c); + } +} diff --git a/dx/src/com/android/jack/dx/io/instructions/TwoRegisterDecodedInstruction.java b/dx/src/com/android/jack/dx/io/instructions/TwoRegisterDecodedInstruction.java new file mode 100644 index 00000000..6ff4e179 --- /dev/null +++ b/dx/src/com/android/jack/dx/io/instructions/TwoRegisterDecodedInstruction.java @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.io.instructions; + +import com.android.jack.dx.io.IndexType; + +/** + * A decoded Dalvik instruction which has two register arguments. + */ +public final class TwoRegisterDecodedInstruction extends DecodedInstruction { + /** register argument "A" */ + private final int a; + + /** register argument "B" */ + private final int b; + + /** + * Constructs an instance. + */ + public TwoRegisterDecodedInstruction(InstructionCodec format, int opcode, + int index, IndexType indexType, int target, long literal, + int a, int b) { + super(format, opcode, index, indexType, target, literal); + + this.a = a; + this.b = b; + } + + /** @inheritDoc */ + public int getRegisterCount() { + return 2; + } + + /** @inheritDoc */ + public int getA() { + return a; + } + + /** @inheritDoc */ + public int getB() { + return b; + } + + /** @inheritDoc */ + public DecodedInstruction withIndex(int newIndex) { + return new TwoRegisterDecodedInstruction( + getFormat(), getOpcode(), newIndex, getIndexType(), + getTarget(), getLiteral(), a, b); + } +} diff --git a/dx/src/com/android/jack/dx/io/instructions/ZeroRegisterDecodedInstruction.java b/dx/src/com/android/jack/dx/io/instructions/ZeroRegisterDecodedInstruction.java new file mode 100644 index 00000000..0e69a5ee --- /dev/null +++ b/dx/src/com/android/jack/dx/io/instructions/ZeroRegisterDecodedInstruction.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.io.instructions; + +import com.android.jack.dx.io.IndexType; + +/** + * A decoded Dalvik instruction which has no register arguments. + */ +public final class ZeroRegisterDecodedInstruction extends DecodedInstruction { + /** + * Constructs an instance. + */ + public ZeroRegisterDecodedInstruction(InstructionCodec format, int opcode, + int index, IndexType indexType, int target, long literal) { + super(format, opcode, index, indexType, target, literal); + } + + /** @inheritDoc */ + public int getRegisterCount() { + return 0; + } + + /** @inheritDoc */ + public DecodedInstruction withIndex(int newIndex) { + return new ZeroRegisterDecodedInstruction( + getFormat(), getOpcode(), newIndex, getIndexType(), + getTarget(), getLiteral()); + } +} diff --git a/dx/src/com/android/jack/dx/merge/CollisionPolicy.java b/dx/src/com/android/jack/dx/merge/CollisionPolicy.java new file mode 100644 index 00000000..f30bf0a3 --- /dev/null +++ b/dx/src/com/android/jack/dx/merge/CollisionPolicy.java @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.merge; + +/** + * What to do when two dex files define the same class. + */ +public enum CollisionPolicy { + + /** + * Keep the class def from the first dex file and discard the def from the + * second dex file. This policy is appropriate for incremental builds. + */ + KEEP_FIRST, + + /** + * Forbid collisions. This policy is appropriate for merging libraries. + */ + FAIL +} diff --git a/dx/src/com/android/jack/dx/merge/DexMerger.java b/dx/src/com/android/jack/dx/merge/DexMerger.java new file mode 100644 index 00000000..dad25244 --- /dev/null +++ b/dx/src/com/android/jack/dx/merge/DexMerger.java @@ -0,0 +1,1086 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.merge; + +import com.android.jack.dx.dex.SizeOf; +import com.android.jack.dx.dex.TableOfContents; +import com.android.jack.dx.io.Annotation; +import com.android.jack.dx.io.ClassData; +import com.android.jack.dx.io.ClassDef; +import com.android.jack.dx.io.Code; +import com.android.jack.dx.io.DexBuffer; +import com.android.jack.dx.io.DexHasher; +import com.android.jack.dx.io.FieldId; +import com.android.jack.dx.io.MethodId; +import com.android.jack.dx.io.ProtoId; +import com.android.jack.dx.util.DexException; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +/** + * Combine two dex files into one. + */ +public final class DexMerger { + private final DexBuffer dexA; + private final DexBuffer dexB; + private final CollisionPolicy collisionPolicy; + private final WriterSizes writerSizes; + + private final DexBuffer dexOut = new DexBuffer(); + + private final DexBuffer.Section headerOut; + + /** All IDs and definitions sections */ + private final DexBuffer.Section idsDefsOut; + + private final DexBuffer.Section mapListOut; + + private final DexBuffer.Section typeListOut; + + private final DexBuffer.Section classDataOut; + + private final DexBuffer.Section codeOut; + + private final DexBuffer.Section stringDataOut; + + private final DexBuffer.Section debugInfoOut; + + private final DexBuffer.Section encodedArrayOut; + + /** annotations directory on a type */ + private final DexBuffer.Section annotationsDirectoryOut; + + /** sets of annotations on a member, parameter or type */ + private final DexBuffer.Section annotationSetOut; + + /** parameter lists */ + private final DexBuffer.Section annotationSetRefListOut; + + /** individual annotations, each containing zero or more fields */ + private final DexBuffer.Section annotationOut; + + private final TableOfContents contentsOut; + + private final IndexMap aIndexMap; + private final IndexMap bIndexMap; + private final InstructionTransformer aInstructionTransformer; + private final InstructionTransformer bInstructionTransformer; + + /** minimum number of wasted bytes before it's worthwhile to compact the result */ + private int compactWasteThreshold = 1024 * 1024; // 1MiB + + public DexMerger(DexBuffer dexA, DexBuffer dexB, CollisionPolicy collisionPolicy) + throws IOException { + this(dexA, dexB, collisionPolicy, new WriterSizes(dexA, dexB)); + } + + private DexMerger(DexBuffer dexA, DexBuffer dexB, CollisionPolicy collisionPolicy, + WriterSizes writerSizes) throws IOException { + this.dexA = dexA; + this.dexB = dexB; + this.collisionPolicy = collisionPolicy; + this.writerSizes = writerSizes; + + TableOfContents aContents = dexA.getTableOfContents(); + TableOfContents bContents = dexB.getTableOfContents(); + aIndexMap = new IndexMap(dexOut, aContents); + bIndexMap = new IndexMap(dexOut, bContents); + aInstructionTransformer = new InstructionTransformer(aIndexMap); + bInstructionTransformer = new InstructionTransformer(bIndexMap); + + headerOut = dexOut.appendSection(writerSizes.header, "header"); + idsDefsOut = dexOut.appendSection(writerSizes.idsDefs, "ids defs"); + + contentsOut = dexOut.getTableOfContents(); + contentsOut.dataOff = dexOut.getLength(); + + contentsOut.mapList.off = dexOut.getLength(); + contentsOut.mapList.size = 1; + mapListOut = dexOut.appendSection(writerSizes.mapList, "map list"); + + contentsOut.typeLists.off = dexOut.getLength(); + typeListOut = dexOut.appendSection(writerSizes.typeList, "type list"); + + contentsOut.annotationSetRefLists.off = dexOut.getLength(); + annotationSetRefListOut = dexOut.appendSection( + writerSizes.annotationsSetRefList, "annotation set ref list"); + + contentsOut.annotationSets.off = dexOut.getLength(); + annotationSetOut = dexOut.appendSection(writerSizes.annotationsSet, "annotation sets"); + + contentsOut.classDatas.off = dexOut.getLength(); + classDataOut = dexOut.appendSection(writerSizes.classData, "class data"); + + contentsOut.codes.off = dexOut.getLength(); + codeOut = dexOut.appendSection(writerSizes.code, "code"); + + contentsOut.stringDatas.off = dexOut.getLength(); + stringDataOut = dexOut.appendSection(writerSizes.stringData, "string data"); + + contentsOut.debugInfos.off = dexOut.getLength(); + debugInfoOut = dexOut.appendSection(writerSizes.debugInfo, "debug info"); + + contentsOut.annotations.off = dexOut.getLength(); + annotationOut = dexOut.appendSection(writerSizes.annotation, "annotation"); + + contentsOut.encodedArrays.off = dexOut.getLength(); + encodedArrayOut = dexOut.appendSection(writerSizes.encodedArray, "encoded array"); + + contentsOut.annotationsDirectories.off = dexOut.getLength(); + annotationsDirectoryOut = dexOut.appendSection( + writerSizes.annotationsDirectory, "annotations directory"); + + dexOut.noMoreSections(); + contentsOut.dataSize = dexOut.getLength() - contentsOut.dataOff; + } + + public void setCompactWasteThreshold(int compactWasteThreshold) { + this.compactWasteThreshold = compactWasteThreshold; + } + + private DexBuffer mergeDexBuffers() throws IOException { + mergeStringIds(); + mergeTypeIds(); + mergeTypeLists(); + mergeProtoIds(); + mergeFieldIds(); + mergeMethodIds(); + mergeAnnotations(); + unionAnnotationSetsAndDirectories(); + mergeClassDefs(); + + // write the header + contentsOut.header.off = 0; + contentsOut.header.size = 1; + contentsOut.fileSize = dexOut.getLength(); + contentsOut.computeSizesFromOffsets(); + contentsOut.writeHeader(headerOut); + contentsOut.writeMap(mapListOut); + + // generate and write the hashes + new DexHasher().writeHashes(dexOut); + + return dexOut; + } + + public DexBuffer merge() throws IOException { + long start = System.nanoTime(); + DexBuffer result = mergeDexBuffers(); + + /* + * We use pessimistic sizes when merging dex files. If those sizes + * result in too many bytes wasted, compact the result. To compact, + * simply merge the result with itself. + */ + WriterSizes compactedSizes = new WriterSizes(this); + int wastedByteCount = writerSizes.size() - compactedSizes.size(); + if (wastedByteCount > + compactWasteThreshold) { + DexMerger compacter = new DexMerger( + dexOut, new DexBuffer(), CollisionPolicy.FAIL, compactedSizes); + result = compacter.mergeDexBuffers(); + System.out.printf("Result compacted from %.1fKiB to %.1fKiB to save %.1fKiB%n", + dexOut.getLength() / 1024f, + result.getLength() / 1024f, + wastedByteCount / 1024f); + } + + long elapsed = System.nanoTime() - start; + System.out.printf("Merged dex A (%d defs/%.1fKiB) with dex B " + + "(%d defs/%.1fKiB). Result is %d defs/%.1fKiB. Took %.1fs%n", + dexA.getTableOfContents().classDefs.size, + dexA.getLength() / 1024f, + dexB.getTableOfContents().classDefs.size, + dexB.getLength() / 1024f, + result.getTableOfContents().classDefs.size, + result.getLength() / 1024f, + elapsed / 1000000000f); + + return result; + } + + /** + * Reads an IDs section of two dex files and writes an IDs section of a + * merged dex file. Populates maps from old to new indices in the process. + */ + abstract class IdMerger> { + private final DexBuffer.Section out; + + protected IdMerger(DexBuffer.Section out) { + this.out = out; + } + + /** + * Merges already-sorted sections, reading only two values into memory + * at a time. + */ + public final void mergeSorted() { + TableOfContents.Section aSection = getSection(dexA.getTableOfContents()); + TableOfContents.Section bSection = getSection(dexB.getTableOfContents()); + getSection(contentsOut).off = out.getPosition(); + + DexBuffer.Section inA = aSection.exists() ? dexA.open(aSection.off) : null; + DexBuffer.Section inB = bSection.exists() ? dexB.open(bSection.off) : null; + int aOffset = -1; + int bOffset = -1; + int aIndex = 0; + int bIndex = 0; + int outCount = 0; + T a = null; + T b = null; + + while (true) { + if (a == null && aIndex < aSection.size) { + aOffset = inA.getPosition(); + a = read(inA, aIndexMap, aIndex); + } + if (b == null && bIndex < bSection.size) { + bOffset = inB.getPosition(); + b = read(inB, bIndexMap, bIndex); + } + + // Write the smaller of a and b. If they're equal, write only once + boolean advanceA; + boolean advanceB; + if (a != null && b != null) { + int compare = a.compareTo(b); + advanceA = compare <= 0; + advanceB = compare >= 0; + } else { + advanceA = (a != null); + advanceB = (b != null); + } + + T toWrite = null; + if (advanceA) { + toWrite = a; + updateIndex(aOffset, aIndexMap, aIndex++, outCount); + a = null; + aOffset = -1; + } + if (advanceB) { + toWrite = b; + updateIndex(bOffset, bIndexMap, bIndex++, outCount); + b = null; + bOffset = -1; + } + if (toWrite == null) { + break; // advanceA == false && advanceB == false + } + write(toWrite); + outCount++; + } + + getSection(contentsOut).size = outCount; + } + + /** + * Merges unsorted sections by reading them completely into memory and + * sorting in memory. + */ + public final void mergeUnsorted() { + getSection(contentsOut).off = out.getPosition(); + + List all = new ArrayList(); + all.addAll(readUnsortedValues(dexA, aIndexMap)); + all.addAll(readUnsortedValues(dexB, bIndexMap)); + Collections.sort(all); + + int outCount = 0; + for (int i = 0; i < all.size(); ) { + UnsortedValue e1 = all.get(i++); + updateIndex(e1.offset, getIndexMap(e1.source), e1.index, outCount - 1); + + while (i < all.size() && e1.compareTo(all.get(i)) == 0) { + UnsortedValue e2 = all.get(i++); + updateIndex(e2.offset, getIndexMap(e2.source), e2.index, outCount - 1); + } + + write(e1.value); + outCount++; + } + + getSection(contentsOut).size = outCount; + } + + private List readUnsortedValues(DexBuffer source, IndexMap indexMap) { + TableOfContents.Section section = getSection(source.getTableOfContents()); + if (!section.exists()) { + return Collections.emptyList(); + } + + List result = new ArrayList(); + DexBuffer.Section in = source.open(section.off); + for (int i = 0; i < section.size; i++) { + int offset = in.getPosition(); + T value = read(in, indexMap, 0); + result.add(new UnsortedValue(source, indexMap, value, i, offset)); + } + return result; + } + + abstract TableOfContents.Section getSection(TableOfContents tableOfContents); + abstract T read(DexBuffer.Section in, IndexMap indexMap, int index); + abstract void updateIndex(int offset, IndexMap indexMap, int oldIndex, int newIndex); + abstract void write(T value); + + class UnsortedValue implements Comparable { + final DexBuffer source; + final IndexMap indexMap; + final T value; + final int index; + final int offset; + + UnsortedValue(DexBuffer source, IndexMap indexMap, T value, int index, int offset) { + this.source = source; + this.indexMap = indexMap; + this.value = value; + this.index = index; + this.offset = offset; + } + + public int compareTo(UnsortedValue unsortedValue) { + return value.compareTo(unsortedValue.value); + } + } + } + + private IndexMap getIndexMap(DexBuffer dexBuffer) { + if (dexBuffer == dexA) { + return aIndexMap; + } else if (dexBuffer == dexB) { + return bIndexMap; + } else { + throw new IllegalArgumentException(); + } + } + + private void mergeStringIds() { + new IdMerger(idsDefsOut) { + @Override TableOfContents.Section getSection(TableOfContents tableOfContents) { + return tableOfContents.stringIds; + } + + @Override String read(DexBuffer.Section in, IndexMap indexMap, int index) { + return in.readString(); + } + + @Override void updateIndex(int offset, IndexMap indexMap, int oldIndex, int newIndex) { + indexMap.stringIds[oldIndex] = newIndex; + } + + @Override void write(String value) { + contentsOut.stringDatas.size++; + idsDefsOut.writeInt(stringDataOut.getPosition()); + stringDataOut.writeStringData(value); + } + }.mergeSorted(); + } + + private void mergeTypeIds() { + new IdMerger(idsDefsOut) { + @Override TableOfContents.Section getSection(TableOfContents tableOfContents) { + return tableOfContents.typeIds; + } + + @Override Integer read(DexBuffer.Section in, IndexMap indexMap, int index) { + int stringIndex = in.readInt(); + return indexMap.adjustString(stringIndex); + } + + @Override void updateIndex(int offset, IndexMap indexMap, int oldIndex, int newIndex) { + checkIndex16(newIndex); + indexMap.typeIds[oldIndex] = (short) newIndex; + } + + @Override void write(Integer value) { + idsDefsOut.writeInt(value); + } + }.mergeSorted(); + } + + private void mergeTypeLists() { + new IdMerger(typeListOut) { + @Override TableOfContents.Section getSection(TableOfContents tableOfContents) { + return tableOfContents.typeLists; + } + + @Override TypeList read(DexBuffer.Section in, IndexMap indexMap, int index) { + return indexMap.adjustTypeList(in.readTypeList()); + } + + @Override void updateIndex(int offset, IndexMap indexMap, int oldIndex, int newIndex) { + indexMap.putTypeListOffset(offset, typeListOut.getPosition()); + } + + @Override void write(TypeList value) { + typeListOut.writeTypeList(value); + } + }.mergeUnsorted(); + } + + private void mergeProtoIds() { + new IdMerger(idsDefsOut) { + @Override TableOfContents.Section getSection(TableOfContents tableOfContents) { + return tableOfContents.protoIds; + } + + @Override ProtoId read(DexBuffer.Section in, IndexMap indexMap, int index) { + return indexMap.adjust(in.readProtoId()); + } + + @Override void updateIndex(int offset, IndexMap indexMap, int oldIndex, int newIndex) { + checkIndex16(newIndex); + indexMap.protoIds[oldIndex] = (short) newIndex; + } + + @Override void write(ProtoId value) { + value.writeTo(idsDefsOut); + } + }.mergeSorted(); + } + + private void mergeFieldIds() { + new IdMerger(idsDefsOut) { + @Override TableOfContents.Section getSection(TableOfContents tableOfContents) { + return tableOfContents.fieldIds; + } + + @Override FieldId read(DexBuffer.Section in, IndexMap indexMap, int index) { + return indexMap.adjust(in.readFieldId()); + } + + @Override void updateIndex(int offset, IndexMap indexMap, int oldIndex, int newIndex) { + checkIndex16(newIndex); + indexMap.fieldIds[oldIndex] = (short) newIndex; + } + + @Override void write(FieldId value) { + value.writeTo(idsDefsOut); + } + }.mergeSorted(); + } + + private void mergeMethodIds() { + new IdMerger(idsDefsOut) { + @Override TableOfContents.Section getSection(TableOfContents tableOfContents) { + return tableOfContents.methodIds; + } + + @Override MethodId read(DexBuffer.Section in, IndexMap indexMap, int index) { + return indexMap.adjust(in.readMethodId()); + } + + @Override void updateIndex(int offset, IndexMap indexMap, int oldIndex, int newIndex) { + checkIndex16(newIndex); + indexMap.methodIds[oldIndex] = (short) newIndex; + } + + @Override void write(MethodId methodId) { + methodId.writeTo(idsDefsOut); + } + }.mergeSorted(); + } + + private void mergeAnnotations() { + new IdMerger(annotationOut) { + @Override TableOfContents.Section getSection(TableOfContents tableOfContents) { + return tableOfContents.annotations; + } + + @Override Annotation read(DexBuffer.Section in, IndexMap indexMap, int index) { + return indexMap.adjust(in.readAnnotation()); + } + + @Override void updateIndex(int offset, IndexMap indexMap, int oldIndex, int newIndex) { + indexMap.putAnnotationOffset(offset, annotationOut.getPosition()); + } + + @Override void write(Annotation value) { + value.writeTo(annotationOut); + } + }.mergeUnsorted(); + } + + private void mergeClassDefs() { + SortableType[] types = getSortedTypes(); + contentsOut.classDefs.off = idsDefsOut.getPosition(); + contentsOut.classDefs.size = types.length; + + for (SortableType type : types) { + DexBuffer in = type.getBuffer(); + IndexMap indexMap = (in == dexA) ? aIndexMap : bIndexMap; + transformClassDef(in, type.getClassDef(), indexMap); + } + } + + /** + * Returns the union of classes from both files, sorted in order such that + * a class is always preceded by its supertype and implemented interfaces. + */ + private SortableType[] getSortedTypes() { + // size is pessimistic; doesn't include arrays + SortableType[] sortableTypes = new SortableType[contentsOut.typeIds.size]; + readSortableTypes(sortableTypes, dexA, aIndexMap); + readSortableTypes(sortableTypes, dexB, bIndexMap); + + /* + * Populate the depths of each sortable type. This makes D iterations + * through all N types, where 'D' is the depth of the deepest type. For + * example, the deepest class in libcore is Xalan's KeyIterator, which + * is 11 types deep. + */ + while (true) { + boolean allDone = true; + for (SortableType sortableType : sortableTypes) { + if (sortableType != null && !sortableType.isDepthAssigned()) { + allDone &= sortableType.tryAssignDepth(sortableTypes); + } + } + if (allDone) { + break; + } + } + + // Now that all types have depth information, the result can be sorted + Arrays.sort(sortableTypes, SortableType.NULLS_LAST_ORDER); + + // Strip nulls from the end + int firstNull = Arrays.asList(sortableTypes).indexOf(null); + return firstNull != -1 + ? Arrays.copyOfRange(sortableTypes, 0, firstNull) + : sortableTypes; + } + + /** + * Reads just enough data on each class so that we can sort it and then find + * it later. + */ + private void readSortableTypes(SortableType[] sortableTypes, DexBuffer buffer, + IndexMap indexMap) { + for (ClassDef classDef : buffer.classDefs()) { + SortableType sortableType = indexMap.adjust(new SortableType(buffer, classDef)); + int t = sortableType.getTypeIndex(); + if (sortableTypes[t] == null) { + sortableTypes[t] = sortableType; + } else if (collisionPolicy != CollisionPolicy.KEEP_FIRST) { + throw new DexException("Multiple dex files define " + + buffer.typeNames().get(classDef.getTypeIndex())); + } + } + } + + /** + * Copy annotation sets from each input to the output. + * + * TODO: this may write multiple copies of the same annotation set. + * We should shrink the output by merging rather than unioning + */ + private void unionAnnotationSetsAndDirectories() { + transformAnnotationSets(dexA, aIndexMap); + transformAnnotationSets(dexB, bIndexMap); + transformAnnotationDirectories(dexA, aIndexMap); + transformAnnotationDirectories(dexB, bIndexMap); + transformStaticValues(dexA, aIndexMap); + transformStaticValues(dexB, bIndexMap); + } + + private void transformAnnotationSets(DexBuffer in, IndexMap indexMap) { + TableOfContents.Section section = in.getTableOfContents().annotationSets; + if (section.exists()) { + DexBuffer.Section setIn = in.open(section.off); + for (int i = 0; i < section.size; i++) { + transformAnnotationSet(indexMap, setIn); + } + } + } + + private void checkIndex16(int index) { + if (index > Character.MAX_VALUE || index < 0) { + throw new DexException("Too many IDs in dex"); + } + } + + private void transformAnnotationDirectories(DexBuffer in, IndexMap indexMap) { + TableOfContents.Section section = in.getTableOfContents().annotationsDirectories; + if (section.exists()) { + DexBuffer.Section directoryIn = in.open(section.off); + for (int i = 0; i < section.size; i++) { + transformAnnotationDirectory(in, directoryIn, indexMap); + } + } + } + + private void transformStaticValues(DexBuffer in, IndexMap indexMap) { + TableOfContents.Section section = in.getTableOfContents().encodedArrays; + if (section.exists()) { + DexBuffer.Section staticValuesIn = in.open(section.off); + for (int i = 0; i < section.size; i++) { + transformStaticValues(staticValuesIn, indexMap); + } + } + } + + /** + * Reads a class_def_item beginning at {@code in} and writes the index and + * data. + */ + private void transformClassDef(DexBuffer in, ClassDef classDef, IndexMap indexMap) { + idsDefsOut.assertFourByteAligned(); + idsDefsOut.writeInt(classDef.getTypeIndex()); + idsDefsOut.writeInt(classDef.getAccessFlags()); + idsDefsOut.writeInt(classDef.getSupertypeIndex()); + idsDefsOut.writeInt(classDef.getInterfacesOffset()); + + int sourceFileIndex = indexMap.adjustString(classDef.getSourceFileIndex()); + idsDefsOut.writeInt(sourceFileIndex); + + int annotationsOff = classDef.getAnnotationsOffset(); + idsDefsOut.writeInt(indexMap.adjustAnnotationDirectory(annotationsOff)); + + int classDataOff = classDef.getClassDataOffset(); + if (classDataOff == 0) { + idsDefsOut.writeInt(0); + } else { + idsDefsOut.writeInt(classDataOut.getPosition()); + ClassData classData = in.readClassData(classDef); + transformClassData(in, classData, indexMap); + } + + int staticValuesOff = classDef.getStaticValuesOffset(); + idsDefsOut.writeInt(indexMap.adjustStaticValues(staticValuesOff)); + } + + /** + * Transform all annotations on a class. + */ + private void transformAnnotationDirectory( + DexBuffer in, DexBuffer.Section directoryIn, IndexMap indexMap) { + contentsOut.annotationsDirectories.size++; + annotationsDirectoryOut.assertFourByteAligned(); + indexMap.putAnnotationDirectoryOffset( + directoryIn.getPosition(), annotationsDirectoryOut.getPosition()); + + int classAnnotationsOffset = indexMap.adjustAnnotationSet(directoryIn.readInt()); + annotationsDirectoryOut.writeInt(classAnnotationsOffset); + + int fieldsSize = directoryIn.readInt(); + annotationsDirectoryOut.writeInt(fieldsSize); + + int methodsSize = directoryIn.readInt(); + annotationsDirectoryOut.writeInt(methodsSize); + + int parameterListSize = directoryIn.readInt(); + annotationsDirectoryOut.writeInt(parameterListSize); + + for (int i = 0; i < fieldsSize; i++) { + // field index + annotationsDirectoryOut.writeInt(indexMap.adjustField(directoryIn.readInt())); + + // annotations offset + annotationsDirectoryOut.writeInt(indexMap.adjustAnnotationSet(directoryIn.readInt())); + } + + for (int i = 0; i < methodsSize; i++) { + // method index + annotationsDirectoryOut.writeInt(indexMap.adjustMethod(directoryIn.readInt())); + + // annotation set offset + annotationsDirectoryOut.writeInt( + indexMap.adjustAnnotationSet(directoryIn.readInt())); + } + + for (int i = 0; i < parameterListSize; i++) { + contentsOut.annotationSetRefLists.size++; + annotationSetRefListOut.assertFourByteAligned(); + + // method index + annotationsDirectoryOut.writeInt(indexMap.adjustMethod(directoryIn.readInt())); + + // annotations offset + annotationsDirectoryOut.writeInt(annotationSetRefListOut.getPosition()); + DexBuffer.Section refListIn = in.open(directoryIn.readInt()); + + // parameters + int parameterCount = refListIn.readInt(); + annotationSetRefListOut.writeInt(parameterCount); + for (int p = 0; p < parameterCount; p++) { + annotationSetRefListOut.writeInt(indexMap.adjustAnnotationSet(refListIn.readInt())); + } + } + } + + /** + * Transform all annotations on a single type, member or parameter. + */ + private void transformAnnotationSet(IndexMap indexMap, DexBuffer.Section setIn) { + contentsOut.annotationSets.size++; + annotationSetOut.assertFourByteAligned(); + indexMap.putAnnotationSetOffset(setIn.getPosition(), annotationSetOut.getPosition()); + + int size = setIn.readInt(); + annotationSetOut.writeInt(size); + + for (int j = 0; j < size; j++) { + annotationSetOut.writeInt(indexMap.adjustAnnotation(setIn.readInt())); + } + } + + private void transformClassData(DexBuffer in, ClassData classData, IndexMap indexMap) { + contentsOut.classDatas.size++; + + ClassData.Field[] staticFields = classData.getStaticFields(); + ClassData.Field[] instanceFields = classData.getInstanceFields(); + ClassData.Method[] directMethods = classData.getDirectMethods(); + ClassData.Method[] virtualMethods = classData.getVirtualMethods(); + + classDataOut.writeUleb128(staticFields.length); + classDataOut.writeUleb128(instanceFields.length); + classDataOut.writeUleb128(directMethods.length); + classDataOut.writeUleb128(virtualMethods.length); + + transformFields(indexMap, staticFields); + transformFields(indexMap, instanceFields); + transformMethods(in, indexMap, directMethods); + transformMethods(in, indexMap, virtualMethods); + } + + private void transformFields(IndexMap indexMap, ClassData.Field[] fields) { + int lastOutFieldIndex = 0; + for (ClassData.Field field : fields) { + int outFieldIndex = indexMap.adjustField(field.getFieldIndex()); + classDataOut.writeUleb128(outFieldIndex - lastOutFieldIndex); + lastOutFieldIndex = outFieldIndex; + classDataOut.writeUleb128(field.getAccessFlags()); + } + } + + private void transformMethods(DexBuffer in, IndexMap indexMap, ClassData.Method[] methods) { + int lastOutMethodIndex = 0; + for (ClassData.Method method : methods) { + int outMethodIndex = indexMap.adjustMethod(method.getMethodIndex()); + classDataOut.writeUleb128(outMethodIndex - lastOutMethodIndex); + lastOutMethodIndex = outMethodIndex; + + classDataOut.writeUleb128(method.getAccessFlags()); + + if (method.getCodeOffset() == 0) { + classDataOut.writeUleb128(0); + } else { + codeOut.alignToFourBytes(); + classDataOut.writeUleb128(codeOut.getPosition()); + transformCode(in, in.readCode(method), indexMap); + } + } + } + + private void transformCode(DexBuffer in, Code code, IndexMap indexMap) { + contentsOut.codes.size++; + codeOut.assertFourByteAligned(); + + codeOut.writeUnsignedShort(code.getRegistersSize()); + codeOut.writeUnsignedShort(code.getInsSize()); + codeOut.writeUnsignedShort(code.getOutsSize()); + + Code.Try[] tries = code.getTries(); + Code.CatchHandler[] catchHandlers = code.getCatchHandlers(); + codeOut.writeUnsignedShort(tries.length); + + int debugInfoOffset = code.getDebugInfoOffset(); + if (debugInfoOffset != 0) { + codeOut.writeInt(debugInfoOut.getPosition()); + transformDebugInfoItem(in.open(debugInfoOffset), indexMap); + } else { + codeOut.writeInt(0); + } + + short[] instructions = code.getInstructions(); + InstructionTransformer transformer = (in == dexA) + ? aInstructionTransformer + : bInstructionTransformer; + short[] newInstructions = transformer.transform(instructions); + codeOut.writeInt(newInstructions.length); + codeOut.write(newInstructions); + + if (tries.length > 0) { + if (newInstructions.length % 2 == 1) { + codeOut.writeShort((short) 0); // padding + } + + /* + * We can't write the tries until we've written the catch handlers. + * Unfortunately they're in the opposite order in the dex file so we + * need to transform them out-of-order. + */ + DexBuffer.Section triesSection = dexOut.open(codeOut.getPosition()); + codeOut.skip(tries.length * SizeOf.TRY_ITEM); + int[] offsets = transformCatchHandlers(indexMap, catchHandlers); + transformTries(triesSection, tries, offsets); + } + } + + /** + * Writes the catch handlers to {@code codeOut} and returns their indices. + */ + private int[] transformCatchHandlers(IndexMap indexMap, Code.CatchHandler[] catchHandlers) { + int baseOffset = codeOut.getPosition(); + codeOut.writeUleb128(catchHandlers.length); + int[] offsets = new int[catchHandlers.length]; + for (int i = 0; i < catchHandlers.length; i++) { + offsets[i] = codeOut.getPosition() - baseOffset; + transformEncodedCatchHandler(catchHandlers[i], indexMap); + } + return offsets; + } + + private void transformTries(DexBuffer.Section out, Code.Try[] tries, + int[] catchHandlerOffsets) { + for (Code.Try tryItem : tries) { + out.writeInt(tryItem.getStartAddress()); + out.writeUnsignedShort(tryItem.getInstructionCount()); + out.writeUnsignedShort(catchHandlerOffsets[tryItem.getCatchHandlerIndex()]); + } + } + + private static final byte DBG_END_SEQUENCE = 0x00; + private static final byte DBG_ADVANCE_PC = 0x01; + private static final byte DBG_ADVANCE_LINE = 0x02; + private static final byte DBG_START_LOCAL = 0x03; + private static final byte DBG_START_LOCAL_EXTENDED = 0x04; + private static final byte DBG_END_LOCAL = 0x05; + private static final byte DBG_RESTART_LOCAL = 0x06; + private static final byte DBG_SET_PROLOGUE_END = 0x07; + private static final byte DBG_SET_EPILOGUE_BEGIN = 0x08; + private static final byte DBG_SET_FILE = 0x09; + + private void transformDebugInfoItem(DexBuffer.Section in, IndexMap indexMap) { + contentsOut.debugInfos.size++; + int lineStart = in.readUleb128(); + debugInfoOut.writeUleb128(lineStart); + + int parametersSize = in.readUleb128(); + debugInfoOut.writeUleb128(parametersSize); + + for (int p = 0; p < parametersSize; p++) { + int parameterName = in.readUleb128p1(); + debugInfoOut.writeUleb128p1(indexMap.adjustString(parameterName)); + } + + int addrDiff; // uleb128 address delta. + int lineDiff; // sleb128 line delta. + int registerNum; // uleb128 register number. + int nameIndex; // uleb128p1 string index. Needs indexMap adjustment. + int typeIndex; // uleb128p1 type index. Needs indexMap adjustment. + int sigIndex; // uleb128p1 string index. Needs indexMap adjustment. + + while (true) { + int opcode = in.readByte(); + debugInfoOut.writeByte(opcode); + + switch (opcode) { + case DBG_END_SEQUENCE: + return; + + case DBG_ADVANCE_PC: + addrDiff = in.readUleb128(); + debugInfoOut.writeUleb128(addrDiff); + break; + + case DBG_ADVANCE_LINE: + lineDiff = in.readSleb128(); + debugInfoOut.writeSleb128(lineDiff); + break; + + case DBG_START_LOCAL: + case DBG_START_LOCAL_EXTENDED: + registerNum = in.readUleb128(); + debugInfoOut.writeUleb128(registerNum); + nameIndex = in.readUleb128p1(); + debugInfoOut.writeUleb128p1(indexMap.adjustString(nameIndex)); + typeIndex = in.readUleb128p1(); + debugInfoOut.writeUleb128p1(indexMap.adjustType(typeIndex)); + if (opcode == DBG_START_LOCAL_EXTENDED) { + sigIndex = in.readUleb128p1(); + debugInfoOut.writeUleb128p1(indexMap.adjustString(sigIndex)); + } + break; + + case DBG_END_LOCAL: + case DBG_RESTART_LOCAL: + registerNum = in.readUleb128(); + debugInfoOut.writeUleb128(registerNum); + break; + + case DBG_SET_FILE: + nameIndex = in.readUleb128p1(); + debugInfoOut.writeUleb128p1(indexMap.adjustString(nameIndex)); + break; + + case DBG_SET_PROLOGUE_END: + case DBG_SET_EPILOGUE_BEGIN: + default: + break; + } + } + } + + private void transformEncodedCatchHandler(Code.CatchHandler catchHandler, IndexMap indexMap) { + int catchAllAddress = catchHandler.getCatchAllAddress(); + int[] typeIndexes = catchHandler.getTypeIndexes(); + int[] addresses = catchHandler.getAddresses(); + + if (catchAllAddress != -1) { + codeOut.writeSleb128(-typeIndexes.length); + } else { + codeOut.writeSleb128(typeIndexes.length); + } + + for (int i = 0; i < typeIndexes.length; i++) { + codeOut.writeUleb128(indexMap.adjustType(typeIndexes[i])); + codeOut.writeUleb128(addresses[i]); + } + + if (catchAllAddress != -1) { + codeOut.writeUleb128(catchAllAddress); + } + } + + private void transformStaticValues(DexBuffer.Section in, IndexMap indexMap) { + contentsOut.encodedArrays.size++; + indexMap.putStaticValuesOffset(in.getPosition(), encodedArrayOut.getPosition()); + indexMap.adjustEncodedArray(in.readEncodedArray()).writeTo(encodedArrayOut); + } + + /** + * Byte counts for the sections written when creating a dex. Target sizes + * are defined in one of two ways: + *
    + *
  • By pessimistically guessing how large the union of dex files will be. + * We're pessimistic because we can't predict the amount of duplication + * between dex files, nor can we predict the length of ULEB-encoded + * offsets or indices. + *
  • By exactly measuring an existing dex. + *
+ */ + private static class WriterSizes { + private int header = SizeOf.HEADER_ITEM; + private int idsDefs; + private int mapList; + private int typeList; + private int classData; + private int code; + private int stringData; + private int debugInfo; + private int encodedArray; + private int annotationsDirectory; + private int annotationsSet; + private int annotationsSetRefList; + private int annotation; + + /** + * Compute sizes for merging a and b. + */ + public WriterSizes(DexBuffer a, DexBuffer b) { + plus(a.getTableOfContents(), false); + plus(b.getTableOfContents(), false); + } + + public WriterSizes(DexMerger dexMerger) { + header = dexMerger.headerOut.used(); + idsDefs = dexMerger.idsDefsOut.used(); + mapList = dexMerger.mapListOut.used(); + typeList = dexMerger.typeListOut.used(); + classData = dexMerger.classDataOut.used(); + code = dexMerger.codeOut.used(); + stringData = dexMerger.stringDataOut.used(); + debugInfo = dexMerger.debugInfoOut.used(); + encodedArray = dexMerger.encodedArrayOut.used(); + annotationsDirectory = dexMerger.annotationsDirectoryOut.used(); + annotationsSet = dexMerger.annotationSetOut.used(); + annotationsSetRefList = dexMerger.annotationSetRefListOut.used(); + annotation = dexMerger.annotationOut.used(); + } + + public void plus(TableOfContents contents, boolean exact) { + idsDefs += contents.stringIds.size * SizeOf.STRING_ID_ITEM + + contents.typeIds.size * SizeOf.TYPE_ID_ITEM + + contents.protoIds.size * SizeOf.PROTO_ID_ITEM + + contents.fieldIds.size * SizeOf.MEMBER_ID_ITEM + + contents.methodIds.size * SizeOf.MEMBER_ID_ITEM + + contents.classDefs.size * SizeOf.CLASS_DEF_ITEM; + mapList = SizeOf.UINT + (contents.sections.length * SizeOf.MAP_ITEM); + typeList += contents.typeLists.byteCount; + stringData += contents.stringDatas.byteCount; + annotationsDirectory += contents.annotationsDirectories.byteCount; + annotationsSet += contents.annotationSets.byteCount; + annotationsSetRefList += contents.annotationSetRefLists.byteCount; + + if (exact) { + code += contents.codes.byteCount; + classData += contents.classDatas.byteCount; + encodedArray += contents.encodedArrays.byteCount; + annotation += contents.annotations.byteCount; + debugInfo += contents.debugInfos.byteCount; + } else { + // at most 1/4 of the bytes in a code section are uleb/sleb + code += (int) Math.ceil(contents.codes.byteCount * 1.25); + // at most 1/3 of the bytes in a class data section are uleb/sleb + classData += (int) Math.ceil(contents.classDatas.byteCount * 1.34); + // all of the bytes in an encoding arrays section may be uleb/sleb + encodedArray += contents.encodedArrays.byteCount * 2; + // all of the bytes in an annotations section may be uleb/sleb + annotation += (int) Math.ceil(contents.annotations.byteCount * 2); + // all of the bytes in a debug info section may be uleb/sleb + debugInfo += contents.debugInfos.byteCount * 2; + } + + typeList = DexBuffer.fourByteAlign(typeList); + code = DexBuffer.fourByteAlign(code); + } + + public int size() { + return header + idsDefs + mapList + typeList + classData + code + stringData + debugInfo + + encodedArray + annotationsDirectory + annotationsSet + annotationsSetRefList + + annotation; + } + } + + public static void main(String[] args) throws IOException { + if (args.length < 2) { + printUsage(); + return; + } + + DexBuffer merged = new DexBuffer(new File(args[1])); + for (int i = 2; i < args.length; i++) { + DexBuffer toMerge = new DexBuffer(new File(args[i])); + merged = new DexMerger(merged, toMerge, CollisionPolicy.KEEP_FIRST).merge(); + } + merged.writeTo(new File(args[0])); + } + + private static void printUsage() { + System.out.println("Usage: DexMerger ..."); + System.out.println(); + System.out.println( + "If a class is defined in several dex, the class found in the first dex will be used."); + } +} diff --git a/dx/src/com/android/jack/dx/merge/IndexMap.java b/dx/src/com/android/jack/dx/merge/IndexMap.java new file mode 100644 index 00000000..37cd30ec --- /dev/null +++ b/dx/src/com/android/jack/dx/merge/IndexMap.java @@ -0,0 +1,307 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.merge; + +import com.android.jack.dx.dex.TableOfContents; +import com.android.jack.dx.io.Annotation; +import com.android.jack.dx.io.ClassDef; +import com.android.jack.dx.io.DexBuffer; +import com.android.jack.dx.io.EncodedValue; +import com.android.jack.dx.io.EncodedValueReader; +import com.android.jack.dx.io.FieldId; +import com.android.jack.dx.io.MethodId; +import com.android.jack.dx.io.ProtoId; +import com.android.jack.dx.util.ByteArrayAnnotatedOutput; +import com.android.jack.dx.util.ByteInput; +import com.android.jack.dx.util.ByteOutput; +import com.android.jack.dx.util.Leb128Utils; +import com.android.jack.dx.util.Unsigned; + +import java.util.HashMap; + +/** + * Maps the index offsets from one dex file to those in another. For example, if + * you have string #5 in the old dex file, its position in the new dex file is + * {@code strings[5]}. + */ +public final class IndexMap { + private final DexBuffer target; + public final int[] stringIds; + public final short[] typeIds; + public final short[] protoIds; + public final short[] fieldIds; + public final short[] methodIds; + private final HashMap typeListOffsets; + private final HashMap annotationOffsets; + private final HashMap annotationSetOffsets; + private final HashMap annotationDirectoryOffsets; + private final HashMap staticValuesOffsets; + + public IndexMap(DexBuffer target, TableOfContents tableOfContents) { + this.target = target; + this.stringIds = new int[tableOfContents.stringIds.size]; + this.typeIds = new short[tableOfContents.typeIds.size]; + this.protoIds = new short[tableOfContents.protoIds.size]; + this.fieldIds = new short[tableOfContents.fieldIds.size]; + this.methodIds = new short[tableOfContents.methodIds.size]; + this.typeListOffsets = new HashMap(); + this.annotationOffsets = new HashMap(); + this.annotationSetOffsets = new HashMap(); + this.annotationDirectoryOffsets = new HashMap(); + this.staticValuesOffsets = new HashMap(); + + /* + * A type list, annotation set, annotation directory, or static value at + * offset 0 is always empty. Always map offset 0 to 0. + */ + this.typeListOffsets.put(0, 0); + this.annotationSetOffsets.put(0, 0); + this.annotationDirectoryOffsets.put(0, 0); + this.staticValuesOffsets.put(0, 0); + } + + public void putTypeListOffset(int oldOffset, int newOffset) { + if (oldOffset <= 0 || newOffset <= 0) { + throw new IllegalArgumentException(); + } + typeListOffsets.put(oldOffset, newOffset); + } + + public void putAnnotationOffset(int oldOffset, int newOffset) { + if (oldOffset <= 0 || newOffset <= 0) { + throw new IllegalArgumentException(); + } + annotationOffsets.put(oldOffset, newOffset); + } + + public void putAnnotationSetOffset(int oldOffset, int newOffset) { + if (oldOffset <= 0 || newOffset <= 0) { + throw new IllegalArgumentException(); + } + annotationSetOffsets.put(oldOffset, newOffset); + } + + public void putAnnotationDirectoryOffset(int oldOffset, int newOffset) { + if (oldOffset <= 0 || newOffset <= 0) { + throw new IllegalArgumentException(); + } + annotationDirectoryOffsets.put(oldOffset, newOffset); + } + + public void putStaticValuesOffset(int oldOffset, int newOffset) { + if (oldOffset <= 0 || newOffset <= 0) { + throw new IllegalArgumentException(); + } + staticValuesOffsets.put(oldOffset, newOffset); + } + + public int adjustString(int stringIndex) { + return stringIndex == ClassDef.NO_INDEX ? ClassDef.NO_INDEX : stringIds[stringIndex]; + } + + public int adjustType(int typeIndex) { + return (typeIndex == ClassDef.NO_INDEX) ? ClassDef.NO_INDEX : (typeIds[typeIndex] & 0xffff); + } + + public TypeList adjustTypeList(TypeList typeList) { + if (typeList == TypeList.EMPTY) { + return typeList; + } + short[] types = typeList.getTypes().clone(); + for (int i = 0; i < types.length; i++) { + types[i] = (short) adjustType(types[i]); + } + return new TypeList(target, types); + } + + public int adjustProto(int protoIndex) { + return protoIds[protoIndex] & 0xffff; + } + + public int adjustField(int fieldIndex) { + return fieldIds[fieldIndex] & 0xffff; + } + + public int adjustMethod(int methodIndex) { + return methodIds[methodIndex] & 0xffff; + } + + public int adjustTypeListOffset(int typeListOffset) { + return typeListOffsets.get(typeListOffset); + } + + public int adjustAnnotation(int annotationOffset) { + return annotationOffsets.get(annotationOffset); + } + + public int adjustAnnotationSet(int annotationSetOffset) { + return annotationSetOffsets.get(annotationSetOffset); + } + + public int adjustAnnotationDirectory(int annotationDirectoryOffset) { + return annotationDirectoryOffsets.get(annotationDirectoryOffset); + } + + public int adjustStaticValues(int staticValuesOffset) { + return staticValuesOffsets.get(staticValuesOffset); + } + + public MethodId adjust(MethodId methodId) { + return new MethodId(target, + adjustType(methodId.getDeclaringClassIndex()), + adjustProto(methodId.getProtoIndex()), + adjustString(methodId.getNameIndex())); + } + + public FieldId adjust(FieldId fieldId) { + return new FieldId(target, + adjustType(fieldId.getDeclaringClassIndex()), + adjustType(fieldId.getTypeIndex()), + adjustString(fieldId.getNameIndex())); + + } + + public ProtoId adjust(ProtoId protoId) { + return new ProtoId(target, + adjustString(protoId.getShortyIndex()), + adjustType(protoId.getReturnTypeIndex()), + adjustTypeListOffset(protoId.getParametersOffset())); + } + + public ClassDef adjust(ClassDef classDef) { + return new ClassDef(target, classDef.getOffset(), adjustType(classDef.getTypeIndex()), + classDef.getAccessFlags(), adjustType(classDef.getSupertypeIndex()), + adjustTypeListOffset(classDef.getInterfacesOffset()), classDef.getSourceFileIndex(), + classDef.getAnnotationsOffset(), classDef.getClassDataOffset(), + classDef.getStaticValuesOffset()); + } + + public SortableType adjust(SortableType sortableType) { + return new SortableType(sortableType.getBuffer(), adjust(sortableType.getClassDef())); + } + + public EncodedValue adjustEncodedValue(EncodedValue encodedValue) { + ByteArrayAnnotatedOutput out = new ByteArrayAnnotatedOutput(32); + new EncodedValueTransformer(encodedValue, out).readValue(); + return new EncodedValue(out.toByteArray()); + } + + public EncodedValue adjustEncodedArray(EncodedValue encodedArray) { + ByteArrayAnnotatedOutput out = new ByteArrayAnnotatedOutput(32); + new EncodedValueTransformer(encodedArray, out).readArray(); + return new EncodedValue(out.toByteArray()); + } + + public Annotation adjust(Annotation annotation) { + int[] names = annotation.getNames().clone(); + EncodedValue[] values = annotation.getValues().clone(); + for (int i = 0; i < names.length; i++) { + names[i] = adjustString(names[i]); + values[i] = adjustEncodedValue(values[i]); + } + return new Annotation(target, annotation.getVisibility(), + adjustType(annotation.getTypeIndex()), names, values); + } + + /** + * Adjust an encoded value or array. + */ + private final class EncodedValueTransformer extends EncodedValueReader { + private final ByteOutput out; + + public EncodedValueTransformer(EncodedValue encodedValue, ByteOutput out) { + super(encodedValue); + this.out = out; + } + + protected void visitArray(int size) { + Leb128Utils.writeUnsignedLeb128(out, size); + } + + protected void visitAnnotation(int typeIndex, int size) { + Leb128Utils.writeUnsignedLeb128(out, adjustType(typeIndex)); + Leb128Utils.writeUnsignedLeb128(out, size); + } + + protected void visitAnnotationName(int index) { + Leb128Utils.writeUnsignedLeb128(out, adjustString(index)); + } + + protected void visitPrimitive(int argAndType, int type, int arg, int size) { + out.writeByte(argAndType); + copyBytes(in, out, size); + } + + protected void visitString(int type, int index) { + writeTypeAndSizeAndIndex(type, adjustString(index)); + } + + protected void visitType(int type, int index) { + writeTypeAndSizeAndIndex(type, adjustType(index)); + } + + protected void visitField(int type, int index) { + writeTypeAndSizeAndIndex(type, adjustField(index)); + } + + protected void visitMethod(int type, int index) { + writeTypeAndSizeAndIndex(type, adjustMethod(index)); + } + + protected void visitArrayValue(int argAndType) { + out.writeByte(argAndType); + } + + protected void visitAnnotationValue(int argAndType) { + out.writeByte(argAndType); + } + + protected void visitEncodedBoolean(int argAndType) { + out.writeByte(argAndType); + } + + protected void visitEncodedNull(int argAndType) { + out.writeByte(argAndType); + } + + private void writeTypeAndSizeAndIndex(int type, int index) { + int byteCount; + if (Unsigned.compare(index, 0xff) <= 0) { + byteCount = 1; + } else if (Unsigned.compare(index, 0xffff) <= 0) { + byteCount = 2; + } else if (Unsigned.compare(index, 0xffffff) <= 0) { + byteCount = 3; + } else { + byteCount = 4; + } + int argAndType = ((byteCount - 1) << 5) | type; + out.writeByte(argAndType); + + for (int i = 0; i < byteCount; i++) { + out.writeByte(index & 0xff); + index >>>= 8; + } + } + + private void copyBytes(ByteInput in, ByteOutput out, int size) { + for (int i = 0; i < size; i++) { + out.writeByte(in.readByte()); + } + } + } +} diff --git a/dx/src/com/android/jack/dx/merge/InstructionTransformer.java b/dx/src/com/android/jack/dx/merge/InstructionTransformer.java new file mode 100644 index 00000000..0aa5364c --- /dev/null +++ b/dx/src/com/android/jack/dx/merge/InstructionTransformer.java @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.merge; + +import com.android.jack.dx.io.CodeReader; +import com.android.jack.dx.io.Opcodes; +import com.android.jack.dx.io.instructions.DecodedInstruction; +import com.android.jack.dx.io.instructions.ShortArrayCodeOutput; +import com.android.jack.dx.util.DexException; + +final class InstructionTransformer { + private final IndexMap indexMap; + private final CodeReader reader; + private DecodedInstruction[] mappedInstructions; + private int mappedAt; + + public InstructionTransformer(IndexMap indexMap) { + this.indexMap = indexMap; + this.reader = new CodeReader(); + this.reader.setAllVisitors(new GenericVisitor()); + this.reader.setStringVisitor(new StringVisitor()); + this.reader.setTypeVisitor(new TypeVisitor()); + this.reader.setFieldVisitor(new FieldVisitor()); + this.reader.setMethodVisitor(new MethodVisitor()); + } + + public short[] transform(short[] encodedInstructions) throws DexException { + DecodedInstruction[] decodedInstructions = + DecodedInstruction.decodeAll(encodedInstructions); + int size = decodedInstructions.length; + + mappedInstructions = new DecodedInstruction[size]; + mappedAt = 0; + reader.visitAll(decodedInstructions); + + ShortArrayCodeOutput out = new ShortArrayCodeOutput(size); + for (DecodedInstruction instruction : mappedInstructions) { + if (instruction != null) { + instruction.encode(out); + } + } + + return out.getArray(); + } + + private class GenericVisitor implements CodeReader.Visitor { + public void visit(DecodedInstruction[] all, DecodedInstruction one) { + mappedInstructions[mappedAt++] = one; + } + } + + private class StringVisitor implements CodeReader.Visitor { + public void visit(DecodedInstruction[] all, DecodedInstruction one) { + int stringId = one.getIndex(); + int mappedId = indexMap.adjustString(stringId); + boolean isJumbo = (one.getOpcode() == Opcodes.CONST_STRING_JUMBO); + jumboCheck(isJumbo, mappedId); + mappedInstructions[mappedAt++] = one.withIndex(mappedId); + } + } + + private class FieldVisitor implements CodeReader.Visitor { + public void visit(DecodedInstruction[] all, DecodedInstruction one) { + int fieldId = one.getIndex(); + int mappedId = indexMap.adjustField(fieldId); + boolean isJumbo = (one.getOpcode() == Opcodes.CONST_STRING_JUMBO); + jumboCheck(isJumbo, mappedId); + mappedInstructions[mappedAt++] = one.withIndex(mappedId); + } + } + + private class TypeVisitor implements CodeReader.Visitor { + public void visit(DecodedInstruction[] all, DecodedInstruction one) { + int typeId = one.getIndex(); + int mappedId = indexMap.adjustType(typeId); + boolean isJumbo = (one.getOpcode() == Opcodes.CONST_STRING_JUMBO); + jumboCheck(isJumbo, mappedId); + mappedInstructions[mappedAt++] = one.withIndex(mappedId); + } + } + + private class MethodVisitor implements CodeReader.Visitor { + public void visit(DecodedInstruction[] all, DecodedInstruction one) { + int methodId = one.getIndex(); + int mappedId = indexMap.adjustMethod(methodId); + boolean isJumbo = (one.getOpcode() == Opcodes.CONST_STRING_JUMBO); + jumboCheck(isJumbo, mappedId); + mappedInstructions[mappedAt++] = one.withIndex(mappedId); + } + } + + private static void jumboCheck(boolean isJumbo, int newIndex) { + if (!isJumbo && (newIndex > 0xffff)) { + throw new DexException("Cannot merge new index " + newIndex + + " into a non-jumbo instruction!"); + } + } +} diff --git a/dx/src/com/android/jack/dx/merge/SortableType.java b/dx/src/com/android/jack/dx/merge/SortableType.java new file mode 100644 index 00000000..9435619b --- /dev/null +++ b/dx/src/com/android/jack/dx/merge/SortableType.java @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.merge; + +import com.android.jack.dx.io.ClassDef; +import com.android.jack.dx.io.DexBuffer; + +import java.util.Comparator; + +/** + * Name and structure of a type. Used to order types such that each type is + * preceded by its supertype and implemented interfaces. + */ +final class SortableType { + public static final Comparator NULLS_LAST_ORDER = new Comparator() { + public int compare(SortableType a, SortableType b) { + if (a == b) { + return 0; + } + if (b == null) { + return -1; + } + if (a == null) { + return 1; + } + if (a.depth != b.depth) { + return a.depth - b.depth; + } + return a.getTypeIndex() - b.getTypeIndex(); + } + }; + + private final DexBuffer buffer; + private ClassDef classDef; + private int depth = -1; + + public SortableType(DexBuffer buffer, ClassDef classDef) { + this.buffer = buffer; + this.classDef = classDef; + } + + public DexBuffer getBuffer() { + return buffer; + } + + public ClassDef getClassDef() { + return classDef; + } + + public int getTypeIndex() { + return classDef.getTypeIndex(); + } + + /** + * Assigns this type's depth if the depths of its supertype and implemented + * interfaces are known. Returns false if the depth couldn't be computed + * yet. + */ + public boolean tryAssignDepth(SortableType[] types) { + int max; + if (classDef.getSupertypeIndex() == ClassDef.NO_INDEX) { + max = 0; // this is Object.class or an interface + } else { + SortableType sortableSupertype = types[classDef.getSupertypeIndex()]; + if (sortableSupertype == null) { + max = 1; // unknown, so assume it's a root. + } else if (sortableSupertype.depth == -1) { + return false; + } else { + max = sortableSupertype.depth; + } + } + + for (short interfaceIndex : classDef.getInterfaces()) { + SortableType implemented = types[interfaceIndex]; + if (implemented == null) { + max = Math.max(max, 1); // unknown, so assume it's a root. + } else if (implemented.depth == -1) { + return false; + } else { + max = Math.max(max, implemented.depth); + } + } + + depth = max + 1; + return true; + } + + public boolean isDepthAssigned() { + return depth != -1; + } +} diff --git a/dx/src/com/android/jack/dx/merge/TypeList.java b/dx/src/com/android/jack/dx/merge/TypeList.java new file mode 100644 index 00000000..a34d0002 --- /dev/null +++ b/dx/src/com/android/jack/dx/merge/TypeList.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.merge; + +import com.android.jack.dx.io.DexBuffer; +import com.android.jack.dx.util.Unsigned; + +import java.util.Arrays; + +public final class TypeList implements Comparable { + + public static final TypeList EMPTY = new TypeList(null, new short[0]); + + private final DexBuffer buffer; + private final short[] types; + + public TypeList(DexBuffer buffer, short[] types) { + this.buffer = buffer; + this.types = types; + } + + public short[] getTypes() { + return types; + } + + public int compareTo(TypeList other) { + for (int i = 0; i < types.length && i < other.types.length; i++) { + if (types[i] != other.types[i]) { + return Unsigned.compare(types[i], other.types[i]); + } + } + return Unsigned.compare(types.length, other.types.length); + } + + @Override public String toString() { + StringBuilder result = new StringBuilder(); + result.append("("); + for (int i = 0, typesLength = types.length; i < typesLength; i++) { + result.append(buffer != null ? buffer.typeNames().get(types[i]) : types[i]); + } + result.append(")"); + return result.toString(); + } +} diff --git a/dx/src/com/android/jack/dx/rop/annotation/Annotation.java b/dx/src/com/android/jack/dx/rop/annotation/Annotation.java new file mode 100644 index 00000000..ccf331e1 --- /dev/null +++ b/dx/src/com/android/jack/dx/rop/annotation/Annotation.java @@ -0,0 +1,224 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.rop.annotation; + +import com.android.jack.dx.rop.cst.CstString; +import com.android.jack.dx.rop.cst.CstType; +import com.android.jack.dx.util.MutabilityControl; +import com.android.jack.dx.util.ToHuman; + +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.TreeMap; + +/** + * An annotation on an element of a class. Annotations have an + * associated type and additionally consist of a set of (name, value) + * pairs, where the names are unique. + */ +public final class Annotation extends MutabilityControl + implements Comparable, ToHuman { + /** {@code non-null;} type of the annotation */ + private final CstType type; + + /** {@code non-null;} the visibility of the annotation */ + private final AnnotationVisibility visibility; + + /** {@code non-null;} map from names to {@link NameValuePair} instances */ + private final TreeMap elements; + + /** + * Construct an instance. It initially contains no elements. + * + * @param type {@code non-null;} type of the annotation + * @param visibility {@code non-null;} the visibility of the annotation + */ + public Annotation(CstType type, AnnotationVisibility visibility) { + if (type == null) { + throw new NullPointerException("type == null"); + } + + if (visibility == null) { + throw new NullPointerException("visibility == null"); + } + + this.type = type; + this.visibility = visibility; + this.elements = new TreeMap(); + } + + /** {@inheritDoc} */ + @Override + public boolean equals(Object other) { + if (! (other instanceof Annotation)) { + return false; + } + + Annotation otherAnnotation = (Annotation) other; + + if (! (type.equals(otherAnnotation.type) + && (visibility == otherAnnotation.visibility))) { + return false; + } + + return elements.equals(otherAnnotation.elements); + } + + /** {@inheritDoc} */ + public int hashCode() { + int hash = type.hashCode(); + hash = (hash * 31) + elements.hashCode(); + hash = (hash * 31) + visibility.hashCode(); + return hash; + } + + /** {@inheritDoc} */ + public int compareTo(Annotation other) { + int result = type.compareTo(other.type); + + if (result != 0) { + return result; + } + + result = visibility.compareTo(other.visibility); + + if (result != 0) { + return result; + } + + Iterator thisIter = elements.values().iterator(); + Iterator otherIter = other.elements.values().iterator(); + + while (thisIter.hasNext() && otherIter.hasNext()) { + NameValuePair thisOne = thisIter.next(); + NameValuePair otherOne = otherIter.next(); + + result = thisOne.compareTo(otherOne); + if (result != 0) { + return result; + } + } + + if (thisIter.hasNext()) { + return 1; + } else if (otherIter.hasNext()) { + return -1; + } + + return 0; + } + + /** {@inheritDoc} */ + @Override + public String toString() { + return toHuman(); + } + + /** {@inheritDoc} */ + public String toHuman() { + StringBuilder sb = new StringBuilder(); + + sb.append(visibility.toHuman()); + sb.append("-annotation "); + sb.append(type.toHuman()); + sb.append(" {"); + + boolean first = true; + for (NameValuePair pair : elements.values()) { + if (first) { + first = false; + } else { + sb.append(", "); + } + sb.append(pair.getName().toHuman()); + sb.append(": "); + sb.append(pair.getValue().toHuman()); + } + + sb.append("}"); + return sb.toString(); + } + + /** + * Gets the type of this instance. + * + * @return {@code non-null;} the type + */ + public CstType getType() { + return type; + } + + /** + * Gets the visibility of this instance. + * + * @return {@code non-null;} the visibility + */ + public AnnotationVisibility getVisibility() { + return visibility; + } + + /** + * Put an element into the set of (name, value) pairs for this instance. + * If there is a preexisting element with the same name, it will be + * replaced by this method. + * + * @param pair {@code non-null;} the (name, value) pair to place into this instance + */ + public void put(NameValuePair pair) { + throwIfImmutable(); + + if (pair == null) { + throw new NullPointerException("pair == null"); + } + + elements.put(pair.getName(), pair); + } + + /** + * Add an element to the set of (name, value) pairs for this instance. + * It is an error to call this method if there is a preexisting element + * with the same name. + * + * @param pair {@code non-null;} the (name, value) pair to add to this instance + */ + public void add(NameValuePair pair) { + throwIfImmutable(); + + if (pair == null) { + throw new NullPointerException("pair == null"); + } + + CstString name = pair.getName(); + + if (elements.get(name) != null) { + throw new IllegalArgumentException("name already added: " + name); + } + + elements.put(name, pair); + } + + /** + * Gets the set of name-value pairs contained in this instance. The + * result is always unmodifiable. + * + * @return {@code non-null;} the set of name-value pairs + */ + public Collection getNameValuePairs() { + return Collections.unmodifiableCollection(elements.values()); + } +} diff --git a/dx/src/com/android/jack/dx/rop/annotation/AnnotationVisibility.java b/dx/src/com/android/jack/dx/rop/annotation/AnnotationVisibility.java new file mode 100644 index 00000000..e8ca5045 --- /dev/null +++ b/dx/src/com/android/jack/dx/rop/annotation/AnnotationVisibility.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.rop.annotation; + +import com.android.jack.dx.util.ToHuman; + +/** + * Visibility scope of an annotation. + */ +public enum AnnotationVisibility implements ToHuman { + RUNTIME("runtime"), + BUILD("build"), + SYSTEM("system"), + EMBEDDED("embedded"); + + /** {@code non-null;} the human-oriented string representation */ + private final String human; + + /** + * Constructs an instance. + * + * @param human {@code non-null;} the human-oriented string representation + */ + private AnnotationVisibility(String human) { + this.human = human; + } + + /** {@inheritDoc} */ + public String toHuman() { + return human; + } +} diff --git a/dx/src/com/android/jack/dx/rop/annotation/Annotations.java b/dx/src/com/android/jack/dx/rop/annotation/Annotations.java new file mode 100644 index 00000000..9f9f92b7 --- /dev/null +++ b/dx/src/com/android/jack/dx/rop/annotation/Annotations.java @@ -0,0 +1,213 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.rop.annotation; + +import com.android.jack.dx.rop.cst.CstType; +import com.android.jack.dx.util.MutabilityControl; + +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.TreeMap; + +/** + * List of {@link Annotation} instances. + */ +public final class Annotations extends MutabilityControl + implements Comparable { + /** {@code non-null;} immutable empty instance */ + public static final Annotations EMPTY = new Annotations(); + + static { + EMPTY.setImmutable(); + } + + /** {@code non-null;} map from types to annotations */ + private final TreeMap annotations; + + /** + * Constructs an immutable instance which is the combination of the + * two given instances. The two instances must contain disjoint sets + * of types. + * + * @param a1 {@code non-null;} an instance + * @param a2 {@code non-null;} the other instance + * @return {@code non-null;} the combination + * @throws IllegalArgumentException thrown if there is a duplicate type + */ + public static Annotations combine(Annotations a1, Annotations a2) { + Annotations result = new Annotations(); + + result.addAll(a1); + result.addAll(a2); + result.setImmutable(); + + return result; + } + + /** + * Constructs an immutable instance which is the combination of the + * given instance with the given additional annotation. The latter's + * type must not already appear in the former. + * + * @param annotations {@code non-null;} the instance to augment + * @param annotation {@code non-null;} the additional annotation + * @return {@code non-null;} the combination + * @throws IllegalArgumentException thrown if there is a duplicate type + */ + public static Annotations combine(Annotations annotations, + Annotation annotation) { + Annotations result = new Annotations(); + + result.addAll(annotations); + result.add(annotation); + result.setImmutable(); + + return result; + } + + /** + * Constructs an empty instance. + */ + public Annotations() { + annotations = new TreeMap(); + } + + /** {@inheritDoc} */ + @Override + public int hashCode() { + return annotations.hashCode(); + } + + /** {@inheritDoc} */ + @Override + public boolean equals(Object other) { + if (! (other instanceof Annotations)) { + return false; + } + + Annotations otherAnnotations = (Annotations) other; + + return annotations.equals(otherAnnotations.annotations); + } + + /** {@inheritDoc} */ + public int compareTo(Annotations other) { + Iterator thisIter = annotations.values().iterator(); + Iterator otherIter = other.annotations.values().iterator(); + + while (thisIter.hasNext() && otherIter.hasNext()) { + Annotation thisOne = thisIter.next(); + Annotation otherOne = otherIter.next(); + + int result = thisOne.compareTo(otherOne); + if (result != 0) { + return result; + } + } + + if (thisIter.hasNext()) { + return 1; + } else if (otherIter.hasNext()) { + return -1; + } + + return 0; + } + + /** {@inheritDoc} */ + public String toString() { + StringBuilder sb = new StringBuilder(); + boolean first = true; + + sb.append("annotations{"); + + for (Annotation a : annotations.values()) { + if (first) { + first = false; + } else { + sb.append(", "); + } + sb.append(a.toHuman()); + } + + sb.append("}"); + return sb.toString(); + } + + /** + * Gets the number of elements in this instance. + * + * @return {@code >= 0;} the size + */ + public int size() { + return annotations.size(); + } + + /** + * Adds an element to this instance. There must not already be an + * element of the same type. + * + * @param annotation {@code non-null;} the element to add + * @throws IllegalArgumentException thrown if there is a duplicate type + */ + public void add(Annotation annotation) { + throwIfImmutable(); + + if (annotation == null) { + throw new NullPointerException("annotation == null"); + } + + CstType type = annotation.getType(); + + if (annotations.containsKey(type)) { + throw new IllegalArgumentException("duplicate type: " + + type.toHuman()); + } + + annotations.put(type, annotation); + } + + /** + * Adds all of the elements of the given instance to this one. The + * instances must not have any duplicate types. + * + * @param toAdd {@code non-null;} the annotations to add + * @throws IllegalArgumentException thrown if there is a duplicate type + */ + public void addAll(Annotations toAdd) { + throwIfImmutable(); + + if (toAdd == null) { + throw new NullPointerException("toAdd == null"); + } + + for (Annotation a : toAdd.annotations.values()) { + add(a); + } + } + + /** + * Gets the set of annotations contained in this instance. The + * result is always unmodifiable. + * + * @return {@code non-null;} the set of annotations + */ + public Collection getAnnotations() { + return Collections.unmodifiableCollection(annotations.values()); + } +} diff --git a/dx/src/com/android/jack/dx/rop/annotation/AnnotationsList.java b/dx/src/com/android/jack/dx/rop/annotation/AnnotationsList.java new file mode 100644 index 00000000..a013673a --- /dev/null +++ b/dx/src/com/android/jack/dx/rop/annotation/AnnotationsList.java @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.rop.annotation; + +import com.android.jack.dx.util.FixedSizeList; + +/** + * List of {@link Annotations} instances. + */ +public final class AnnotationsList + extends FixedSizeList { + /** {@code non-null;} immutable empty instance */ + public static final AnnotationsList EMPTY = new AnnotationsList(0); + + /** + * Constructs an immutable instance which is the combination of + * the two given instances. The two instances must each have the + * same number of elements, and each pair of elements must contain + * disjoint sets of types. + * + * @param list1 {@code non-null;} an instance + * @param list2 {@code non-null;} the other instance + * @return {@code non-null;} the combination + */ + public static AnnotationsList combine(AnnotationsList list1, + AnnotationsList list2) { + int size = list1.size(); + + if (size != list2.size()) { + throw new IllegalArgumentException("list1.size() != list2.size()"); + } + + AnnotationsList result = new AnnotationsList(size); + + for (int i = 0; i < size; i++) { + Annotations a1 = list1.get(i); + Annotations a2 = list2.get(i); + result.set(i, Annotations.combine(a1, a2)); + } + + result.setImmutable(); + return result; + } + + /** + * Constructs an instance. All indices initially contain {@code null}. + * + * @param size the size of the list + */ + public AnnotationsList(int size) { + super(size); + } + + /** + * Gets the element at the given index. It is an error to call + * this with the index for an element which was never set; if you + * do that, this will throw {@code NullPointerException}. + * + * @param n {@code >= 0, < size();} which index + * @return {@code non-null;} element at that index + */ + public Annotations get(int n) { + return (Annotations) get0(n); + } + + /** + * Sets the element at the given index. The given element must be + * immutable. + * + * @param n {@code >= 0, < size();} which index + * @param a {@code null-ok;} the element to set at {@code n} + */ + public void set(int n, Annotations a) { + a.throwIfMutable(); + set0(n, a); + } +} diff --git a/dx/src/com/android/jack/dx/rop/annotation/NameValuePair.java b/dx/src/com/android/jack/dx/rop/annotation/NameValuePair.java new file mode 100644 index 00000000..7661fbf2 --- /dev/null +++ b/dx/src/com/android/jack/dx/rop/annotation/NameValuePair.java @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.rop.annotation; + +import com.android.jack.dx.rop.cst.Constant; +import com.android.jack.dx.rop.cst.CstString; + +/** + * A (name, value) pair. These are used as the contents of an annotation. + */ +public final class NameValuePair implements Comparable { + /** {@code non-null;} the name */ + private final CstString name; + + /** {@code non-null;} the value */ + private final Constant value; + + /** + * Construct an instance. + * + * @param name {@code non-null;} the name + * @param value {@code non-null;} the value + */ + public NameValuePair(CstString name, Constant value) { + if (name == null) { + throw new NullPointerException("name == null"); + } + + if (value == null) { + throw new NullPointerException("value == null"); + } + + this.name = name; + this.value = value; + } + + /** {@inheritDoc} */ + public String toString() { + return name.toHuman() + ":" + value; + } + + /** {@inheritDoc} */ + public int hashCode() { + return name.hashCode() * 31 + value.hashCode(); + } + + /** {@inheritDoc} */ + public boolean equals(Object other) { + if (! (other instanceof NameValuePair)) { + return false; + } + + NameValuePair otherPair = (NameValuePair) other; + + return name.equals(otherPair.name) + && value.equals(otherPair.value); + } + + /** + * {@inheritDoc} + * + *

Instances of this class compare in name-major and value-minor + * order.

+ */ + public int compareTo(NameValuePair other) { + int result = name.compareTo(other.name); + + if (result != 0) { + return result; + } + + return value.compareTo(other.value); + } + + /** + * Gets the name. + * + * @return {@code non-null;} the name + */ + public CstString getName() { + return name; + } + + /** + * Gets the value. + * + * @return {@code non-null;} the value + */ + public Constant getValue() { + return value; + } +} diff --git a/dx/src/com/android/jack/dx/rop/code/AccessFlags.java b/dx/src/com/android/jack/dx/rop/code/AccessFlags.java new file mode 100644 index 00000000..95385bd1 --- /dev/null +++ b/dx/src/com/android/jack/dx/rop/code/AccessFlags.java @@ -0,0 +1,406 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.rop.code; + +import com.android.jack.dx.util.Hex; + +/** + * Constants used as "access flags" in various places in classes, and + * related utilities. Although, at the rop layer, flags are generally + * ignored, this is the layer of communication, and as such, this + * package is where these definitions belong. The flag definitions are + * identical to Java access flags, but {@code ACC_SUPER} isn't + * used at all in translated code, and {@code ACC_SYNCHRONIZED} + * is only used in a very limited way. + */ +public final class AccessFlags { + /** public member / class */ + public static final int ACC_PUBLIC = 0x0001; + + /** private member */ + public static final int ACC_PRIVATE = 0x0002; + + /** protected member */ + public static final int ACC_PROTECTED = 0x0004; + + /** static member */ + public static final int ACC_STATIC = 0x0008; + + /** final member / class */ + public static final int ACC_FINAL = 0x0010; + + /** + * synchronized method; only valid in dex files for {@code native} + * methods + */ + public static final int ACC_SYNCHRONIZED = 0x0020; + + /** + * class with new-style {@code invokespecial} for superclass + * method access + */ + public static final int ACC_SUPER = 0x0020; + + /** volatile field */ + public static final int ACC_VOLATILE = 0x0040; + + /** bridge method (generated) */ + public static final int ACC_BRIDGE = 0x0040; + + /** transient field */ + public static final int ACC_TRANSIENT = 0x0080; + + /** varargs method */ + public static final int ACC_VARARGS = 0x0080; + + /** native method */ + public static final int ACC_NATIVE = 0x0100; + + /** "class" is in fact an public static final interface */ + public static final int ACC_INTERFACE = 0x0200; + + /** abstract method / class */ + public static final int ACC_ABSTRACT = 0x0400; + + /** + * method with strict floating point ({@code strictfp}) + * behavior + */ + public static final int ACC_STRICT = 0x0800; + + /** synthetic member */ + public static final int ACC_SYNTHETIC = 0x1000; + + /** class is an annotation type */ + public static final int ACC_ANNOTATION = 0x2000; + + /** + * class is an enumerated type; field is an element of an enumerated + * type + */ + public static final int ACC_ENUM = 0x4000; + + /** method is a constructor */ + public static final int ACC_CONSTRUCTOR = 0x10000; + + /** + * method was declared {@code synchronized}; has no effect on + * execution (other than inspecting this flag, per se) + */ + public static final int ACC_DECLARED_SYNCHRONIZED = 0x20000; + + /** flags defined on classes */ + public static final int CLASS_FLAGS = + ACC_PUBLIC | ACC_FINAL | ACC_SUPER | ACC_INTERFACE | ACC_ABSTRACT | + ACC_SYNTHETIC | ACC_ANNOTATION | ACC_ENUM; + + /** flags defined on inner classes */ + public static final int INNER_CLASS_FLAGS = + ACC_PUBLIC | ACC_PRIVATE | ACC_PROTECTED | ACC_STATIC | ACC_FINAL | + ACC_INTERFACE | ACC_ABSTRACT | ACC_SYNTHETIC | ACC_ANNOTATION | + ACC_ENUM; + + /** flags defined on fields */ + public static final int FIELD_FLAGS = + ACC_PUBLIC | ACC_PRIVATE | ACC_PROTECTED | ACC_STATIC | ACC_FINAL | + ACC_VOLATILE | ACC_TRANSIENT | ACC_SYNTHETIC | ACC_ENUM; + + /** flags defined on methods */ + public static final int METHOD_FLAGS = + ACC_PUBLIC | ACC_PRIVATE | ACC_PROTECTED | ACC_STATIC | ACC_FINAL | + ACC_SYNCHRONIZED | ACC_BRIDGE | ACC_VARARGS | ACC_NATIVE | + ACC_ABSTRACT | ACC_STRICT | ACC_SYNTHETIC | ACC_CONSTRUCTOR | + ACC_DECLARED_SYNCHRONIZED; + + /** indicates conversion of class flags */ + private static final int CONV_CLASS = 1; + + /** indicates conversion of field flags */ + private static final int CONV_FIELD = 2; + + /** indicates conversion of method flags */ + private static final int CONV_METHOD = 3; + + /** + * This class is uninstantiable. + */ + private AccessFlags() { + // This space intentionally left blank. + } + + /** + * Returns a human-oriented string representing the given access flags, + * as defined on classes (not fields or methods). + * + * @param flags the flags + * @return {@code non-null;} human-oriented string + */ + public static String classString(int flags) { + return humanHelper(flags, CLASS_FLAGS, CONV_CLASS); + } + + /** + * Returns a human-oriented string representing the given access flags, + * as defined on inner classes. + * + * @param flags the flags + * @return {@code non-null;} human-oriented string + */ + public static String innerClassString(int flags) { + return humanHelper(flags, INNER_CLASS_FLAGS, CONV_CLASS); + } + + /** + * Returns a human-oriented string representing the given access flags, + * as defined on fields (not classes or methods). + * + * @param flags the flags + * @return {@code non-null;} human-oriented string + */ + public static String fieldString(int flags) { + return humanHelper(flags, FIELD_FLAGS, CONV_FIELD); + } + + /** + * Returns a human-oriented string representing the given access flags, + * as defined on methods (not classes or fields). + * + * @param flags the flags + * @return {@code non-null;} human-oriented string + */ + public static String methodString(int flags) { + return humanHelper(flags, METHOD_FLAGS, CONV_METHOD); + } + + /** + * Returns whether the flag {@code ACC_PUBLIC} is on in the given + * flags. + * + * @param flags the flags to check + * @return the value of the {@code ACC_PUBLIC} flag + */ + public static boolean isPublic(int flags) { + return (flags & ACC_PUBLIC) != 0; + } + + /** + * Returns whether the flag {@code ACC_PROTECTED} is on in the given + * flags. + * + * @param flags the flags to check + * @return the value of the {@code ACC_PROTECTED} flag + */ + public static boolean isProtected(int flags) { + return (flags & ACC_PROTECTED) != 0; + } + + /** + * Returns whether the flag {@code ACC_PRIVATE} is on in the given + * flags. + * + * @param flags the flags to check + * @return the value of the {@code ACC_PRIVATE} flag + */ + public static boolean isPrivate(int flags) { + return (flags & ACC_PRIVATE) != 0; + } + + /** + * Returns whether the flag {@code ACC_STATIC} is on in the given + * flags. + * + * @param flags the flags to check + * @return the value of the {@code ACC_STATIC} flag + */ + public static boolean isStatic(int flags) { + return (flags & ACC_STATIC) != 0; + } + + /** + * Returns whether the flag {@code ACC_CONSTRUCTOR} is on in + * the given flags. + * + * @param flags the flags to check + * @return the value of the {@code ACC_CONSTRUCTOR} flag + */ + public static boolean isConstructor(int flags) { + return (flags & ACC_CONSTRUCTOR) != 0; + } + + /** + * Returns whether the flag {@code ACC_INTERFACE} is on in + * the given flags. + * + * @param flags the flags to check + * @return the value of the {@code ACC_INTERFACE} flag + */ + public static boolean isInterface(int flags) { + return (flags & ACC_INTERFACE) != 0; + } + + /** + * Returns whether the flag {@code ACC_SYNCHRONIZED} is on in + * the given flags. + * + * @param flags the flags to check + * @return the value of the {@code ACC_SYNCHRONIZED} flag + */ + public static boolean isSynchronized(int flags) { + return (flags & ACC_SYNCHRONIZED) != 0; + } + + /** + * Returns whether the flag {@code ACC_ABSTRACT} is on in the given + * flags. + * + * @param flags the flags to check + * @return the value of the {@code ACC_ABSTRACT} flag + */ + public static boolean isAbstract(int flags) { + return (flags & ACC_ABSTRACT) != 0; + } + + /** + * Returns whether the flag {@code ACC_NATIVE} is on in the given + * flags. + * + * @param flags the flags to check + * @return the value of the {@code ACC_NATIVE} flag + */ + public static boolean isNative(int flags) { + return (flags & ACC_NATIVE) != 0; + } + + /** + * Returns whether the flag {@code ACC_ANNOTATION} is on in the given + * flags. + * + * @param flags the flags to check + * @return the value of the {@code ACC_ANNOTATION} flag + */ + public static boolean isAnnotation(int flags) { + return (flags & ACC_ANNOTATION) != 0; + } + + /** + * Returns whether the flag {@code ACC_DECLARED_SYNCHRONIZED} is + * on in the given flags. + * + * @param flags the flags to check + * @return the value of the {@code ACC_DECLARED_SYNCHRONIZED} flag + */ + public static boolean isDeclaredSynchronized(int flags) { + return (flags & ACC_DECLARED_SYNCHRONIZED) != 0; + } + + /** + * Returns whether the flag {@code ACC_ENUM} is on in the given flags. + * + * @param flags the flags to check + * @return the value of the {@code ACC_ENUM} flag + */ + public static boolean isEnum(int flags) { + return (flags & ACC_ENUM) != 0; + } + + /** + * Helper to return a human-oriented string representing the given + * access flags. + * + * @param flags the defined flags + * @param mask mask for the "defined" bits + * @param what what the flags represent (one of {@code CONV_*}) + * @return {@code non-null;} human-oriented string + */ + private static String humanHelper(int flags, int mask, int what) { + StringBuffer sb = new StringBuffer(80); + int extra = flags & ~mask; + + flags &= mask; + + if ((flags & ACC_PUBLIC) != 0) { + sb.append("|public"); + } + if ((flags & ACC_PRIVATE) != 0) { + sb.append("|private"); + } + if ((flags & ACC_PROTECTED) != 0) { + sb.append("|protected"); + } + if ((flags & ACC_STATIC) != 0) { + sb.append("|static"); + } + if ((flags & ACC_FINAL) != 0) { + sb.append("|final"); + } + if ((flags & ACC_SYNCHRONIZED) != 0) { + if (what == CONV_CLASS) { + sb.append("|super"); + } else { + sb.append("|synchronized"); + } + } + if ((flags & ACC_VOLATILE) != 0) { + if (what == CONV_METHOD) { + sb.append("|bridge"); + } else { + sb.append("|volatile"); + } + } + if ((flags & ACC_TRANSIENT) != 0) { + if (what == CONV_METHOD) { + sb.append("|varargs"); + } else { + sb.append("|transient"); + } + } + if ((flags & ACC_NATIVE) != 0) { + sb.append("|native"); + } + if ((flags & ACC_INTERFACE) != 0) { + sb.append("|interface"); + } + if ((flags & ACC_ABSTRACT) != 0) { + sb.append("|abstract"); + } + if ((flags & ACC_STRICT) != 0) { + sb.append("|strictfp"); + } + if ((flags & ACC_SYNTHETIC) != 0) { + sb.append("|synthetic"); + } + if ((flags & ACC_ANNOTATION) != 0) { + sb.append("|annotation"); + } + if ((flags & ACC_ENUM) != 0) { + sb.append("|enum"); + } + if ((flags & ACC_CONSTRUCTOR) != 0) { + sb.append("|constructor"); + } + if ((flags & ACC_DECLARED_SYNCHRONIZED) != 0) { + sb.append("|declared_synchronized"); + } + + if ((extra != 0) || (sb.length() == 0)) { + sb.append('|'); + sb.append(Hex.u2(extra)); + } + + return sb.substring(1); + } +} diff --git a/dx/src/com/android/jack/dx/rop/code/BasicBlock.java b/dx/src/com/android/jack/dx/rop/code/BasicBlock.java new file mode 100644 index 00000000..c87fc524 --- /dev/null +++ b/dx/src/com/android/jack/dx/rop/code/BasicBlock.java @@ -0,0 +1,281 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.rop.code; + +import com.android.jack.dx.rop.type.TypeList; +import com.android.jack.dx.util.Hex; +import com.android.jack.dx.util.IntList; +import com.android.jack.dx.util.LabeledItem; + +/** + * Basic block of register-based instructions. + */ +public final class BasicBlock implements LabeledItem { + /** {@code >= 0;} target label for this block */ + private final int label; + + /** {@code non-null;} list of instructions in this block */ + private final InsnList insns; + + /** + * {@code non-null;} full list of successors that this block may + * branch to + */ + private final IntList successors; + + /** + * {@code >= -1;} the primary / standard-flow / "default" successor, or + * {@code -1} if this block has no successors (that is, it + * exits the function/method) + */ + private final int primarySuccessor; + + /** + * Constructs an instance. The predecessor set is set to {@code null}. + * + * @param label {@code >= 0;} target label for this block + * @param insns {@code non-null;} list of instructions in this block + * @param successors {@code non-null;} full list of successors that this + * block may branch to + * @param primarySuccessor {@code >= -1;} the primary / standard-flow / + * "default" successor, or {@code -1} if this block has no + * successors (that is, it exits the function/method or is an + * unconditional throw) + */ + public BasicBlock(int label, InsnList insns, IntList successors, + int primarySuccessor) { + if (label < 0) { + throw new IllegalArgumentException("label < 0"); + } + + try { + insns.throwIfMutable(); + } catch (NullPointerException ex) { + // Elucidate exception. + throw new NullPointerException("insns == null"); + } + + int sz = insns.size(); + + if (sz == 0) { + throw new IllegalArgumentException("insns.size() == 0"); + } + + for (int i = sz - 2; i >= 0; i--) { + Rop one = insns.get(i).getOpcode(); + if (one.getBranchingness() != Rop.BRANCH_NONE) { + throw new IllegalArgumentException("insns[" + i + "] is a " + + "branch or can throw"); + } + } + + Insn lastInsn = insns.get(sz - 1); + if (lastInsn.getOpcode().getBranchingness() == Rop.BRANCH_NONE) { + throw new IllegalArgumentException("insns does not end with " + + "a branch or throwing " + + "instruction"); + } + + try { + successors.throwIfMutable(); + } catch (NullPointerException ex) { + // Elucidate exception. + throw new NullPointerException("successors == null"); + } + + if (primarySuccessor < -1) { + throw new IllegalArgumentException("primarySuccessor < -1"); + } + + if (primarySuccessor >= 0 && !successors.contains(primarySuccessor)) { + throw new IllegalArgumentException( + "primarySuccessor " + primarySuccessor + " not in successors " + successors); + } + + this.label = label; + this.insns = insns; + this.successors = successors; + this.primarySuccessor = primarySuccessor; + } + + /** + * {@inheritDoc} + * + * Instances of this class compare by identity. That is, + * {@code x.equals(y)} is only true if {@code x == y}. + */ + @Override + public boolean equals(Object other) { + return (this == other); + } + + /** + * {@inheritDoc} + * + * Return the identity hashcode of this instance. This is proper, + * since instances of this class compare by identity (see {@link #equals}). + */ + @Override + public int hashCode() { + return System.identityHashCode(this); + } + + /** + * Gets the target label of this block. + * + * @return {@code >= 0;} the label + */ + public int getLabel() { + return label; + } + + /** + * Gets the list of instructions inside this block. + * + * @return {@code non-null;} the instruction list + */ + public InsnList getInsns() { + return insns; + } + + /** + * Gets the list of successors that this block may branch to. + * + * @return {@code non-null;} the successors list + */ + public IntList getSuccessors() { + return successors; + } + + /** + * Gets the primary successor of this block. + * + * @return {@code >= -1;} the primary successor, or {@code -1} if this + * block has no successors at all + */ + public int getPrimarySuccessor() { + return primarySuccessor; + } + + /** + * Gets the secondary successor of this block. It is only valid to call + * this method on blocks that have exactly two successors. + * + * @return {@code >= 0;} the secondary successor + */ + public int getSecondarySuccessor() { + if (successors.size() != 2) { + throw new UnsupportedOperationException( + "block doesn't have exactly two successors"); + } + + int succ = successors.get(0); + if (succ == primarySuccessor) { + succ = successors.get(1); + } + + return succ; + } + + /** + * Gets the first instruction of this block. This is just a + * convenient shorthand for {@code getInsns().get(0)}. + * + * @return {@code non-null;} the first instruction + */ + public Insn getFirstInsn() { + return insns.get(0); + } + + /** + * Gets the last instruction of this block. This is just a + * convenient shorthand for {@code getInsns().getLast()}. + * + * @return {@code non-null;} the last instruction + */ + public Insn getLastInsn() { + return insns.getLast(); + } + + /** + * Returns whether this block might throw an exception. This is + * just a convenient shorthand for {@code getLastInsn().canThrow()}. + * + * @return {@code true} iff this block might throw an + * exception + */ + public boolean canThrow() { + return insns.getLast().canThrow(); + } + + /** + * Returns whether this block has any associated exception handlers. + * This is just a shorthand for inspecting the last instruction in + * the block to see if it could throw, and if so, whether it in fact + * has any associated handlers. + * + * @return {@code true} iff this block has any associated + * exception handlers + */ + public boolean hasExceptionHandlers() { + Insn lastInsn = insns.getLast(); + return lastInsn.getCatches().size() != 0; + } + + /** + * Returns the exception handler types associated with this block, + * if any. This is just a shorthand for inspecting the last + * instruction in the block to see if it could throw, and if so, + * grabbing the catch list out of it. If not, this returns an + * empty list (not {@code null}). + * + * @return {@code non-null;} the exception handler types associated with + * this block + */ + public TypeList getExceptionHandlerTypes() { + Insn lastInsn = insns.getLast(); + return lastInsn.getCatches(); + } + + /** + * Returns an instance that is identical to this one, except that + * the registers in each instruction are offset by the given + * amount. + * + * @param delta the amount to offset register numbers by + * @return {@code non-null;} an appropriately-constructed instance + */ + public BasicBlock withRegisterOffset(int delta) { + return new BasicBlock(label, insns.withRegisterOffset(delta), + successors, primarySuccessor); + } + + public String toString() { + return '{' + Hex.u2(label) + '}'; + } + + /** + * BasicBlock visitor interface + */ + public interface Visitor { + /** + * Visits a basic block + * @param b block visited + */ + public void visitBlock (BasicBlock b); + } +} diff --git a/dx/src/com/android/jack/dx/rop/code/BasicBlockList.java b/dx/src/com/android/jack/dx/rop/code/BasicBlockList.java new file mode 100644 index 00000000..3d5ddcac --- /dev/null +++ b/dx/src/com/android/jack/dx/rop/code/BasicBlockList.java @@ -0,0 +1,396 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.rop.code; + +import com.android.jack.dx.rop.type.StdTypeList; +import com.android.jack.dx.rop.type.TypeList; +import com.android.jack.dx.util.Hex; +import com.android.jack.dx.util.IntList; +import com.android.jack.dx.util.LabeledList; + +/** + * List of {@link BasicBlock} instances. + */ +public final class BasicBlockList extends LabeledList { + /** + * {@code >= -1;} the count of registers required by this method or + * {@code -1} if not yet calculated + */ + private int regCount; + + /** + * Constructs an instance. All indices initially contain {@code null}, + * and the first-block label is initially {@code -1}. + * + * @param size the size of the list + */ + public BasicBlockList(int size) { + super(size); + + regCount = -1; + } + + /** + * Constructs a mutable copy for {@code getMutableCopy()}. + * + * @param old block to copy + */ + private BasicBlockList(BasicBlockList old) { + super(old); + regCount = old.regCount; + } + + + /** + * Gets the element at the given index. It is an error to call + * this with the index for an element which was never set; if you + * do that, this will throw {@code NullPointerException}. + * + * @param n {@code >= 0, < size();} which index + * @return {@code non-null;} element at that index + */ + public BasicBlock get(int n) { + return (BasicBlock) get0(n); + } + + /** + * Sets the basic block at the given index. + * + * @param n {@code >= 0, < size();} which index + * @param bb {@code null-ok;} the element to set at {@code n} + */ + public void set(int n, BasicBlock bb) { + super.set(n, bb); + + // Reset regCount, since it will need to be recalculated. + regCount = -1; + } + + /** + * Returns how many registers this method requires. This is simply + * the maximum of register-number-plus-category referred to by this + * instance's instructions (indirectly through {@link BasicBlock} + * instances). + * + * @return {@code >= 0;} the register count + */ + public int getRegCount() { + if (regCount == -1) { + RegCountVisitor visitor = new RegCountVisitor(); + forEachInsn(visitor); + regCount = visitor.getRegCount(); + } + + return regCount; + } + + /** + * Gets the total instruction count for this instance. This is the + * sum of the instruction counts of each block. + * + * @return {@code >= 0;} the total instruction count + */ + public int getInstructionCount() { + int sz = size(); + int result = 0; + + for (int i = 0; i < sz; i++) { + BasicBlock one = (BasicBlock) getOrNull0(i); + if (one != null) { + result += one.getInsns().size(); + } + } + + return result; + } + + /** + * Gets the total instruction count for this instance, ignoring + * mark-local instructions which are not actually emitted. + * + * @return {@code >= 0;} the total instruction count + */ + public int getEffectiveInstructionCount() { + int sz = size(); + int result = 0; + + for (int i = 0; i < sz; i++) { + BasicBlock one = (BasicBlock) getOrNull0(i); + if (one != null) { + InsnList insns = one.getInsns(); + int insnsSz = insns.size(); + + for (int j = 0; j < insnsSz; j++) { + Insn insn = insns.get(j); + + if (insn.getOpcode().getOpcode() != RegOps.MARK_LOCAL) { + result++; + } + } + } + } + + return result; + } + + /** + * Gets the first block in the list with the given label, if any. + * + * @param label {@code >= 0;} the label to look for + * @return {@code non-null;} the so-labelled block + * @throws IllegalArgumentException thrown if the label isn't found + */ + public BasicBlock labelToBlock(int label) { + int idx = indexOfLabel(label); + + if (idx < 0) { + throw new IllegalArgumentException("no such label: " + + Hex.u2(label)); + } + + return get(idx); + } + + /** + * Visits each instruction of each block in the list, in order. + * + * @param visitor {@code non-null;} visitor to use + */ + public void forEachInsn(Insn.Visitor visitor) { + int sz = size(); + + for (int i = 0; i < sz; i++) { + BasicBlock one = get(i); + InsnList insns = one.getInsns(); + insns.forEach(visitor); + } + } + + /** + * Returns an instance that is identical to this one, except that + * the registers in each instruction are offset by the given + * amount. Mutability of the result is inherited from the + * original. + * + * @param delta the amount to offset register numbers by + * @return {@code non-null;} an appropriately-constructed instance + */ + public BasicBlockList withRegisterOffset(int delta) { + int sz = size(); + BasicBlockList result = new BasicBlockList(sz); + + for (int i = 0; i < sz; i++) { + BasicBlock one = (BasicBlock) get0(i); + if (one != null) { + result.set(i, one.withRegisterOffset(delta)); + } + } + + if (isImmutable()) { + result.setImmutable(); + } + + return result; + } + + /** + * Returns a mutable copy of this list. + * + * @return {@code non-null;} an appropriately-constructed instance + */ + public BasicBlockList getMutableCopy() { + return new BasicBlockList(this); + } + + /** + * Gets the preferred successor for the given block. If the block + * only has one successor, then that is the preferred successor. + * Otherwise, if the block has a primay successor, then that is + * the preferred successor. If the block has no successors, then + * this returns {@code null}. + * + * @param block {@code non-null;} the block in question + * @return {@code null-ok;} the preferred successor, if any + */ + public BasicBlock preferredSuccessorOf(BasicBlock block) { + int primarySuccessor = block.getPrimarySuccessor(); + IntList successors = block.getSuccessors(); + int succSize = successors.size(); + + switch (succSize) { + case 0: { + return null; + } + case 1: { + return labelToBlock(successors.get(0)); + } + } + + if (primarySuccessor != -1) { + return labelToBlock(primarySuccessor); + } else { + return labelToBlock(successors.get(0)); + } + } + + /** + * Compares the catches of two blocks for equality. This includes + * both the catch types and target labels. + * + * @param block1 {@code non-null;} one block to compare + * @param block2 {@code non-null;} the other block to compare + * @return {@code true} if the two blocks' non-primary successors + * are identical + */ + public boolean catchesEqual(BasicBlock block1, BasicBlock block2) { + TypeList catches1 = block1.getExceptionHandlerTypes(); + TypeList catches2 = block2.getExceptionHandlerTypes(); + + if (!StdTypeList.equalContents(catches1, catches2)) { + return false; + } + + IntList succ1 = block1.getSuccessors(); + IntList succ2 = block2.getSuccessors(); + int size = succ1.size(); // Both are guaranteed to be the same size. + + int primary1 = block1.getPrimarySuccessor(); + int primary2 = block2.getPrimarySuccessor(); + + if (((primary1 == -1) || (primary2 == -1)) + && (primary1 != primary2)) { + /* + * For the current purpose, both blocks in question must + * either both have a primary or both not have a primary to + * be considered equal, and it turns out here that that's not + * the case. + */ + return false; + } + + for (int i = 0; i < size; i++) { + int label1 = succ1.get(i); + int label2 = succ2.get(i); + + if (label1 == primary1) { + /* + * It should be the case that block2's primary is at the + * same index. If not, we consider the blocks unequal for + * the current purpose. + */ + if (label2 != primary2) { + return false; + } + continue; + } + + if (label1 != label2) { + return false; + } + } + + return true; + } + + /** + * Instruction visitor class for counting registers used. + */ + private static class RegCountVisitor + implements Insn.Visitor { + /** {@code >= 0;} register count in-progress */ + private int regCount; + + /** + * Constructs an instance. + */ + public RegCountVisitor() { + regCount = 0; + } + + /** + * Gets the register count. + * + * @return {@code >= 0;} the count + */ + public int getRegCount() { + return regCount; + } + + /** {@inheritDoc} */ + public void visitPlainInsn(PlainInsn insn) { + visit(insn); + } + + /** {@inheritDoc} */ + public void visitPlainCstInsn(PlainCstInsn insn) { + visit(insn); + } + + /** {@inheritDoc} */ + public void visitSwitchInsn(SwitchInsn insn) { + visit(insn); + } + + /** {@inheritDoc} */ + public void visitThrowingCstInsn(ThrowingCstInsn insn) { + visit(insn); + } + + /** {@inheritDoc} */ + public void visitThrowingInsn(ThrowingInsn insn) { + visit(insn); + } + + /** {@inheritDoc} */ + public void visitFillArrayDataInsn(FillArrayDataInsn insn) { + visit(insn); + } + + /** + * Helper for all the {@code visit*} methods. + * + * @param insn {@code non-null;} instruction being visited + */ + private void visit(Insn insn) { + RegisterSpec result = insn.getResult(); + + if (result != null) { + processReg(result); + } + + RegisterSpecList sources = insn.getSources(); + int sz = sources.size(); + + for (int i = 0; i < sz; i++) { + processReg(sources.get(i)); + } + } + + /** + * Processes the given register spec. + * + * @param spec {@code non-null;} the register spec + */ + private void processReg(RegisterSpec spec) { + int reg = spec.getNextReg(); + + if (reg > regCount) { + regCount = reg; + } + } + } +} diff --git a/dx/src/com/android/jack/dx/rop/code/ConservativeTranslationAdvice.java b/dx/src/com/android/jack/dx/rop/code/ConservativeTranslationAdvice.java new file mode 100644 index 00000000..6a4ee227 --- /dev/null +++ b/dx/src/com/android/jack/dx/rop/code/ConservativeTranslationAdvice.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.rop.code; + +/** + * Implementation of {@link TranslationAdvice} which conservatively answers + * {@code false} to all methods. + */ +public final class ConservativeTranslationAdvice + implements TranslationAdvice { + /** {@code non-null;} standard instance of this class */ + public static final ConservativeTranslationAdvice THE_ONE = + new ConservativeTranslationAdvice(); + + /** + * This class is not publicly instantiable. Use {@link #THE_ONE}. + */ + private ConservativeTranslationAdvice() { + // This space intentionally left blank. + } + + /** {@inheritDoc} */ + public boolean hasConstantOperation(Rop opcode, + RegisterSpec sourceA, RegisterSpec sourceB) { + return false; + } + + /** {@inheritDoc} */ + public boolean requiresSourcesInOrder(Rop opcode, + RegisterSpecList sources) { + return false; + } + + /** {@inheritDoc} */ + public int getMaxOptimalRegisterCount() { + return Integer.MAX_VALUE; + } +} diff --git a/dx/src/com/android/jack/dx/rop/code/CstInsn.java b/dx/src/com/android/jack/dx/rop/code/CstInsn.java new file mode 100644 index 00000000..4e5a7fee --- /dev/null +++ b/dx/src/com/android/jack/dx/rop/code/CstInsn.java @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.rop.code; + +import com.android.jack.dx.rop.cst.Constant; + +/** + * Instruction which contains an explicit reference to a constant. + */ +public abstract class CstInsn + extends Insn { + /** {@code non-null;} the constant */ + private final Constant cst; + + /** + * Constructs an instance. + * + * @param opcode {@code non-null;} the opcode + * @param position {@code non-null;} source position + * @param result {@code null-ok;} spec for the result, if any + * @param sources {@code non-null;} specs for all the sources + * @param cst {@code non-null;} constant + */ + public CstInsn(Rop opcode, SourcePosition position, RegisterSpec result, + RegisterSpecList sources, Constant cst) { + super(opcode, position, result, sources); + + if (cst == null) { + throw new NullPointerException("cst == null"); + } + + this.cst = cst; + } + + /** {@inheritDoc} */ + @Override + public String getInlineString() { + return cst.toHuman(); + } + + /** + * Gets the constant. + * + * @return {@code non-null;} the constant + */ + public Constant getConstant() { + return cst; + } + + /** {@inheritDoc} */ + @Override + public boolean contentEquals(Insn b) { + /* + * The cast (CstInsn)b below should always succeed since + * Insn.contentEquals compares classes of this and b. + */ + return super.contentEquals(b) + && cst.equals(((CstInsn)b).getConstant()); + } +} diff --git a/dx/src/com/android/jack/dx/rop/code/DexTranslationAdvice.java b/dx/src/com/android/jack/dx/rop/code/DexTranslationAdvice.java new file mode 100644 index 00000000..4654a393 --- /dev/null +++ b/dx/src/com/android/jack/dx/rop/code/DexTranslationAdvice.java @@ -0,0 +1,130 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.rop.code; + +import com.android.jack.dx.rop.cst.CstInteger; +import com.android.jack.dx.rop.type.Type; + +/** + * Implementation of {@link TranslationAdvice} which represents what + * the dex format will be able to represent. + */ +public final class DexTranslationAdvice + implements TranslationAdvice { + /** {@code non-null;} standard instance of this class */ + public static final DexTranslationAdvice THE_ONE = + new DexTranslationAdvice(); + + /** debug advice for disabling invoke-range optimization */ + public static final DexTranslationAdvice NO_SOURCES_IN_ORDER = + new DexTranslationAdvice(true); + + /** + * The minimum source width, in register units, for an invoke + * instruction that requires its sources to be in order and contiguous. + */ + private static final int MIN_INVOKE_IN_ORDER = 6; + + /** when true: always returns false for requiresSourcesInOrder */ + private final boolean disableSourcesInOrder; + + /** + * This class is not publicly instantiable. Use {@link #THE_ONE}. + */ + private DexTranslationAdvice() { + disableSourcesInOrder = false; + } + + private DexTranslationAdvice(boolean disableInvokeRange) { + this.disableSourcesInOrder = disableInvokeRange; + } + + /** {@inheritDoc} */ + public boolean hasConstantOperation(Rop opcode, + RegisterSpec sourceA, RegisterSpec sourceB) { + if (sourceA.getType() != Type.INT) { + return false; + } + + // Return false if second source isn't a constant + if (! (sourceB.getTypeBearer() instanceof CstInteger)) { + // Except for rsub-int (reverse sub) where first source is constant + if (sourceA.getTypeBearer() instanceof CstInteger && + opcode.getOpcode() == RegOps.SUB) { + CstInteger cst = (CstInteger) sourceA.getTypeBearer(); + return cst.fitsIn16Bits(); + } else { + return false; + } + } + + CstInteger cst = (CstInteger) sourceB.getTypeBearer(); + + switch (opcode.getOpcode()) { + // These have 8 and 16 bit cst representations + case RegOps.REM: + case RegOps.ADD: + case RegOps.MUL: + case RegOps.DIV: + case RegOps.AND: + case RegOps.OR: + case RegOps.XOR: + return cst.fitsIn16Bits(); + // These only have 8 bit cst reps + case RegOps.SHL: + case RegOps.SHR: + case RegOps.USHR: + return cst.fitsIn8Bits(); + // No sub-const insn, so check if equivalent add-const fits + case RegOps.SUB: + CstInteger cst2 = CstInteger.make(-cst.getValue()); + return cst2.fitsIn16Bits(); + default: + return false; + } + } + + /** {@inheritDoc} */ + public boolean requiresSourcesInOrder(Rop opcode, + RegisterSpecList sources) { + + return !disableSourcesInOrder && opcode.isCallLike() + && totalRopWidth(sources) >= MIN_INVOKE_IN_ORDER; + } + + /** + * Calculates the total rop width of the list of SSA registers + * + * @param sources {@code non-null;} list of SSA registers + * @return {@code >= 0;} rop-form width in register units + */ + private int totalRopWidth(RegisterSpecList sources) { + int sz = sources.size(); + int total = 0; + + for (int i = 0; i < sz; i++) { + total += sources.get(i).getCategory(); + } + + return total; + } + + /** {@inheritDoc} */ + public int getMaxOptimalRegisterCount() { + return 16; + } +} diff --git a/dx/src/com/android/jack/dx/rop/code/Exceptions.java b/dx/src/com/android/jack/dx/rop/code/Exceptions.java new file mode 100644 index 00000000..f91f4592 --- /dev/null +++ b/dx/src/com/android/jack/dx/rop/code/Exceptions.java @@ -0,0 +1,133 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.rop.code; + +import com.android.jack.dx.rop.type.StdTypeList; +import com.android.jack.dx.rop.type.Type; + +/** + * Common exception types. + */ +public final class Exceptions { + /** {@code non-null;} the type {@code java.lang.ArithmeticException} */ + public static final Type TYPE_ArithmeticException = + Type.intern("Ljava/lang/ArithmeticException;"); + + /** + * {@code non-null;} the type + * {@code java.lang.ArrayIndexOutOfBoundsException} + */ + public static final Type TYPE_ArrayIndexOutOfBoundsException = + Type.intern("Ljava/lang/ArrayIndexOutOfBoundsException;"); + + /** {@code non-null;} the type {@code java.lang.ArrayStoreException} */ + public static final Type TYPE_ArrayStoreException = + Type.intern("Ljava/lang/ArrayStoreException;"); + + /** {@code non-null;} the type {@code java.lang.ClassCastException} */ + public static final Type TYPE_ClassCastException = + Type.intern("Ljava/lang/ClassCastException;"); + + /** {@code non-null;} the type {@code java.lang.Error} */ + public static final Type TYPE_Error = Type.intern("Ljava/lang/Error;"); + + /** + * {@code non-null;} the type + * {@code java.lang.IllegalMonitorStateException} + */ + public static final Type TYPE_IllegalMonitorStateException = + Type.intern("Ljava/lang/IllegalMonitorStateException;"); + + /** {@code non-null;} the type {@code java.lang.NegativeArraySizeException} */ + public static final Type TYPE_NegativeArraySizeException = + Type.intern("Ljava/lang/NegativeArraySizeException;"); + + /** {@code non-null;} the type {@code java.lang.NullPointerException} */ + public static final Type TYPE_NullPointerException = + Type.intern("Ljava/lang/NullPointerException;"); + + /** {@code non-null;} the list {@code [java.lang.Error]} */ + public static final StdTypeList LIST_Error = StdTypeList.make(TYPE_Error); + + /** + * {@code non-null;} the list {@code[java.lang.Error, + * java.lang.ArithmeticException]} + */ + public static final StdTypeList LIST_Error_ArithmeticException = + StdTypeList.make(TYPE_Error, TYPE_ArithmeticException); + + /** + * {@code non-null;} the list {@code[java.lang.Error, + * java.lang.ClassCastException]} + */ + public static final StdTypeList LIST_Error_ClassCastException = + StdTypeList.make(TYPE_Error, TYPE_ClassCastException); + + /** + * {@code non-null;} the list {@code [java.lang.Error, + * java.lang.NegativeArraySizeException]} + */ + public static final StdTypeList LIST_Error_NegativeArraySizeException = + StdTypeList.make(TYPE_Error, TYPE_NegativeArraySizeException); + + /** + * {@code non-null;} the list {@code [java.lang.Error, + * java.lang.NullPointerException]} + */ + public static final StdTypeList LIST_Error_NullPointerException = + StdTypeList.make(TYPE_Error, TYPE_NullPointerException); + + /** + * {@code non-null;} the list {@code [java.lang.Error, + * java.lang.NullPointerException, + * java.lang.ArrayIndexOutOfBoundsException]} + */ + public static final StdTypeList LIST_Error_Null_ArrayIndexOutOfBounds = + StdTypeList.make(TYPE_Error, + TYPE_NullPointerException, + TYPE_ArrayIndexOutOfBoundsException); + + /** + * {@code non-null;} the list {@code [java.lang.Error, + * java.lang.NullPointerException, + * java.lang.ArrayIndexOutOfBoundsException, + * java.lang.ArrayStoreException]} + */ + public static final StdTypeList LIST_Error_Null_ArrayIndex_ArrayStore = + StdTypeList.make(TYPE_Error, + TYPE_NullPointerException, + TYPE_ArrayIndexOutOfBoundsException, + TYPE_ArrayStoreException); + + /** + * {@code non-null;} the list {@code [java.lang.Error, + * java.lang.NullPointerException, + * java.lang.IllegalMonitorStateException]} + */ + public static final StdTypeList + LIST_Error_Null_IllegalMonitorStateException = + StdTypeList.make(TYPE_Error, + TYPE_NullPointerException, + TYPE_IllegalMonitorStateException); + + /** + * This class is uninstantiable. + */ + private Exceptions() { + // This space intentionally left blank. + } +} diff --git a/dx/src/com/android/jack/dx/rop/code/FillArrayDataInsn.java b/dx/src/com/android/jack/dx/rop/code/FillArrayDataInsn.java new file mode 100644 index 00000000..f92fd71f --- /dev/null +++ b/dx/src/com/android/jack/dx/rop/code/FillArrayDataInsn.java @@ -0,0 +1,116 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.rop.code; + +import com.android.jack.dx.rop.cst.Constant; +import com.android.jack.dx.rop.type.StdTypeList; +import com.android.jack.dx.rop.type.Type; +import com.android.jack.dx.rop.type.TypeList; + +import java.util.ArrayList; + +/** + * Instruction which fills a newly created array with a predefined list of + * constant values. + */ +public final class FillArrayDataInsn + extends Insn { + + /** non-null: initial values to fill the newly created array */ + private final ArrayList initValues; + + /** + * non-null: type of the array. Will be used to determine the width of + * elements in the array-data table. + */ + private final Constant arrayType; + + /** + * Constructs an instance. + * + * @param opcode {@code non-null;} the opcode + * @param position {@code non-null;} source position + * @param sources {@code non-null;} specs for all the sources + * @param initValues {@code non-null;} list of initial values to fill the array + * @param cst {@code non-null;} type of the new array + */ + public FillArrayDataInsn(Rop opcode, SourcePosition position, + RegisterSpecList sources, + ArrayList initValues, + Constant cst) { + super(opcode, position, null, sources); + + if (opcode.getBranchingness() != Rop.BRANCH_NONE) { + throw new IllegalArgumentException("bogus branchingness"); + } + + this.initValues = initValues; + this.arrayType = cst; + } + + + /** {@inheritDoc} */ + @Override + public TypeList getCatches() { + return StdTypeList.EMPTY; + } + + /** + * Return the list of init values + * @return {@code non-null;} list of init values + */ + public ArrayList getInitValues() { + return initValues; + } + + /** + * Return the type of the newly created array + * @return {@code non-null;} array type + */ + public Constant getConstant() { + return arrayType; + } + + /** {@inheritDoc} */ + @Override + public void accept(Visitor visitor) { + visitor.visitFillArrayDataInsn(this); + } + + /** {@inheritDoc} */ + @Override + public Insn withAddedCatch(Type type) { + throw new UnsupportedOperationException("unsupported"); + } + + /** {@inheritDoc} */ + @Override + public Insn withRegisterOffset(int delta) { + return new FillArrayDataInsn(getOpcode(), getPosition(), + getSources().withOffset(delta), + initValues, arrayType); + } + + /** {@inheritDoc} */ + @Override + public Insn withNewRegisters(RegisterSpec result, + RegisterSpecList sources) { + + return new FillArrayDataInsn(getOpcode(), getPosition(), + sources, initValues, arrayType); + } +} diff --git a/dx/src/com/android/jack/dx/rop/code/Insn.java b/dx/src/com/android/jack/dx/rop/code/Insn.java new file mode 100644 index 00000000..312f051f --- /dev/null +++ b/dx/src/com/android/jack/dx/rop/code/Insn.java @@ -0,0 +1,456 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.rop.code; + +import com.android.jack.dx.rop.type.StdTypeList; +import com.android.jack.dx.rop.type.Type; +import com.android.jack.dx.rop.type.TypeList; +import com.android.jack.dx.util.ToHuman; + +/** + * A register-based instruction. An instruction is the combination of + * an opcode (which specifies operation and source/result types), a + * list of actual sources and result registers/values, and additional + * information. + */ +public abstract class Insn implements ToHuman { + /** {@code non-null;} opcode */ + private final Rop opcode; + + /** {@code non-null;} source position */ + private final SourcePosition position; + + /** {@code null-ok;} spec for the result of this instruction, if any */ + private final RegisterSpec result; + + /** {@code non-null;} specs for all the sources of this instruction */ + private final RegisterSpecList sources; + + /** + * Constructs an instance. + * + * @param opcode {@code non-null;} the opcode + * @param position {@code non-null;} source position + * @param result {@code null-ok;} spec for the result, if any + * @param sources {@code non-null;} specs for all the sources + */ + public Insn(Rop opcode, SourcePosition position, RegisterSpec result, + RegisterSpecList sources) { + if (opcode == null) { + throw new NullPointerException("opcode == null"); + } + + if (position == null) { + throw new NullPointerException("position == null"); + } + + if (sources == null) { + throw new NullPointerException("sources == null"); + } + + this.opcode = opcode; + this.position = position; + this.result = result; + this.sources = sources; + } + + /** + * {@inheritDoc} + * + * Instances of this class compare by identity. That is, + * {@code x.equals(y)} is only true if {@code x == y}. + */ + @Override + public final boolean equals(Object other) { + return (this == other); + } + + /** + * {@inheritDoc} + * + * This implementation returns the identity hashcode of this + * instance. This is proper, since instances of this class compare + * by identity (see {@link #equals}). + */ + @Override + public final int hashCode() { + return System.identityHashCode(this); + } + + /** {@inheritDoc} */ + @Override + public String toString() { + return toStringWithInline(getInlineString()); + } + + /** + * Gets a human-oriented (and slightly lossy) string for this instance. + * + * @return {@code non-null;} the human string form + */ + public String toHuman() { + return toHumanWithInline(getInlineString()); + } + + /** + * Gets an "inline" string portion for toHuman(), if available. This + * is the portion that appears after the Rop opcode + * + * @return {@code null-ok;} if non-null, the inline text for toHuman() + */ + public String getInlineString() { + return null; + } + + /** + * Gets the opcode. + * + * @return {@code non-null;} the opcode + */ + public final Rop getOpcode() { + return opcode; + } + + /** + * Gets the source position. + * + * @return {@code non-null;} the source position + */ + public final SourcePosition getPosition() { + return position; + } + + /** + * Gets the result spec, if any. A return value of {@code null} + * means this instruction returns nothing. + * + * @return {@code null-ok;} the result spec, if any + */ + public final RegisterSpec getResult() { + return result; + } + + /** + * Gets the spec of a local variable assignment that occurs at this + * instruction, or null if no local variable assignment occurs. This + * may be the result register, or for {@code mark-local} insns + * it may be the source. + * + * @return {@code null-ok;} a named register spec or null + */ + public final RegisterSpec getLocalAssignment() { + RegisterSpec assignment; + if (opcode.getOpcode() == RegOps.MARK_LOCAL) { + assignment = sources.get(0); + } else { + assignment = result; + } + + if (assignment == null) { + return null; + } + + LocalItem localItem = assignment.getLocalItem(); + + if (localItem == null) { + return null; + } + + return assignment; + } + + /** + * Gets the source specs. + * + * @return {@code non-null;} the source specs + */ + public final RegisterSpecList getSources() { + return sources; + } + + /** + * Gets whether this instruction can possibly throw an exception. This + * is just a convenient wrapper for {@code getOpcode().canThrow()}. + * + * @return {@code true} iff this instruction can possibly throw + */ + public final boolean canThrow() { + return opcode.canThrow(); + } + + /** + * Gets the list of possibly-caught exceptions. This returns {@link + * StdTypeList#EMPTY} if this instruction has no handlers, + * which can be either if this instruction can't possibly + * throw or if it merely doesn't handle any of its possible + * exceptions. To determine whether this instruction can throw, + * use {@link #canThrow}. + * + * @return {@code non-null;} the catches list + */ + public abstract TypeList getCatches(); + + /** + * Calls the appropriate method on the given visitor, depending on the + * class of this instance. Subclasses must override this. + * + * @param visitor {@code non-null;} the visitor to call on + */ + public abstract void accept(Visitor visitor); + + /** + * Returns an instance that is just like this one, except that it + * has a catch list with the given item appended to the end. This + * method throws an exception if this instance can't possibly + * throw. To determine whether this instruction can throw, use + * {@link #canThrow}. + * + * @param type {@code non-null;} type to append to the catch list + * @return {@code non-null;} an appropriately-constructed instance + */ + public abstract Insn withAddedCatch(Type type); + + /** + * Returns an instance that is just like this one, except that all + * register references have been offset by the given delta. + * + * @param delta the amount to offset register references by + * @return {@code non-null;} an appropriately-constructed instance + */ + public abstract Insn withRegisterOffset(int delta); + + /** + * Returns an instance that is just like this one, except that, if + * possible, the insn is converted into a version in which a source + * (if it is a constant) is represented directly rather than as a + * register reference. {@code this} is returned in cases where the + * translation is not possible. + * + * @return {@code non-null;} an appropriately-constructed instance + */ + public Insn withSourceLiteral() { + return this; + } + + /** + * Returns an exact copy of this Insn + * + * @return {@code non-null;} an appropriately-constructed instance + */ + public Insn copy() { + return withRegisterOffset(0); + } + + + /** + * Compares, handling nulls safely + * + * @param a first object + * @param b second object + * @return true if they're equal or both null. + */ + private static boolean equalsHandleNulls (Object a, Object b) { + return (a == b) || ((a != null) && a.equals(b)); + } + + /** + * Compares Insn contents, since {@code Insn.equals()} is defined + * to be an identity compare. Insn's are {@code contentEquals()} + * if they have the same opcode, registers, source position, and other + * metadata. + * + * @return true in the case described above + */ + public boolean contentEquals(Insn b) { + return opcode == b.getOpcode() + && position.equals(b.getPosition()) + && (getClass() == b.getClass()) + && equalsHandleNulls(result, b.getResult()) + && equalsHandleNulls(sources, b.getSources()) + && StdTypeList.equalContents(getCatches(), b.getCatches()); + } + + /** + * Returns an instance that is just like this one, except + * with new result and source registers. + * + * @param result {@code null-ok;} new result register + * @param sources {@code non-null;} new sources registers + * @return {@code non-null;} an appropriately-constructed instance + */ + public abstract Insn withNewRegisters(RegisterSpec result, + RegisterSpecList sources); + + /** + * Returns the string form of this instance, with the given bit added in + * the standard location for an inline argument. + * + * @param extra {@code null-ok;} the inline argument string + * @return {@code non-null;} the string form + */ + protected final String toStringWithInline(String extra) { + StringBuffer sb = new StringBuffer(80); + + sb.append("Insn{"); + sb.append(position); + sb.append(' '); + sb.append(opcode); + + if (extra != null) { + sb.append(' '); + sb.append(extra); + } + + sb.append(" :: "); + + if (result != null) { + sb.append(result); + sb.append(" <- "); + } + + sb.append(sources); + sb.append('}'); + + return sb.toString(); + } + + /** + * Returns the human string form of this instance, with the given + * bit added in the standard location for an inline argument. + * + * @param extra {@code null-ok;} the inline argument string + * @return {@code non-null;} the human string form + */ + protected final String toHumanWithInline(String extra) { + StringBuffer sb = new StringBuffer(80); + + sb.append(position); + sb.append(": "); + sb.append(opcode.getNickname()); + + if (extra != null) { + sb.append("("); + sb.append(extra); + sb.append(")"); + } + + if (result == null) { + sb.append(" ."); + } else { + sb.append(" "); + sb.append(result.toHuman()); + } + + sb.append(" <-"); + + int sz = sources.size(); + if (sz == 0) { + sb.append(" ."); + } else { + for (int i = 0; i < sz; i++) { + sb.append(" "); + sb.append(sources.get(i).toHuman()); + } + } + + return sb.toString(); + } + + + /** + * Visitor interface for this (outer) class. + */ + public static interface Visitor { + /** + * Visits a {@link PlainInsn}. + * + * @param insn {@code non-null;} the instruction to visit + */ + public void visitPlainInsn(PlainInsn insn); + + /** + * Visits a {@link PlainCstInsn}. + * + * @param insn {@code non-null;} the instruction to visit + */ + public void visitPlainCstInsn(PlainCstInsn insn); + + /** + * Visits a {@link SwitchInsn}. + * + * @param insn {@code non-null;} the instruction to visit + */ + public void visitSwitchInsn(SwitchInsn insn); + + /** + * Visits a {@link ThrowingCstInsn}. + * + * @param insn {@code non-null;} the instruction to visit + */ + public void visitThrowingCstInsn(ThrowingCstInsn insn); + + /** + * Visits a {@link ThrowingInsn}. + * + * @param insn {@code non-null;} the instruction to visit + */ + public void visitThrowingInsn(ThrowingInsn insn); + + /** + * Visits a {@link FillArrayDataInsn}. + * + * @param insn {@code non-null;} the instruction to visit + */ + public void visitFillArrayDataInsn(FillArrayDataInsn insn); + } + + /** + * Base implementation of {@link Visitor}, which has empty method + * bodies for all methods. + */ + public static class BaseVisitor implements Visitor { + /** {@inheritDoc} */ + public void visitPlainInsn(PlainInsn insn) { + // This space intentionally left blank. + } + + /** {@inheritDoc} */ + public void visitPlainCstInsn(PlainCstInsn insn) { + // This space intentionally left blank. + } + + /** {@inheritDoc} */ + public void visitSwitchInsn(SwitchInsn insn) { + // This space intentionally left blank. + } + + /** {@inheritDoc} */ + public void visitThrowingCstInsn(ThrowingCstInsn insn) { + // This space intentionally left blank. + } + + /** {@inheritDoc} */ + public void visitThrowingInsn(ThrowingInsn insn) { + // This space intentionally left blank. + } + + /** {@inheritDoc} */ + public void visitFillArrayDataInsn(FillArrayDataInsn insn) { + // This space intentionally left blank. + } + } +} diff --git a/dx/src/com/android/jack/dx/rop/code/InsnList.java b/dx/src/com/android/jack/dx/rop/code/InsnList.java new file mode 100644 index 00000000..fb787410 --- /dev/null +++ b/dx/src/com/android/jack/dx/rop/code/InsnList.java @@ -0,0 +1,130 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.rop.code; + +import com.android.jack.dx.util.FixedSizeList; + +/** + * List of {@link Insn} instances. + */ +public final class InsnList + extends FixedSizeList { + /** + * Constructs an instance. All indices initially contain {@code null}. + * + * @param size the size of the list + */ + public InsnList(int size) { + super(size); + } + + /** + * Gets the element at the given index. It is an error to call + * this with the index for an element which was never set; if you + * do that, this will throw {@code NullPointerException}. + * + * @param n {@code >= 0, < size();} which index + * @return {@code non-null;} element at that index + */ + public Insn get(int n) { + return (Insn) get0(n); + } + + /** + * Sets the instruction at the given index. + * + * @param n {@code >= 0, < size();} which index + * @param insn {@code non-null;} the instruction to set at {@code n} + */ + public void set(int n, Insn insn) { + set0(n, insn); + } + + /** + * Gets the last instruction. This is just a convenient shorthand for + * {@code get(size() - 1)}. + * + * @return {@code non-null;} the last instruction + */ + public Insn getLast() { + return get(size() - 1); + } + + /** + * Visits each instruction in the list, in order. + * + * @param visitor {@code non-null;} visitor to use + */ + public void forEach(Insn.Visitor visitor) { + int sz = size(); + + for (int i = 0; i < sz; i++) { + get(i).accept(visitor); + } + } + + /** + * Compares the contents of this {@code InsnList} with another. + * The blocks must have the same number of insns, and each Insn must + * also return true to {@code Insn.contentEquals()}. + * + * @param b to compare + * @return true in the case described above. + */ + public boolean contentEquals(InsnList b) { + if (b == null) return false; + + int sz = size(); + + if (sz != b.size()) return false; + + for (int i = 0; i < sz; i++) { + if (!get(i).contentEquals(b.get(i))) { + return false; + } + } + + return true; + } + + /** + * Returns an instance that is identical to this one, except that + * the registers in each instruction are offset by the given + * amount. Mutability of the result is inherited from the + * original. + * + * @param delta the amount to offset register numbers by + * @return {@code non-null;} an appropriately-constructed instance + */ + public InsnList withRegisterOffset(int delta) { + int sz = size(); + InsnList result = new InsnList(sz); + + for (int i = 0; i < sz; i++) { + Insn one = (Insn) get0(i); + if (one != null) { + result.set0(i, one.withRegisterOffset(delta)); + } + } + + if (isImmutable()) { + result.setImmutable(); + } + + return result; + } +} diff --git a/dx/src/com/android/jack/dx/rop/code/LocalItem.java b/dx/src/com/android/jack/dx/rop/code/LocalItem.java new file mode 100644 index 00000000..fd9c9525 --- /dev/null +++ b/dx/src/com/android/jack/dx/rop/code/LocalItem.java @@ -0,0 +1,189 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.rop.code; + +import com.android.jack.dx.rop.cst.CstString; +import com.android.jack.dx.rop.cst.CstType; + +/** + * A local variable item: either a name or a signature or both. + */ +public class LocalItem implements Comparable { + /** {@code null-ok;} local variable name */ + private final CstString name; + + /** {@code null-ok;} local variable type */ + private final CstType type; + + /** {@code null-ok;} local variable signature */ + private final CstString signature; + + /** + * Make a new item. If both name and type are null, null is returned. + * + * TODO: intern these + * + * @param name {@code null-ok;} local variable name + * @param type {@code null-ok;} local variable type + * @param signature {@code null-ok;} local variable signature which will be referenced as-is by + * the sig_idx in the debug_info_item (cf. the documentation for debug_info_item and the + * discussion under {@code dalvik.annotation.Signature} in "dex-format.html") + * @return {@code null-ok;} appropriate instance. + */ + public static LocalItem make(CstString name, CstType type, CstString signature) { + if (name == null && type == null) { + return null; + } + + return new LocalItem (name, type, signature); + } + + /** + * Constructs instance. + * + * @param name {@code null-ok;} local variable name + * @param type {@code null-ok;} local variable type + * @param signature {@code null-ok;} local variable signature + */ + private LocalItem(CstString name, CstType type, CstString signature) { + this.name = name; + this.type = type; + this.signature = signature; + } + + /** {@inheritDoc} */ + @Override + public boolean equals(Object other) { + if (!(other instanceof LocalItem)) { + return false; + } + + LocalItem local = (LocalItem) other; + + return 0 == compareTo(local); + } + + /** + * Compares two strings like String.compareTo(), excepts treats a null + * as the least-possible string value. + * + * @return negative integer, zero, or positive integer in accordance + * with Comparable.compareTo() + */ + private static int compareHandlesNulls(CstString a, CstString b) { + if (a == b) { + return 0; + } else if (a == null) { + return -1; + } else if (b == null) { + return 1; + } else { + return a.compareTo(b); + } + } + + /** + * Compares two CstType like CstType.compareTo(), excepts treats a null + * as the least-possible string value. + * + * @return negative integer, zero, or positive integer in accordance + * with Comparable.compareTo() + */ + private static int compareHandlesNulls(CstType a, CstType b) { + if (a == b) { + return 0; + } else if (a == null) { + return -1; + } else if (b == null) { + return 1; + } else { + return a.compareTo(b); + } + } + + /** {@inheritDoc} */ + public int compareTo(LocalItem local) { + int ret; + + ret = compareHandlesNulls(name, local.name); + + if (ret != 0) { + return ret; + } + + ret = compareHandlesNulls(type, local.type); + + if (ret != 0) { + return ret; + } + + ret = compareHandlesNulls(signature, local.signature); + + return ret; + } + + + /** {@inheritDoc} */ + @Override + public int hashCode() { + return (name == null ? 0 : name.hashCode()) * 31 + + (type == null ? 0 : type.hashCode()) + + (signature == null ? 0 : signature.hashCode()) * 17; + } + + /** {@inheritDoc} */ + @Override + public String toString() { + if (name != null && type == null && signature == null) { + return name.toQuoted(); + } else if (name == null && type == null && signature == null) { + return ""; + } + + return "[" + (name == null ? "" : name.toQuoted()) + + "|" + (type == null ? "" : type.getDescriptor().toQuoted()) + + "|" + (signature == null ? "" : signature.toQuoted()); + } + + /** + * Gets name. + * + * @return {@code null-ok;} name + */ + public CstString getName() { + return name; + } + + /** + * Gets the signature that must be represented to successfully implement the source language's + * semantics (cf. the discussion under {@code dalvik.annotation.Signature} in "dex-format.html") + * + * @return signature + */ + public CstString getSignature() { + return signature; + } + + /** + * Gets type. + * + * @return {@code null-ok;} type. + */ + public CstType getType() { + return type; + } +} diff --git a/dx/src/com/android/jack/dx/rop/code/LocalVariableExtractor.java b/dx/src/com/android/jack/dx/rop/code/LocalVariableExtractor.java new file mode 100644 index 00000000..6420d389 --- /dev/null +++ b/dx/src/com/android/jack/dx/rop/code/LocalVariableExtractor.java @@ -0,0 +1,191 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.rop.code; + +import com.android.jack.dx.util.Bits; +import com.android.jack.dx.util.IntList; + +/** + * Code to figure out which local variables are active at which points in + * a method. + */ +public final class LocalVariableExtractor { + /** {@code non-null;} method being extracted from */ + private final RopMethod method; + + /** {@code non-null;} block list for the method */ + private final BasicBlockList blocks; + + /** {@code non-null;} result in-progress */ + private final LocalVariableInfo resultInfo; + + /** {@code non-null;} work set indicating blocks needing to be processed */ + private final int[] workSet; + + /** + * Extracts out all the local variable information from the given method. + * + * @param method {@code non-null;} the method to extract from + * @return {@code non-null;} the extracted information + */ + public static LocalVariableInfo extract(RopMethod method) { + LocalVariableExtractor lve = new LocalVariableExtractor(method); + return lve.doit(); + } + + /** + * Constructs an instance. This method is private. Use {@link #extract}. + * + * @param method {@code non-null;} the method to extract from + */ + private LocalVariableExtractor(RopMethod method) { + if (method == null) { + throw new NullPointerException("method == null"); + } + + BasicBlockList blocks = method.getBlocks(); + int maxLabel = blocks.getMaxLabel(); + + this.method = method; + this.blocks = blocks; + this.resultInfo = new LocalVariableInfo(method); + this.workSet = Bits.makeBitSet(maxLabel); + } + + /** + * Does the extraction. + * + * @return {@code non-null;} the extracted information + */ + private LocalVariableInfo doit() { + for (int label = method.getFirstLabel(); + label >= 0; + label = Bits.findFirst(workSet, 0)) { + Bits.clear(workSet, label); + processBlock(label); + } + + resultInfo.setImmutable(); + return resultInfo; + } + + /** + * Processes a single block. + * + * @param label {@code >= 0;} label of the block to process + */ + private void processBlock(int label) { + RegisterSpecSet primaryState = resultInfo.mutableCopyOfStarts(label); + BasicBlock block = blocks.labelToBlock(label); + InsnList insns = block.getInsns(); + int insnSz = insns.size(); + + /* + * We may have to treat the last instruction specially: If it + * can (but doesn't always) throw, and the exception can be + * caught within the same method, then we need to use the + * state *before* executing it to be what is merged into + * exception targets. + */ + boolean canThrowDuringLastInsn = block.hasExceptionHandlers() && + (insns.getLast().getResult() != null); + int freezeSecondaryStateAt = insnSz - 1; + RegisterSpecSet secondaryState = primaryState; + + /* + * Iterate over the instructions, adding information for each place + * that the active variable set changes. + */ + + for (int i = 0; i < insnSz; i++) { + if (canThrowDuringLastInsn && (i == freezeSecondaryStateAt)) { + // Until this point, primaryState == secondaryState. + primaryState.setImmutable(); + primaryState = primaryState.mutableCopy(); + } + + Insn insn = insns.get(i); + RegisterSpec result; + + result = insn.getLocalAssignment(); + + if (result == null) { + /* + * If an assignment assigns over an existing local, make + * sure to mark the local as going out of scope. + */ + + result = insn.getResult(); + + if (result != null + && primaryState.get(result.getReg()) != null) { + primaryState.remove(primaryState.get(result.getReg())); + } + continue; + } + + result = result.withSimpleType(); + + RegisterSpec already = primaryState.get(result); + /* + * The equals() check ensures we only add new info if + * the instruction causes a change to the set of + * active variables. + */ + if (!result.equals(already)) { + /* + * If this insn represents a local moving from one register + * to another, remove the association between the old register + * and the local. + */ + RegisterSpec previous + = primaryState.localItemToSpec(result.getLocalItem()); + + if (previous != null + && (previous.getReg() != result.getReg())) { + + primaryState.remove(previous); + } + + resultInfo.addAssignment(insn, result); + primaryState.put(result); + } + } + + primaryState.setImmutable(); + + /* + * Merge this state into the start state for each successor, + * and update the work set where required (that is, in cases + * where the start state for a block changes). + */ + + IntList successors = block.getSuccessors(); + int succSz = successors.size(); + int primarySuccessor = block.getPrimarySuccessor(); + + for (int i = 0; i < succSz; i++) { + int succ = successors.get(i); + RegisterSpecSet state = (succ == primarySuccessor) ? + primaryState : secondaryState; + + if (resultInfo.mergeStarts(succ, state)) { + Bits.set(workSet, succ); + } + } + } +} diff --git a/dx/src/com/android/jack/dx/rop/code/LocalVariableInfo.java b/dx/src/com/android/jack/dx/rop/code/LocalVariableInfo.java new file mode 100644 index 00000000..a15402fd --- /dev/null +++ b/dx/src/com/android/jack/dx/rop/code/LocalVariableInfo.java @@ -0,0 +1,254 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.rop.code; + +import com.android.jack.dx.rop.type.TypeBearer; +import com.android.jack.dx.util.MutabilityControl; + +import java.util.HashMap; + +/** + * Container for local variable information for a particular {@link + * RopMethod}. + */ +public final class LocalVariableInfo + extends MutabilityControl { + /** {@code >= 0;} the register count for the method */ + private final int regCount; + + /** + * {@code non-null;} {@link RegisterSpecSet} to use when indicating a block + * that has no locals; it is empty and immutable but has an appropriate + * max size for the method + */ + private final RegisterSpecSet emptySet; + + /** + * {@code non-null;} array consisting of register sets representing the + * sets of variables already assigned upon entry to each block, + * where array indices correspond to block labels + */ + private final RegisterSpecSet[] blockStarts; + + /** {@code non-null;} map from instructions to the variable each assigns */ + private final HashMap insnAssignments; + + /** + * Constructs an instance. + * + * @param method {@code non-null;} the method being represented by this instance + */ + public LocalVariableInfo(RopMethod method) { + if (method == null) { + throw new NullPointerException("method == null"); + } + + BasicBlockList blocks = method.getBlocks(); + int maxLabel = blocks.getMaxLabel(); + + this.regCount = blocks.getRegCount(); + this.emptySet = new RegisterSpecSet(regCount); + this.blockStarts = new RegisterSpecSet[maxLabel]; + this.insnAssignments = + new HashMap(blocks.getInstructionCount()); + + emptySet.setImmutable(); + } + + /** + * Sets the register set associated with the start of the block with + * the given label. + * + * @param label {@code >= 0;} the block label + * @param specs {@code non-null;} the register set to associate with the block + */ + public void setStarts(int label, RegisterSpecSet specs) { + throwIfImmutable(); + + if (specs == null) { + throw new NullPointerException("specs == null"); + } + + try { + blockStarts[label] = specs; + } catch (ArrayIndexOutOfBoundsException ex) { + // Translate the exception. + throw new IllegalArgumentException("bogus label"); + } + } + + /** + * Merges the given register set into the set for the block with the + * given label. If there was not already an associated set, then this + * is the same as calling {@link #setStarts}. Otherwise, this will + * merge the two sets and call {@link #setStarts} on the result of the + * merge. + * + * @param label {@code >= 0;} the block label + * @param specs {@code non-null;} the register set to merge into the start set + * for the block + * @return {@code true} if the merge resulted in an actual change + * to the associated set (including storing one for the first time) or + * {@code false} if there was no change + */ + public boolean mergeStarts(int label, RegisterSpecSet specs) { + RegisterSpecSet start = getStarts0(label); + boolean changed = false; + + if (start == null) { + setStarts(label, specs); + return true; + } + + RegisterSpecSet newStart = start.mutableCopy(); + if (start.size() != 0) { + newStart.intersect(specs, true); + } else { + newStart = specs.mutableCopy(); + } + + if (start.equals(newStart)) { + return false; + } + + newStart.setImmutable(); + setStarts(label, newStart); + + return true; + } + + /** + * Gets the register set associated with the start of the block + * with the given label. This returns an empty set with the appropriate + * max size if no set was associated with the block in question. + * + * @param label {@code >= 0;} the block label + * @return {@code non-null;} the associated register set + */ + public RegisterSpecSet getStarts(int label) { + RegisterSpecSet result = getStarts0(label); + + return (result != null) ? result : emptySet; + } + + /** + * Gets the register set associated with the start of the given + * block. This is just convenient shorthand for + * {@code getStarts(block.getLabel())}. + * + * @param block {@code non-null;} the block in question + * @return {@code non-null;} the associated register set + */ + public RegisterSpecSet getStarts(BasicBlock block) { + return getStarts(block.getLabel()); + } + + /** + * Gets a mutable copy of the register set associated with the + * start of the block with the given label. This returns a + * newly-allocated empty {@link RegisterSpecSet} of appropriate + * max size if there is not yet any set associated with the block. + * + * @param label {@code >= 0;} the block label + * @return {@code non-null;} the associated register set + */ + public RegisterSpecSet mutableCopyOfStarts(int label) { + RegisterSpecSet result = getStarts0(label); + + return (result != null) ? + result.mutableCopy() : new RegisterSpecSet(regCount); + } + + /** + * Adds an assignment association for the given instruction and + * register spec. This throws an exception if the instruction + * doesn't actually perform a named variable assignment. + * + * Note: Although the instruction contains its own spec for + * the result, it still needs to be passed in explicitly to this + * method, since the spec that is stored here should always have a + * simple type and the one in the instruction can be an arbitrary + * {@link TypeBearer} (such as a constant value). + * + * @param insn {@code non-null;} the instruction in question + * @param spec {@code non-null;} the associated register spec + */ + public void addAssignment(Insn insn, RegisterSpec spec) { + throwIfImmutable(); + + if (insn == null) { + throw new NullPointerException("insn == null"); + } + + if (spec == null) { + throw new NullPointerException("spec == null"); + } + + insnAssignments.put(insn, spec); + } + + /** + * Gets the named register being assigned by the given instruction, if + * previously stored in this instance. + * + * @param insn {@code non-null;} instruction in question + * @return {@code null-ok;} the named register being assigned, if any + */ + public RegisterSpec getAssignment(Insn insn) { + return insnAssignments.get(insn); + } + + /** + * Gets the number of assignments recorded by this instance. + * + * @return {@code >= 0;} the number of assignments + */ + public int getAssignmentCount() { + return insnAssignments.size(); + } + + public void debugDump() { + for (int label = 0 ; label < blockStarts.length; label++) { + if (blockStarts[label] == null) { + continue; + } + + if (blockStarts[label] == emptySet) { + System.out.printf("%04x: empty set\n", label); + } else { + System.out.printf("%04x: %s\n", label, blockStarts[label]); + } + } + } + + /** + * Helper method, to get the starts for a label, throwing the + * right exception for range problems. + * + * @param label {@code >= 0;} the block label + * @return {@code null-ok;} associated register set or {@code null} if there + * is none + */ + private RegisterSpecSet getStarts0(int label) { + try { + return blockStarts[label]; + } catch (ArrayIndexOutOfBoundsException ex) { + // Translate the exception. + throw new IllegalArgumentException("bogus label"); + } + } +} diff --git a/dx/src/com/android/jack/dx/rop/code/PlainCstInsn.java b/dx/src/com/android/jack/dx/rop/code/PlainCstInsn.java new file mode 100644 index 00000000..597b92d5 --- /dev/null +++ b/dx/src/com/android/jack/dx/rop/code/PlainCstInsn.java @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.rop.code; + +import com.android.jack.dx.rop.cst.Constant; +import com.android.jack.dx.rop.type.StdTypeList; +import com.android.jack.dx.rop.type.Type; +import com.android.jack.dx.rop.type.TypeList; + +/** + * Instruction which contains an explicit reference to a constant + * but which cannot throw an exception. + */ +public final class PlainCstInsn + extends CstInsn { + /** + * Constructs an instance. + * + * @param opcode {@code non-null;} the opcode + * @param position {@code non-null;} source position + * @param result {@code null-ok;} spec for the result, if any + * @param sources {@code non-null;} specs for all the sources + * @param cst {@code non-null;} the constant + */ + public PlainCstInsn(Rop opcode, SourcePosition position, + RegisterSpec result, RegisterSpecList sources, + Constant cst) { + super(opcode, position, result, sources, cst); + + if (opcode.getBranchingness() != Rop.BRANCH_NONE) { + throw new IllegalArgumentException("bogus branchingness"); + } + } + + /** {@inheritDoc} */ + @Override + public TypeList getCatches() { + return StdTypeList.EMPTY; + } + + /** {@inheritDoc} */ + @Override + public void accept(Visitor visitor) { + visitor.visitPlainCstInsn(this); + } + + /** {@inheritDoc} */ + @Override + public Insn withAddedCatch(Type type) { + throw new UnsupportedOperationException("unsupported"); + } + + /** {@inheritDoc} */ + @Override + public Insn withRegisterOffset(int delta) { + return new PlainCstInsn(getOpcode(), getPosition(), + getResult().withOffset(delta), + getSources().withOffset(delta), + getConstant()); + } + + /** {@inheritDoc} */ + @Override + public Insn withNewRegisters(RegisterSpec result, + RegisterSpecList sources) { + + return new PlainCstInsn(getOpcode(), getPosition(), + result, + sources, + getConstant()); + + } +} diff --git a/dx/src/com/android/jack/dx/rop/code/PlainInsn.java b/dx/src/com/android/jack/dx/rop/code/PlainInsn.java new file mode 100644 index 00000000..a6745370 --- /dev/null +++ b/dx/src/com/android/jack/dx/rop/code/PlainInsn.java @@ -0,0 +1,157 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.rop.code; + +import com.android.jack.dx.rop.cst.Constant; +import com.android.jack.dx.rop.cst.CstInteger; +import com.android.jack.dx.rop.type.StdTypeList; +import com.android.jack.dx.rop.type.Type; +import com.android.jack.dx.rop.type.TypeBearer; +import com.android.jack.dx.rop.type.TypeList; + +/** + * Plain instruction, which has no embedded data and which cannot possibly + * throw an exception. + */ +public final class PlainInsn + extends Insn { + /** + * Constructs an instance. + * + * @param opcode {@code non-null;} the opcode + * @param position {@code non-null;} source position + * @param result {@code null-ok;} spec for the result, if any + * @param sources {@code non-null;} specs for all the sources + */ + public PlainInsn(Rop opcode, SourcePosition position, + RegisterSpec result, RegisterSpecList sources) { + super(opcode, position, result, sources); + + switch (opcode.getBranchingness()) { + case Rop.BRANCH_SWITCH: + case Rop.BRANCH_THROW: { + throw new IllegalArgumentException("bogus branchingness"); + } + } + + if (result != null && opcode.getBranchingness() != Rop.BRANCH_NONE) { + // move-result-pseudo is required here + throw new IllegalArgumentException + ("can't mix branchingness with result"); + } + } + + /** + * Constructs a single-source instance. + * + * @param opcode {@code non-null;} the opcode + * @param position {@code non-null;} source position + * @param result {@code null-ok;} spec for the result, if any + * @param source {@code non-null;} spec for the source + */ + public PlainInsn(Rop opcode, SourcePosition position, RegisterSpec result, + RegisterSpec source) { + this(opcode, position, result, RegisterSpecList.make(source)); + } + + /** {@inheritDoc} */ + @Override + public TypeList getCatches() { + return StdTypeList.EMPTY; + } + + /** {@inheritDoc} */ + @Override + public void accept(Visitor visitor) { + visitor.visitPlainInsn(this); + } + + /** {@inheritDoc} */ + @Override + public Insn withAddedCatch(Type type) { + throw new UnsupportedOperationException("unsupported"); + } + + /** {@inheritDoc} */ + @Override + public Insn withRegisterOffset(int delta) { + return new PlainInsn(getOpcode(), getPosition(), + getResult().withOffset(delta), + getSources().withOffset(delta)); + } + + /** {@inheritDoc} */ + @Override + public Insn withSourceLiteral() { + RegisterSpecList sources = getSources(); + int szSources = sources.size(); + + if (szSources == 0) { + return this; + } + + TypeBearer lastType = sources.get(szSources - 1).getTypeBearer(); + + if (!lastType.isConstant()) { + // Check for reverse subtraction, where first source is constant + TypeBearer firstType = sources.get(0).getTypeBearer(); + if (szSources == 2 && firstType.isConstant()) { + Constant cst = (Constant) firstType; + RegisterSpecList newSources = sources.withoutFirst(); + Rop newRop = Rops.ropFor(getOpcode().getOpcode(), getResult(), + newSources, cst); + return new PlainCstInsn(newRop, getPosition(), getResult(), + newSources, cst); + } + return this; + } else { + + Constant cst = (Constant) lastType; + + RegisterSpecList newSources = sources.withoutLast(); + + Rop newRop; + try { + // Check for constant subtraction and flip it to be addition + int opcode = getOpcode().getOpcode(); + if (opcode == RegOps.SUB && cst instanceof CstInteger) { + opcode = RegOps.ADD; + cst = CstInteger.make(-((CstInteger)cst).getValue()); + } + newRop = Rops.ropFor(opcode, getResult(), newSources, cst); + } catch (IllegalArgumentException ex) { + // There's no rop for this case + return this; + } + + return new PlainCstInsn(newRop, getPosition(), + getResult(), newSources, cst); + } + } + + + /** {@inheritDoc} */ + @Override + public Insn withNewRegisters(RegisterSpec result, + RegisterSpecList sources) { + + return new PlainInsn(getOpcode(), getPosition(), + result, + sources); + + } +} diff --git a/dx/src/com/android/jack/dx/rop/code/RegOps.java b/dx/src/com/android/jack/dx/rop/code/RegOps.java new file mode 100644 index 00000000..a863a89f --- /dev/null +++ b/dx/src/com/android/jack/dx/rop/code/RegOps.java @@ -0,0 +1,399 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.rop.code; + +import com.android.jack.dx.util.Hex; + +/** + * All the register-based opcodes, and related utilities. + * + *

Note: Opcode descriptions use a rough pseudocode. {@code r} + * is the result register, {@code x} is the first argument, + * {@code y} is the second argument, and {@code z} is the + * third argument. The expression which describes + * the operation uses Java-ish syntax but is preceded by type indicators for + * each of the values. + */ +public final class RegOps { + /** {@code nop()} */ + public static final int NOP = 1; + + /** {@code T: any type; r,x: T :: r = x;} */ + public static final int MOVE = 2; + + /** {@code T: any type; r,param(x): T :: r = param(x)} */ + public static final int MOVE_PARAM = 3; + + /** + * {@code T: Throwable; r: T :: r = caught_exception}. + * Note: This opcode should only ever be used in the + * first instruction of a block, and such blocks must be + * the start of an exception handler. + */ + public static final int MOVE_EXCEPTION = 4; + + /** {@code T: any type; r, literal: T :: r = literal;} */ + public static final int CONST = 5; + + /** {@code goto label} */ + public static final int GOTO = 6; + + /** + * {@code T: int or Object; x,y: T :: if (x == y) goto + * label} + */ + public static final int IF_EQ = 7; + + /** + * {@code T: int or Object; x,y: T :: if (x != y) goto + * label} + */ + public static final int IF_NE = 8; + + /** {@code x,y: int :: if (x < y) goto label} */ + public static final int IF_LT = 9; + + /** {@code x,y: int :: if (x >= y) goto label} */ + public static final int IF_GE = 10; + + /** {@code x,y: int :: if (x <= y) goto label} */ + public static final int IF_LE = 11; + + /** {@code x,y: int :: if (x > y) goto label} */ + public static final int IF_GT = 12; + + /** {@code x: int :: goto table[x]} */ + public static final int SWITCH = 13; + + /** {@code T: any numeric type; r,x,y: T :: r = x + y} */ + public static final int ADD = 14; + + /** {@code T: any numeric type; r,x,y: T :: r = x - y} */ + public static final int SUB = 15; + + /** {@code T: any numeric type; r,x,y: T :: r = x * y} */ + public static final int MUL = 16; + + /** {@code T: any numeric type; r,x,y: T :: r = x / y} */ + public static final int DIV = 17; + + /** + * {@code T: any numeric type; r,x,y: T :: r = x % y} + * (Java-style remainder) + */ + public static final int REM = 18; + + /** {@code T: any numeric type; r,x: T :: r = -x} */ + public static final int NEG = 19; + + /** {@code T: any integral type; r,x,y: T :: r = x & y} */ + public static final int AND = 20; + + /** {@code T: any integral type; r,x,y: T :: r = x | y} */ + public static final int OR = 21; + + /** {@code T: any integral type; r,x,y: T :: r = x ^ y} */ + public static final int XOR = 22; + + /** + * {@code T: any integral type; r,x: T; y: int :: r = x << y} + */ + public static final int SHL = 23; + + /** + * {@code T: any integral type; r,x: T; y: int :: r = x >> y} + * (signed right-shift) + */ + public static final int SHR = 24; + + /** + * {@code T: any integral type; r,x: T; y: int :: r = x >>> y} + * (unsigned right-shift) + */ + public static final int USHR = 25; + + /** {@code T: any integral type; r,x: T :: r = ~x} */ + public static final int NOT = 26; + + /** + * {@code T: any numeric type; r: int; x,y: T :: r = (x == y) ? 0 + * : (x > y) ? 1 : -1} (Java-style "cmpl" where a NaN is + * considered "less than" all other values; also used for integral + * comparisons) + */ + public static final int CMPL = 27; + + /** + * {@code T: any floating point type; r: int; x,y: T :: r = (x == y) ? 0 + * : (x < y) ? -1 : 1} (Java-style "cmpg" where a NaN is + * considered "greater than" all other values) + */ + public static final int CMPG = 28; + + /** + * {@code T: any numeric type; U: any numeric type; r: T; x: U :: + * r = (T) x} (numeric type conversion between the four + * "real" numeric types) + */ + public static final int CONV = 29; + + /** + * {@code r,x: int :: r = (x << 24) >> 24} (Java-style + * convert int to byte) + */ + public static final int TO_BYTE = 30; + + /** + * {@code r,x: int :: r = x & 0xffff} (Java-style convert int to char) + */ + public static final int TO_CHAR = 31; + + /** + * {@code r,x: int :: r = (x << 16) >> 16} (Java-style + * convert int to short) + */ + public static final int TO_SHORT = 32; + + /** {@code T: return type for the method; x: T; return x} */ + public static final int RETURN = 33; + + /** {@code T: any type; r: int; x: T[]; :: r = x.length} */ + public static final int ARRAY_LENGTH = 34; + + /** {@code x: Throwable :: throw(x)} */ + public static final int THROW = 35; + + /** {@code x: Object :: monitorenter(x)} */ + public static final int MONITOR_ENTER = 36; + + /** {@code x: Object :: monitorexit(x)} */ + public static final int MONITOR_EXIT = 37; + + /** {@code T: any type; r: T; x: T[]; y: int :: r = x[y]} */ + public static final int AGET = 38; + + /** {@code T: any type; x: T; y: T[]; z: int :: x[y] = z} */ + public static final int APUT = 39; + + /** + * {@code T: any non-array object type :: r = + * alloc(T)} (allocate heap space for an object) + */ + public static final int NEW_INSTANCE = 40; + + /** {@code T: any array type; r: T; x: int :: r = new T[x]} */ + public static final int NEW_ARRAY = 41; + + /** + * {@code T: any array type; r: T; x: int; v0..vx: T :: r = new T[x] + * {v0, ..., vx}} + */ + public static final int FILLED_NEW_ARRAY = 42; + + /** + * {@code T: any object type; x: Object :: (T) x} (can + * throw {@code ClassCastException}) + */ + public static final int CHECK_CAST = 43; + + /** + * {@code T: any object type; x: Object :: x instanceof T} + */ + public static final int INSTANCE_OF = 44; + + /** + * {@code T: any type; r: T; x: Object; f: instance field spec of + * type T :: r = x.f} + */ + public static final int GET_FIELD = 45; + + /** + * {@code T: any type; r: T; f: static field spec of type T :: r = + * f} + */ + public static final int GET_STATIC = 46; + + /** + * {@code T: any type; x: T; y: Object; f: instance field spec of type + * T :: y.f = x} + */ + public static final int PUT_FIELD = 47; + + /** + * {@code T: any type; f: static field spec of type T; x: T :: f = x} + */ + public static final int PUT_STATIC = 48; + + /** + * {@code Tr, T0, T1...: any types; r: Tr; m: static method spec; + * y0: T0; y1: T1 ... :: r = m(y0, y1, ...)} (call static + * method) + */ + public static final int INVOKE_STATIC = 49; + + /** + * {@code Tr, T0, T1...: any types; r: Tr; x: Object; m: instance method + * spec; y0: T0; y1: T1 ... :: r = x.m(y0, y1, ...)} (call normal + * virtual method) + */ + public static final int INVOKE_VIRTUAL = 50; + + /** + * {@code Tr, T0, T1...: any types; r: Tr; x: Object; m: instance method + * spec; y0: T0; y1: T1 ... :: r = x.m(y0, y1, ...)} (call + * superclass virtual method) + */ + public static final int INVOKE_SUPER = 51; + + /** + * {@code Tr, T0, T1...: any types; r: Tr; x: Object; m: instance method + * spec; y0: T0; y1: T1 ... :: r = x.m(y0, y1, ...)} (call + * direct/special method) + */ + public static final int INVOKE_DIRECT = 52; + + /** + * {@code Tr, T0, T1...: any types; r: Tr; x: Object; m: interface + * (instance) method spec; y0: T0; y1: T1 ... :: r = x.m(y0, y1, + * ...)} (call interface method) + */ + public static final int INVOKE_INTERFACE = 53; + + /** + * {@code T0: any type; name: local variable name :: mark(name,T0)} + * (mark beginning or end of local variable name) + */ + public static final int MARK_LOCAL = 54; + + /** + * {@code T: Any type; r: T :: r = return_type}. + * Note: This opcode should only ever be used in the + * first instruction of a block following an invoke-*. + */ + public static final int MOVE_RESULT = 55; + + /** + * {@code T: Any type; r: T :: r = return_type}. + * Note: This opcode should only ever be used in the + * first instruction of a block following a non-invoke throwing insn + */ + public static final int MOVE_RESULT_PSEUDO = 56; + + /** {@code T: Any primitive type; v0..vx: T :: {v0, ..., vx}} */ + public static final int FILL_ARRAY_DATA = 57; + + /** + * This class is uninstantiable. + */ + private RegOps() { + // This space intentionally left blank. + } + + /** + * Gets the name of the given opcode. + * + * @param opcode the opcode + * @return {@code non-null;} its name + */ + public static String opName(int opcode) { + switch (opcode) { + case NOP: return "nop"; + case MOVE: return "move"; + case MOVE_PARAM: return "move-param"; + case MOVE_EXCEPTION: return "move-exception"; + case CONST: return "const"; + case GOTO: return "goto"; + case IF_EQ: return "if-eq"; + case IF_NE: return "if-ne"; + case IF_LT: return "if-lt"; + case IF_GE: return "if-ge"; + case IF_LE: return "if-le"; + case IF_GT: return "if-gt"; + case SWITCH: return "switch"; + case ADD: return "add"; + case SUB: return "sub"; + case MUL: return "mul"; + case DIV: return "div"; + case REM: return "rem"; + case NEG: return "neg"; + case AND: return "and"; + case OR: return "or"; + case XOR: return "xor"; + case SHL: return "shl"; + case SHR: return "shr"; + case USHR: return "ushr"; + case NOT: return "not"; + case CMPL: return "cmpl"; + case CMPG: return "cmpg"; + case CONV: return "conv"; + case TO_BYTE: return "to-byte"; + case TO_CHAR: return "to-char"; + case TO_SHORT: return "to-short"; + case RETURN: return "return"; + case ARRAY_LENGTH: return "array-length"; + case THROW: return "throw"; + case MONITOR_ENTER: return "monitor-enter"; + case MONITOR_EXIT: return "monitor-exit"; + case AGET: return "aget"; + case APUT: return "aput"; + case NEW_INSTANCE: return "new-instance"; + case NEW_ARRAY: return "new-array"; + case FILLED_NEW_ARRAY: return "filled-new-array"; + case CHECK_CAST: return "check-cast"; + case INSTANCE_OF: return "instance-of"; + case GET_FIELD: return "get-field"; + case GET_STATIC: return "get-static"; + case PUT_FIELD: return "put-field"; + case PUT_STATIC: return "put-static"; + case INVOKE_STATIC: return "invoke-static"; + case INVOKE_VIRTUAL: return "invoke-virtual"; + case INVOKE_SUPER: return "invoke-super"; + case INVOKE_DIRECT: return "invoke-direct"; + case INVOKE_INTERFACE: return "invoke-interface"; + case MOVE_RESULT: return "move-result"; + case MOVE_RESULT_PSEUDO: return "move-result-pseudo"; + case FILL_ARRAY_DATA: return "fill-array-data"; + } + + return "unknown-" + Hex.u1(opcode); + } + + /** + * Given an IF_* RegOp, returns the right-to-left flipped version. For + * example, IF_GT becomes IF_LT. + * + * @param opcode An IF_* RegOp + * @return flipped IF Regop + */ + public static int flippedIfOpcode(final int opcode) { + switch (opcode) { + case RegOps.IF_EQ: + case RegOps.IF_NE: + return opcode; + case RegOps.IF_LT: + return RegOps.IF_GT; + case RegOps.IF_GE: + return RegOps.IF_LE; + case RegOps.IF_LE: + return RegOps.IF_GE; + case RegOps.IF_GT: + return RegOps.IF_LT; + default: + throw new RuntimeException("Unrecognized IF regop: " + opcode); + } + } +} diff --git a/dx/src/com/android/jack/dx/rop/code/RegisterSpec.java b/dx/src/com/android/jack/dx/rop/code/RegisterSpec.java new file mode 100644 index 00000000..24600412 --- /dev/null +++ b/dx/src/com/android/jack/dx/rop/code/RegisterSpec.java @@ -0,0 +1,657 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.rop.code; + +import com.android.jack.dx.rop.cst.Constant; +import com.android.jack.dx.rop.cst.CstString; +import com.android.jack.dx.rop.type.Type; +import com.android.jack.dx.rop.type.TypeBearer; +import com.android.jack.dx.util.ToHuman; + +import java.util.HashMap; + +/** + * Combination of a register number and a type, used as the sources and + * destinations of register-based operations. + */ +public final class RegisterSpec + implements TypeBearer, ToHuman, Comparable { + /** {@code non-null;} string to prefix register numbers with */ + public static final String PREFIX = "v"; + + /** {@code non-null;} intern table for instances */ + private static final HashMap theInterns = + new HashMap(1000); + + /** {@code non-null;} common comparison instance used while interning */ + private static final ForComparison theInterningItem = new ForComparison(); + + /** {@code >= 0;} register number */ + private final int reg; + + /** {@code non-null;} type loaded or stored */ + private final TypeBearer type; + + /** + * {@code null-ok;} local variable info associated with this register, + * if any + */ + private final LocalItem local; + + /** + * Intern the given triple as an instance of this class. + * + * @param reg {@code >= 0;} the register number + * @param type {@code non-null;} the type (or possibly actual value) which + * is loaded from or stored to the indicated register + * @param local {@code null-ok;} the associated local variable, if any + * @return {@code non-null;} an appropriately-constructed instance + */ + private static RegisterSpec intern(int reg, TypeBearer type, + LocalItem local) { + synchronized (theInterns) { + theInterningItem.set(reg, type, local); + RegisterSpec found = theInterns.get(theInterningItem); + + if (found != null) { + return found; + } + + found = theInterningItem.toRegisterSpec(); + theInterns.put(found, found); + return found; + } + } + + /** + * Returns an instance for the given register number and type, with + * no variable info. This method is allowed to return shared + * instances (but doesn't necessarily do so). + * + * @param reg {@code >= 0;} the register number + * @param type {@code non-null;} the type (or possibly actual value) which + * is loaded from or stored to the indicated register + * @return {@code non-null;} an appropriately-constructed instance + */ + public static RegisterSpec make(int reg, TypeBearer type) { + return intern(reg, type, null); + } + + /** + * Returns an instance for the given register number, type, and + * variable info. This method is allowed to return shared + * instances (but doesn't necessarily do so). + * + * @param reg {@code >= 0;} the register number + * @param type {@code non-null;} the type (or possibly actual value) which + * is loaded from or stored to the indicated register + * @param local {@code non-null;} the associated local variable + * @return {@code non-null;} an appropriately-constructed instance + */ + public static RegisterSpec make(int reg, TypeBearer type, + LocalItem local) { + if (local == null) { + throw new NullPointerException("local == null"); + } + + return intern(reg, type, local); + } + + /** + * Returns an instance for the given register number, type, and + * variable info. This method is allowed to return shared + * instances (but doesn't necessarily do so). + * + * @param reg {@code >= 0;} the register number + * @param type {@code non-null;} the type (or possibly actual value) which + * is loaded from or stored to the indicated register + * @param local {@code null-ok;} the associated variable info or null for + * none + * @return {@code non-null;} an appropriately-constructed instance + */ + public static RegisterSpec makeLocalOptional( + int reg, TypeBearer type, LocalItem local) { + + return intern(reg, type, local); + } + + /** + * Gets the string form for the given register number. + * + * @param reg {@code >= 0;} the register number + * @return {@code non-null;} the string form + */ + public static String regString(int reg) { + return PREFIX + reg; + } + + /** + * Constructs an instance. This constructor is private. Use + * {@link #make}. + * + * @param reg {@code >= 0;} the register number + * @param type {@code non-null;} the type (or possibly actual value) which + * is loaded from or stored to the indicated register + * @param local {@code null-ok;} the associated local variable, if any + */ + private RegisterSpec(int reg, TypeBearer type, LocalItem local) { + if (reg < 0) { + throw new IllegalArgumentException("reg < 0"); + } + + if (type == null) { + throw new NullPointerException("type == null"); + } + + this.reg = reg; + this.type = type; + this.local = local; + } + + /** {@inheritDoc} */ + @Override + public boolean equals(Object other) { + if (!(other instanceof RegisterSpec)) { + if (other instanceof ForComparison) { + ForComparison fc = (ForComparison) other; + return equals(fc.reg, fc.type, fc.local); + } + return false; + } + + RegisterSpec spec = (RegisterSpec) other; + return equals(spec.reg, spec.type, spec.local); + } + + /** + * Like {@code equals}, but only consider the simple types of the + * registers. That is, this compares {@code getType()} on the types + * to ignore whatever arbitrary extra stuff might be carried around + * by an outer {@link TypeBearer}. + * + * @param other {@code null-ok;} spec to compare to + * @return {@code true} iff {@code this} and {@code other} are equal + * in the stated way + */ + public boolean equalsUsingSimpleType(RegisterSpec other) { + if (!matchesVariable(other)) { + return false; + } + + return (reg == other.reg); + } + + /** + * Like {@link #equalsUsingSimpleType} but ignoring the register number. + * This is useful to determine if two instances refer to the "same" + * local variable. + * + * @param other {@code null-ok;} spec to compare to + * @return {@code true} iff {@code this} and {@code other} are equal + * in the stated way + */ + public boolean matchesVariable(RegisterSpec other) { + if (other == null) { + return false; + } + + return type.getType().equals(other.type.getType()) + && ((local == other.local) + || ((local != null) && local.equals(other.local))); + } + + /** + * Helper for {@link #equals} and {@link #ForComparison.equals}, + * which actually does the test. + * + * @param reg value of the instance variable, for another instance + * @param type value of the instance variable, for another instance + * @param local value of the instance variable, for another instance + * @return whether this instance is equal to one with the given + * values + */ + private boolean equals(int reg, TypeBearer type, LocalItem local) { + return (this.reg == reg) + && this.type.equals(type) + && ((this.local == local) + || ((this.local != null) && this.local.equals(local))); + } + + /** + * Compares by (in priority order) register number, unwrapped type + * (that is types not {@link TypeBearer}s, and local info. + * + * @param other {@code non-null;} spec to compare to + * @return {@code -1..1;} standard result of comparison + */ + public int compareTo(RegisterSpec other) { + if (this.reg < other.reg) { + return -1; + } else if (this.reg > other.reg) { + return 1; + } + + int compare = type.getType().compareTo(other.type.getType()); + + if (compare != 0) { + return compare; + } + + if (this.local == null) { + return (other.local == null) ? 0 : -1; + } else if (other.local == null) { + return 1; + } + + return this.local.compareTo(other.local); + } + + /** {@inheritDoc} */ + @Override + public int hashCode() { + return hashCodeOf(reg, type, local); + } + + /** + * Helper for {@link #hashCode} and {@link #ForComparison.hashCode}, + * which actually does the calculation. + * + * @param reg value of the instance variable + * @param type value of the instance variable + * @param local value of the instance variable + * @return the hash code + */ + private static int hashCodeOf(int reg, TypeBearer type, LocalItem local) { + int hash = (local != null) ? local.hashCode() : 0; + + hash = (hash * 31 + type.hashCode()) * 31 + reg; + return hash; + } + + /** {@inheritDoc} */ + @Override + public String toString() { + return toString0(false); + } + + /** {@inheritDoc} */ + public String toHuman() { + return toString0(true); + } + + /** {@inheritDoc} */ + public Type getType() { + return type.getType(); + } + + /** {@inheritDoc} */ + public TypeBearer getFrameType() { + return type.getFrameType(); + } + + /** {@inheritDoc} */ + public final int getBasicType() { + return type.getBasicType(); + } + + /** {@inheritDoc} */ + public final int getBasicFrameType() { + return type.getBasicFrameType(); + } + + /** {@inheritDoc} */ + public final boolean isConstant() { + return false; + } + + /** + * Gets the register number. + * + * @return {@code >= 0;} the register number + */ + public int getReg() { + return reg; + } + + /** + * Gets the type (or actual value) which is loaded from or stored + * to the register associated with this instance. + * + * @return {@code non-null;} the type + */ + public TypeBearer getTypeBearer() { + return type; + } + + /** + * Gets the variable info associated with this instance, if any. + * + * @return {@code null-ok;} the variable info, or {@code null} if this + * instance has none + */ + public LocalItem getLocalItem() { + return local; + } + + /** + * Gets the next available register number after the one in this + * instance. This is equal to the register number plus the width + * (category) of the type used. Among other things, this may also + * be used to determine the minimum required register count + * implied by this instance. + * + * @return {@code >= 0;} the required registers size + */ + public int getNextReg() { + return reg + getCategory(); + } + + /** + * Gets the category of this instance's type. This is just a convenient + * shorthand for {@code getType().getCategory()}. + * + * @see #isCategory1 + * @see #isCategory2 + * @return {@code 1..2;} the category of this instance's type + */ + public int getCategory() { + return type.getType().getCategory(); + } + + /** + * Gets whether this instance's type is category 1. This is just a + * convenient shorthand for {@code getType().isCategory1()}. + * + * @see #getCategory + * @see #isCategory2 + * @return whether or not this instance's type is of category 1 + */ + public boolean isCategory1() { + return type.getType().isCategory1(); + } + + /** + * Gets whether this instance's type is category 2. This is just a + * convenient shorthand for {@code getType().isCategory2()}. + * + * @see #getCategory + * @see #isCategory1 + * @return whether or not this instance's type is of category 2 + */ + public boolean isCategory2() { + return type.getType().isCategory2(); + } + + /** + * Gets the string form for just the register number of this instance. + * + * @return {@code non-null;} the register string form + */ + public String regString() { + return regString(reg); + } + + /** + * Returns an instance that is the intersection between this instance + * and the given one, if any. The intersection is defined as follows: + * + *

    + *
  • If {@code other} is {@code null}, then the result + * is {@code null}. + *
  • If the register numbers don't match, then the intersection + * is {@code null}. Otherwise, the register number of the + * intersection is the same as the one in the two instances.
  • + *
  • If the types returned by {@code getType()} are not + * {@code equals()}, then the intersection is null.
  • + *
  • If the type bearers returned by {@code getTypeBearer()} + * are {@code equals()}, then the intersection's type bearer + * is the one from this instance. Otherwise, the intersection's + * type bearer is the {@code getType()} of this instance.
  • + *
  • If the locals are {@code equals()}, then the local info + * of the intersection is the local info of this instance. Otherwise, + * the local info of the intersection is {@code null}.
  • + *
+ * + * @param other {@code null-ok;} instance to intersect with (or {@code null}) + * @param localPrimary whether local variables are primary to the + * intersection; if {@code true}, then the only non-null + * results occur when registers being intersected have equal local + * infos (or both have {@code null} local infos) + * @return {@code null-ok;} the intersection + */ + public RegisterSpec intersect(RegisterSpec other, boolean localPrimary) { + if (this == other) { + // Easy out. + return this; + } + + if ((other == null) || (reg != other.getReg())) { + return null; + } + + LocalItem resultLocal = + ((local == null) || !local.equals(other.getLocalItem())) + ? null : local; + boolean sameName = (resultLocal == local); + + if (localPrimary && !sameName) { + return null; + } + + Type thisType = getType(); + Type otherType = other.getType(); + + // Note: Types are always interned. + if (thisType != otherType) { + return null; + } + + TypeBearer resultTypeBearer = + type.equals(other.getTypeBearer()) ? type : thisType; + + if ((resultTypeBearer == type) && sameName) { + // It turns out that the intersection is "this" after all. + return this; + } + + return (resultLocal == null) ? make(reg, resultTypeBearer) : + make(reg, resultTypeBearer, resultLocal); + } + + /** + * Returns an instance that is identical to this one, except that the + * register number is replaced by the given one. + * + * @param newReg {@code >= 0;} the new register number + * @return {@code non-null;} an appropriately-constructed instance + */ + public RegisterSpec withReg(int newReg) { + if (reg == newReg) { + return this; + } + + return makeLocalOptional(newReg, type, local); + } + + /** + * Returns an instance that is identical to this one, except that + * the type is replaced by the given one. + * + * @param newType {@code non-null;} the new type + * @return {@code non-null;} an appropriately-constructed instance + */ + public RegisterSpec withType(TypeBearer newType) { + return makeLocalOptional(reg, newType, local); + } + + /** + * Returns an instance that is identical to this one, except that the + * register number is offset by the given amount. + * + * @param delta the amount to offset the register number by + * @return {@code non-null;} an appropriately-constructed instance + */ + public RegisterSpec withOffset(int delta) { + if (delta == 0) { + return this; + } + + return withReg(reg + delta); + } + + /** + * Returns an instance that is identical to this one, except that + * the type bearer is replaced by the actual underlying type + * (thereby stripping off non-type information) with any + * initialization information stripped away as well. + * + * @return {@code non-null;} an appropriately-constructed instance + */ + public RegisterSpec withSimpleType() { + TypeBearer orig = type; + Type newType; + + if (orig instanceof Type) { + newType = (Type) orig; + } else { + newType = orig.getType(); + } + + if (newType.isUninitialized()) { + newType = newType.getInitializedType(); + } + + if (newType == orig) { + return this; + } + + return makeLocalOptional(reg, newType, local); + } + + /** + * Returns an instance that is identical to this one except that the + * local variable is as specified in the parameter. + * + * @param local {@code null-ok;} the local item or null for none + * @return an appropriate instance + */ + public RegisterSpec withLocalItem(LocalItem local) { + if ((this.local== local) + || ((this.local != null) && this.local.equals(local))) { + + return this; + } + + return makeLocalOptional(reg, type, local); + } + + + /** + * Helper for {@link #toString} and {@link #toHuman}. + * + * @param human whether to be human-oriented + * @return {@code non-null;} the string form + */ + private String toString0(boolean human) { + StringBuffer sb = new StringBuffer(40); + + sb.append(regString()); + sb.append(":"); + + if (local != null) { + sb.append(local.toString()); + } + + Type justType = type.getType(); + sb.append(justType); + + if (justType != type) { + sb.append("="); + if (human && (type instanceof CstString)) { + sb.append(((CstString) type).toQuoted()); + } else if (human && (type instanceof Constant)) { + sb.append(type.toHuman()); + } else { + sb.append(type); + } + } + + return sb.toString(); + } + + /** + * Holder of register spec data for the purposes of comparison (so that + * {@code RegisterSpec} itself can still keep {@code final} + * instance variables. + */ + private static class ForComparison { + /** {@code >= 0;} register number */ + private int reg; + + /** {@code non-null;} type loaded or stored */ + private TypeBearer type; + + /** + * {@code null-ok;} local variable associated with this + * register, if any + */ + private LocalItem local; + + /** + * Set all the instance variables. + * + * @param reg {@code >= 0;} the register number + * @param type {@code non-null;} the type (or possibly actual + * value) which is loaded from or stored to the indicated + * register + * @param local {@code null-ok;} the associated local variable, if any + * @return {@code non-null;} an appropriately-constructed instance + */ + public void set(int reg, TypeBearer type, LocalItem local) { + this.reg = reg; + this.type = type; + this.local = local; + } + + /** + * Construct a {@code RegisterSpec} of this instance's + * contents. + * + * @return {@code non-null;} an appropriately-constructed instance + */ + public RegisterSpec toRegisterSpec() { + return new RegisterSpec(reg, type, local); + } + + /** {@inheritDoc} */ + @Override + public boolean equals(Object other) { + if (!(other instanceof RegisterSpec)) { + return false; + } + + RegisterSpec spec = (RegisterSpec) other; + return spec.equals(reg, type, local); + } + + /** {@inheritDoc} */ + @Override + public int hashCode() { + return hashCodeOf(reg, type, local); + } + } +} diff --git a/dx/src/com/android/jack/dx/rop/code/RegisterSpecList.java b/dx/src/com/android/jack/dx/rop/code/RegisterSpecList.java new file mode 100644 index 00000000..50649fe7 --- /dev/null +++ b/dx/src/com/android/jack/dx/rop/code/RegisterSpecList.java @@ -0,0 +1,409 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.rop.code; + +import com.android.jack.dx.rop.type.Type; +import com.android.jack.dx.rop.type.TypeList; +import com.android.jack.dx.util.FixedSizeList; + +import java.util.BitSet; + +/** + * List of {@link RegisterSpec} instances. + */ +public final class RegisterSpecList + extends FixedSizeList implements TypeList { + /** {@code non-null;} no-element instance */ + public static final RegisterSpecList EMPTY = new RegisterSpecList(0); + + /** + * Makes a single-element instance. + * + * @param spec {@code non-null;} the element + * @return {@code non-null;} an appropriately-constructed instance + */ + public static RegisterSpecList make(RegisterSpec spec) { + RegisterSpecList result = new RegisterSpecList(1); + result.set(0, spec); + return result; + } + + /** + * Makes a two-element instance. + * + * @param spec0 {@code non-null;} the first element + * @param spec1 {@code non-null;} the second element + * @return {@code non-null;} an appropriately-constructed instance + */ + public static RegisterSpecList make(RegisterSpec spec0, + RegisterSpec spec1) { + RegisterSpecList result = new RegisterSpecList(2); + result.set(0, spec0); + result.set(1, spec1); + return result; + } + + /** + * Makes a three-element instance. + * + * @param spec0 {@code non-null;} the first element + * @param spec1 {@code non-null;} the second element + * @param spec2 {@code non-null;} the third element + * @return {@code non-null;} an appropriately-constructed instance + */ + public static RegisterSpecList make(RegisterSpec spec0, RegisterSpec spec1, + RegisterSpec spec2) { + RegisterSpecList result = new RegisterSpecList(3); + result.set(0, spec0); + result.set(1, spec1); + result.set(2, spec2); + return result; + } + + /** + * Makes a four-element instance. + * + * @param spec0 {@code non-null;} the first element + * @param spec1 {@code non-null;} the second element + * @param spec2 {@code non-null;} the third element + * @param spec3 {@code non-null;} the fourth element + * @return {@code non-null;} an appropriately-constructed instance + */ + public static RegisterSpecList make(RegisterSpec spec0, RegisterSpec spec1, + RegisterSpec spec2, + RegisterSpec spec3) { + RegisterSpecList result = new RegisterSpecList(4); + result.set(0, spec0); + result.set(1, spec1); + result.set(2, spec2); + result.set(3, spec3); + return result; + } + + /** + * Constructs an instance. All indices initially contain {@code null}. + * + * @param size the size of the list + */ + public RegisterSpecList(int size) { + super(size); + } + + /** {@inheritDoc} */ + public Type getType(int n) { + return get(n).getType().getType(); + } + + /** {@inheritDoc} */ + public int getWordCount() { + int sz = size(); + int result = 0; + + for (int i = 0; i < sz; i++) { + result += getType(i).getCategory(); + } + + return result; + } + + /** {@inheritDoc} */ + public TypeList withAddedType(Type type) { + throw new UnsupportedOperationException("unsupported"); + } + + /** + * Gets the indicated element. It is an error to call this with the + * index for an element which was never set; if you do that, this + * will throw {@code NullPointerException}. + * + * @param n {@code >= 0, < size();} which element + * @return {@code non-null;} the indicated element + */ + public RegisterSpec get(int n) { + return (RegisterSpec) get0(n); + } + + /** + * Returns a RegisterSpec in this list that uses the specified register, + * or null if there is none in this list. + * @param reg Register to find + * @return RegisterSpec that uses argument or null. + */ + public RegisterSpec specForRegister(int reg) { + int sz = size(); + for (int i = 0; i < sz; i++) { + RegisterSpec rs; + + rs = get(i); + + if (rs.getReg() == reg) { + return rs; + } + } + + return null; + } + + /** + * Returns the index of a RegisterSpec in this list that uses the specified + * register, or -1 if none in this list uses the register. + * @param reg Register to find + * @return index of RegisterSpec or -1 + */ + public int indexOfRegister(int reg) { + int sz = size(); + for (int i = 0; i < sz; i++) { + RegisterSpec rs; + + rs = get(i); + + if (rs.getReg() == reg) { + return i; + } + } + + return -1; + } + + /** + * Sets the element at the given index. + * + * @param n {@code >= 0, < size();} which element + * @param spec {@code non-null;} the value to store + */ + public void set(int n, RegisterSpec spec) { + set0(n, spec); + } + + /** + * Gets the minimum required register count implied by this + * instance. This is equal to the highest register number referred + * to plus the widest width (largest category) of the type used in + * that register. + * + * @return {@code >= 0;} the required registers size + */ + public int getRegistersSize() { + int sz = size(); + int result = 0; + + for (int i = 0; i < sz; i++) { + RegisterSpec spec = (RegisterSpec) get0(i); + if (spec != null) { + int min = spec.getNextReg(); + if (min > result) { + result = min; + } + } + } + + return result; + } + + /** + * Returns a new instance, which is the same as this instance, + * except that it has an additional element prepended to the original. + * Mutability of the result is inherited from the original. + * + * @param spec {@code non-null;} the new first spec (to prepend) + * @return {@code non-null;} an appropriately-constructed instance + */ + public RegisterSpecList withFirst(RegisterSpec spec) { + int sz = size(); + RegisterSpecList result = new RegisterSpecList(sz + 1); + + for (int i = 0; i < sz; i++) { + result.set0(i + 1, get0(i)); + } + + result.set0(0, spec); + if (isImmutable()) { + result.setImmutable(); + } + + return result; + } + + /** + * Returns a new instance, which is the same as this instance, + * except that its first element is removed. Mutability of the + * result is inherited from the original. + * + * @return {@code non-null;} an appropriately-constructed instance + */ + public RegisterSpecList withoutFirst() { + int newSize = size() - 1; + + if (newSize == 0) { + return EMPTY; + } + + RegisterSpecList result = new RegisterSpecList(newSize); + + for (int i = 0; i < newSize; i++) { + result.set0(i, get0(i + 1)); + } + + if (isImmutable()) { + result.setImmutable(); + } + + return result; + } + + /** + * Returns a new instance, which is the same as this instance, + * except that its last element is removed. Mutability of the + * result is inherited from the original. + * + * @return {@code non-null;} an appropriately-constructed instance + */ + public RegisterSpecList withoutLast() { + int newSize = size() - 1; + + if (newSize == 0) { + return EMPTY; + } + + RegisterSpecList result = new RegisterSpecList(newSize); + + for (int i = 0; i < newSize; i++) { + result.set0(i, get0(i)); + } + + if (isImmutable()) { + result.setImmutable(); + } + + return result; + } + + /** + * Returns a new instance, which contains a subset of the elements + * specified by the given BitSet. Indexes in the BitSet with a zero + * are included, while indexes with a one are excluded. Mutability + * of the result is inherited from the original. + * + * @param exclusionSet {@code non-null;} set of registers to exclude + * @return {@code non-null;} an appropriately-constructed instance + */ + public RegisterSpecList subset(BitSet exclusionSet) { + int newSize = size() - exclusionSet.cardinality(); + + if (newSize == 0) { + return EMPTY; + } + + RegisterSpecList result = new RegisterSpecList(newSize); + + int newIndex = 0; + for (int oldIndex = 0; oldIndex < size(); oldIndex++) { + if (!exclusionSet.get(oldIndex)) { + result.set0(newIndex, get0(oldIndex)); + newIndex++; + } + } + + if (isImmutable()) { + result.setImmutable(); + } + + return result; + } + + /** + * Returns an instance that is identical to this one, except that + * all register numbers are offset by the given amount. Mutability + * of the result is inherited from the original. + * + * @param delta the amount to offset the register numbers by + * @return {@code non-null;} an appropriately-constructed instance + */ + public RegisterSpecList withOffset(int delta) { + int sz = size(); + + if (sz == 0) { + // Don't bother making a new zero-element instance. + return this; + } + + RegisterSpecList result = new RegisterSpecList(sz); + + for (int i = 0; i < sz; i++) { + RegisterSpec one = (RegisterSpec) get0(i); + if (one != null) { + result.set0(i, one.withOffset(delta)); + } + } + + if (isImmutable()) { + result.setImmutable(); + } + + return result; + } + + /** + * Returns an instance that is identical to this one, except that + * all incompatible register numbers are renumbered sequentially from + * the given base, with the first number duplicated if indicated. If + * a null BitSet is given, it indicates all registers are compatible. + * + * @param base the base register number + * @param duplicateFirst whether to duplicate the first number + * @param compatRegs {@code null-ok;} either a {@code non-null} set of + * compatible registers, or {@code null} to indicate all registers are + * compatible + * @return {@code non-null;} an appropriately-constructed instance + */ + public RegisterSpecList withExpandedRegisters(int base, + boolean duplicateFirst, + BitSet compatRegs) { + int sz = size(); + + if (sz == 0) { + // Don't bother making a new zero-element instance. + return this; + } + + RegisterSpecList result = new RegisterSpecList(sz); + + for (int i = 0; i < sz; i++) { + RegisterSpec one = (RegisterSpec) get0(i); + boolean replace = (compatRegs == null) ? true : !compatRegs.get(i); + + if (replace) { + result.set0(i, one.withReg(base)); + if (!duplicateFirst) { + base += one.getCategory(); + } + } else { + result.set0(i, one); + } + + if (duplicateFirst) { + duplicateFirst = false; + } + } + + if (isImmutable()) { + result.setImmutable(); + } + + return result; + } +} diff --git a/dx/src/com/android/jack/dx/rop/code/RegisterSpecSet.java b/dx/src/com/android/jack/dx/rop/code/RegisterSpecSet.java new file mode 100644 index 00000000..4116155f --- /dev/null +++ b/dx/src/com/android/jack/dx/rop/code/RegisterSpecSet.java @@ -0,0 +1,396 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.rop.code; + +import com.android.jack.dx.util.MutabilityControl; + +/** + * Set of {@link RegisterSpec} instances, where a given register number + * may appear only once in the set. + */ +public final class RegisterSpecSet + extends MutabilityControl { + /** {@code non-null;} no-element instance */ + public static final RegisterSpecSet EMPTY = new RegisterSpecSet(0); + + /** + * {@code non-null;} array of register specs, where each element is + * {@code null} or is an instance whose {@code reg} + * matches the array index + */ + private final RegisterSpec[] specs; + + /** {@code >= -1;} size of the set or {@code -1} if not yet calculated */ + private int size; + + /** + * Constructs an instance. The instance is initially empty. + * + * @param maxSize {@code >= 0;} the maximum register number (exclusive) that + * may be represented in this instance + */ + public RegisterSpecSet(int maxSize) { + super(maxSize != 0); + + this.specs = new RegisterSpec[maxSize]; + this.size = 0; + } + + /** {@inheritDoc} */ + @Override + public boolean equals(Object other) { + if (!(other instanceof RegisterSpecSet)) { + return false; + } + + RegisterSpecSet otherSet = (RegisterSpecSet) other; + RegisterSpec[] otherSpecs = otherSet.specs; + int len = specs.length; + + if ((len != otherSpecs.length) || (size() != otherSet.size())) { + return false; + } + + for (int i = 0; i < len; i++) { + RegisterSpec s1 = specs[i]; + RegisterSpec s2 = otherSpecs[i]; + + if (s1 == s2) { + continue; + } + + if ((s1 == null) || !s1.equals(s2)) { + return false; + } + } + + return true; + } + + /** {@inheritDoc} */ + @Override + public int hashCode() { + int len = specs.length; + int hash = 0; + + for (int i = 0; i < len; i++) { + RegisterSpec spec = specs[i]; + int oneHash = (spec == null) ? 0 : spec.hashCode(); + hash = (hash * 31) + oneHash; + } + + return hash; + } + + /** {@inheritDoc} */ + @Override + public String toString() { + int len = specs.length; + StringBuffer sb = new StringBuffer(len * 25); + + sb.append('{'); + + boolean any = false; + for (int i = 0; i < len; i++) { + RegisterSpec spec = specs[i]; + if (spec != null) { + if (any) { + sb.append(", "); + } else { + any = true; + } + sb.append(spec); + } + } + + sb.append('}'); + return sb.toString(); + } + + /** + * Gets the maximum number of registers that may be in this instance, which + * is also the maximum-plus-one of register numbers that may be + * represented. + * + * @return {@code >= 0;} the maximum size + */ + public int getMaxSize() { + return specs.length; + } + + /** + * Gets the current size of this instance. + * + * @return {@code >= 0;} the size + */ + public int size() { + int result = size; + + if (result < 0) { + int len = specs.length; + + result = 0; + for (int i = 0; i < len; i++) { + if (specs[i] != null) { + result++; + } + } + + size = result; + } + + return result; + } + + /** + * Gets the element with the given register number, if any. + * + * @param reg {@code >= 0;} the desired register number + * @return {@code null-ok;} the element with the given register number or + * {@code null} if there is none + */ + public RegisterSpec get(int reg) { + try { + return specs[reg]; + } catch (ArrayIndexOutOfBoundsException ex) { + // Translate the exception. + throw new IllegalArgumentException("bogus reg"); + } + } + + /** + * Gets the element with the same register number as the given + * spec, if any. This is just a convenient shorthand for + * {@code get(spec.getReg())}. + * + * @param spec {@code non-null;} spec with the desired register number + * @return {@code null-ok;} the element with the matching register number or + * {@code null} if there is none + */ + public RegisterSpec get(RegisterSpec spec) { + return get(spec.getReg()); + } + + /** + * Returns the spec in this set that's currently associated with a + * given local (type, name, and signature), or {@code null} if there is + * none. This ignores the register number of the given spec but + * matches on everything else. + * + * @param spec {@code non-null;} local to look for + * @return {@code null-ok;} first register found that matches, if any + */ + public RegisterSpec findMatchingLocal(RegisterSpec spec) { + int length = specs.length; + + for (int reg = 0; reg < length; reg++) { + RegisterSpec s = specs[reg]; + + if (s == null) { + continue; + } + + if (spec.matchesVariable(s)) { + return s; + } + } + + return null; + } + + /** + * Returns the spec in this set that's currently associated with a given + * local (name and signature), or {@code null} if there is none. + * + * @param local {@code non-null;} local item to search for + * @return {@code null-ok;} first register found with matching name and signature + */ + public RegisterSpec localItemToSpec(LocalItem local) { + int length = specs.length; + + for (int reg = 0; reg < length; reg++) { + RegisterSpec spec = specs[reg]; + + if ((spec != null) && local.equals(spec.getLocalItem())) { + return spec; + } + } + + return null; + } + + /** + * Removes a spec from the set. Only the register number + * of the parameter is significant. + * + * @param toRemove {@code non-null;} register to remove. + */ + public void remove(RegisterSpec toRemove) { + try { + specs[toRemove.getReg()] = null; + size = -1; + } catch (ArrayIndexOutOfBoundsException ex) { + // Translate the exception. + throw new IllegalArgumentException("bogus reg"); + } + } + + /** + * Puts the given spec into the set. If there is already an element in + * the set with the same register number, it is replaced. Additionally, + * if the previous element is for a category-2 register, then that + * previous element is nullified. Finally, if the given spec is for + * a category-2 register, then the immediately subsequent element + * is nullified. + * + * @param spec {@code non-null;} the register spec to put in the instance + */ + public void put(RegisterSpec spec) { + throwIfImmutable(); + + if (spec == null) { + throw new NullPointerException("spec == null"); + } + + size = -1; + + try { + int reg = spec.getReg(); + specs[reg] = spec; + + if (reg > 0) { + int prevReg = reg - 1; + RegisterSpec prevSpec = specs[prevReg]; + if ((prevSpec != null) && (prevSpec.getCategory() == 2)) { + specs[prevReg] = null; + } + } + + if (spec.getCategory() == 2) { + specs[reg + 1] = null; + } + } catch (ArrayIndexOutOfBoundsException ex) { + // Translate the exception. + throw new IllegalArgumentException("spec.getReg() out of range"); + } + } + + /** + * Put the entire contents of the given set into this one. + * + * @param set {@code non-null;} the set to put into this instance + */ + public void putAll(RegisterSpecSet set) { + int max = set.getMaxSize(); + + for (int i = 0; i < max; i++) { + RegisterSpec spec = set.get(i); + if (spec != null) { + put(spec); + } + } + } + + /** + * Intersects this instance with the given one, modifying this + * instance. The intersection consists of the pairwise + * {@link RegisterSpec#intersect} of corresponding elements from + * this instance and the given one where both are non-null. + * + * @param other {@code non-null;} set to intersect with + * @param localPrimary whether local variables are primary to + * the intersection; if {@code true}, then the only non-null + * result elements occur when registers being intersected have + * equal names (or both have {@code null} names) + */ + public void intersect(RegisterSpecSet other, boolean localPrimary) { + throwIfImmutable(); + + RegisterSpec[] otherSpecs = other.specs; + int thisLen = specs.length; + int len = Math.min(thisLen, otherSpecs.length); + + size = -1; + + for (int i = 0; i < len; i++) { + RegisterSpec spec = specs[i]; + + if (spec == null) { + continue; + } + + RegisterSpec intersection = + spec.intersect(otherSpecs[i], localPrimary); + if (intersection != spec) { + specs[i] = intersection; + } + } + + for (int i = len; i < thisLen; i++) { + specs[i] = null; + } + } + + /** + * Returns an instance that is identical to this one, except that + * all register numbers are offset by the given amount. Mutability + * of the result is inherited from the original. + * + * @param delta the amount to offset the register numbers by + * @return {@code non-null;} an appropriately-constructed instance + */ + public RegisterSpecSet withOffset(int delta) { + int len = specs.length; + RegisterSpecSet result = new RegisterSpecSet(len + delta); + + for (int i = 0; i < len; i++) { + RegisterSpec spec = specs[i]; + if (spec != null) { + result.put(spec.withOffset(delta)); + } + } + + result.size = size; + + if (isImmutable()) { + result.setImmutable(); + } + + return result; + } + + /** + * Makes and return a mutable copy of this instance. + * + * @return {@code non-null;} the mutable copy + */ + public RegisterSpecSet mutableCopy() { + int len = specs.length; + RegisterSpecSet copy = new RegisterSpecSet(len); + + for (int i = 0; i < len; i++) { + RegisterSpec spec = specs[i]; + if (spec != null) { + copy.put(spec); + } + } + + copy.size = size; + + return copy; + } +} diff --git a/dx/src/com/android/jack/dx/rop/code/Rop.java b/dx/src/com/android/jack/dx/rop/code/Rop.java new file mode 100644 index 00000000..161a0eef --- /dev/null +++ b/dx/src/com/android/jack/dx/rop/code/Rop.java @@ -0,0 +1,407 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.rop.code; + +import com.android.jack.dx.rop.type.StdTypeList; +import com.android.jack.dx.rop.type.Type; +import com.android.jack.dx.rop.type.TypeList; +import com.android.jack.dx.util.Hex; + +/** + * Class that describes all the immutable parts of register-based operations. + */ +public final class Rop { + /** minimum {@code BRANCH_*} value */ + public static final int BRANCH_MIN = 1; + + /** indicates a non-branching op */ + public static final int BRANCH_NONE = 1; + + /** indicates a function/method return */ + public static final int BRANCH_RETURN = 2; + + /** indicates an unconditional goto */ + public static final int BRANCH_GOTO = 3; + + /** indicates a two-way branch */ + public static final int BRANCH_IF = 4; + + /** indicates a switch-style branch */ + public static final int BRANCH_SWITCH = 5; + + /** indicates a throw-style branch (both always-throws and may-throw) */ + public static final int BRANCH_THROW = 6; + + /** maximum {@code BRANCH_*} value */ + public static final int BRANCH_MAX = 6; + + /** the opcode; one of the constants in {@link RegOps} */ + private final int opcode; + + /** + * {@code non-null;} result type of this operation; {@link Type#VOID} for + * no-result operations + */ + private final Type result; + + /** {@code non-null;} types of all the sources of this operation */ + private final TypeList sources; + + /** {@code non-null;} list of possible types thrown by this operation */ + private final TypeList exceptions; + + /** + * the branchingness of this op; one of the {@code BRANCH_*} + * constants in this class + */ + private final int branchingness; + + /** whether this is a function/method call op or similar */ + private final boolean isCallLike; + + /** {@code null-ok;} nickname, if specified (used for debugging) */ + private final String nickname; + + /** + * Constructs an instance. This method is private. Use one of the + * public constructors. + * + * @param opcode the opcode; one of the constants in {@link RegOps} + * @param result {@code non-null;} result type of this operation; {@link + * Type#VOID} for no-result operations + * @param sources {@code non-null;} types of all the sources of this operation + * @param exceptions {@code non-null;} list of possible types thrown by this + * operation + * @param branchingness the branchingness of this op; one of the + * {@code BRANCH_*} constants + * @param isCallLike whether the op is a function/method call or similar + * @param nickname {@code null-ok;} optional nickname (used for debugging) + */ + public Rop(int opcode, Type result, TypeList sources, + TypeList exceptions, int branchingness, boolean isCallLike, + String nickname) { + if (result == null) { + throw new NullPointerException("result == null"); + } + + if (sources == null) { + throw new NullPointerException("sources == null"); + } + + if (exceptions == null) { + throw new NullPointerException("exceptions == null"); + } + + if ((branchingness < BRANCH_MIN) || (branchingness > BRANCH_MAX)) { + throw new IllegalArgumentException("bogus branchingness"); + } + + if ((exceptions.size() != 0) && (branchingness != BRANCH_THROW)) { + throw new IllegalArgumentException("exceptions / branchingness " + + "mismatch"); + } + + this.opcode = opcode; + this.result = result; + this.sources = sources; + this.exceptions = exceptions; + this.branchingness = branchingness; + this.isCallLike = isCallLike; + this.nickname = nickname; + } + + /** + * Constructs an instance. The constructed instance is never a + * call-like op (see {@link #isCallLike}). + * + * @param opcode the opcode; one of the constants in {@link RegOps} + * @param result {@code non-null;} result type of this operation; {@link + * Type#VOID} for no-result operations + * @param sources {@code non-null;} types of all the sources of this operation + * @param exceptions {@code non-null;} list of possible types thrown by this + * operation + * @param branchingness the branchingness of this op; one of the + * {@code BRANCH_*} constants + * @param nickname {@code null-ok;} optional nickname (used for debugging) + */ + public Rop(int opcode, Type result, TypeList sources, + TypeList exceptions, int branchingness, String nickname) { + this(opcode, result, sources, exceptions, branchingness, false, + nickname); + } + + /** + * Constructs a no-exception instance. The constructed instance is never a + * call-like op (see {@link #isCallLike}). + * + * @param opcode the opcode; one of the constants in {@link RegOps} + * @param result {@code non-null;} result type of this operation; {@link + * Type#VOID} for no-result operations + * @param sources {@code non-null;} types of all the sources of this operation + * @param branchingness the branchingness of this op; one of the + * {@code BRANCH_*} constants + * @param nickname {@code null-ok;} optional nickname (used for debugging) + */ + public Rop(int opcode, Type result, TypeList sources, int branchingness, + String nickname) { + this(opcode, result, sources, StdTypeList.EMPTY, branchingness, false, + nickname); + } + + /** + * Constructs a non-branching no-exception instance. The + * {@code branchingness} is always {@code BRANCH_NONE}, + * and it is never a call-like op (see {@link #isCallLike}). + * + * @param opcode the opcode; one of the constants in {@link RegOps} + * @param result {@code non-null;} result type of this operation; {@link + * Type#VOID} for no-result operations + * @param sources {@code non-null;} types of all the sources of this operation + * @param nickname {@code null-ok;} optional nickname (used for debugging) + */ + public Rop(int opcode, Type result, TypeList sources, String nickname) { + this(opcode, result, sources, StdTypeList.EMPTY, Rop.BRANCH_NONE, + false, nickname); + } + + /** + * Constructs a non-empty exceptions instance. Its + * {@code branchingness} is always {@code BRANCH_THROW}, + * but it is never a call-like op (see {@link #isCallLike}). + * + * @param opcode the opcode; one of the constants in {@link RegOps} + * @param result {@code non-null;} result type of this operation; {@link + * Type#VOID} for no-result operations + * @param sources {@code non-null;} types of all the sources of this operation + * @param exceptions {@code non-null;} list of possible types thrown by this + * operation + * @param nickname {@code null-ok;} optional nickname (used for debugging) + */ + public Rop(int opcode, Type result, TypeList sources, TypeList exceptions, + String nickname) { + this(opcode, result, sources, exceptions, Rop.BRANCH_THROW, false, + nickname); + } + + /** + * Constructs a non-nicknamed instance with non-empty exceptions, which + * is always a call-like op (see {@link #isCallLike}). Its + * {@code branchingness} is always {@code BRANCH_THROW}. + * + * @param opcode the opcode; one of the constants in {@link RegOps} + * @param sources {@code non-null;} types of all the sources of this operation + * @param exceptions {@code non-null;} list of possible types thrown by this + * operation + */ + public Rop(int opcode, TypeList sources, TypeList exceptions) { + this(opcode, Type.VOID, sources, exceptions, Rop.BRANCH_THROW, true, + null); + } + + /** {@inheritDoc} */ + @Override + public boolean equals(Object other) { + if (this == other) { + // Easy out. + return true; + } + + if (!(other instanceof Rop)) { + return false; + } + + Rop rop = (Rop) other; + + return (opcode == rop.opcode) && + (branchingness == rop.branchingness) && + (result == rop.result) && + sources.equals(rop.sources) && + exceptions.equals(rop.exceptions); + } + + /** {@inheritDoc} */ + @Override + public int hashCode() { + int h = (opcode * 31) + branchingness; + h = (h * 31) + result.hashCode(); + h = (h * 31) + sources.hashCode(); + h = (h * 31) + exceptions.hashCode(); + + return h; + } + + /** {@inheritDoc} */ + @Override + public String toString() { + StringBuffer sb = new StringBuffer(40); + + sb.append("Rop{"); + + sb.append(RegOps.opName(opcode)); + + if (result != Type.VOID) { + sb.append(" "); + sb.append(result); + } else { + sb.append(" ."); + } + + sb.append(" <-"); + + int sz = sources.size(); + if (sz == 0) { + sb.append(" ."); + } else { + for (int i = 0; i < sz; i++) { + sb.append(' '); + sb.append(sources.getType(i)); + } + } + + if (isCallLike) { + sb.append(" call"); + } + + sz = exceptions.size(); + if (sz != 0) { + sb.append(" throws"); + for (int i = 0; i < sz; i++) { + sb.append(' '); + Type one = exceptions.getType(i); + if (one == Type.THROWABLE) { + sb.append(""); + } else { + sb.append(exceptions.getType(i)); + } + } + } else { + switch (branchingness) { + case BRANCH_NONE: sb.append(" flows"); break; + case BRANCH_RETURN: sb.append(" returns"); break; + case BRANCH_GOTO: sb.append(" gotos"); break; + case BRANCH_IF: sb.append(" ifs"); break; + case BRANCH_SWITCH: sb.append(" switches"); break; + default: sb.append(" " + Hex.u1(branchingness)); break; + } + } + + sb.append('}'); + + return sb.toString(); + } + + /** + * Gets the opcode. + * + * @return the opcode + */ + public int getOpcode() { + return opcode; + } + + /** + * Gets the result type. A return value of {@link Type#VOID} + * means this operation returns nothing. + * + * @return {@code null-ok;} the result spec + */ + public Type getResult() { + return result; + } + + /** + * Gets the source types. + * + * @return {@code non-null;} the source types + */ + public TypeList getSources() { + return sources; + } + + /** + * Gets the list of exception types that might be thrown. + * + * @return {@code non-null;} the list of exception types + */ + public TypeList getExceptions() { + return exceptions; + } + + /** + * Gets the branchingness of this instance. + * + * @return the branchingness + */ + public int getBranchingness() { + return branchingness; + } + + /** + * Gets whether this opcode is a function/method call or similar. + * + * @return {@code true} iff this opcode is call-like + */ + public boolean isCallLike() { + return isCallLike; + } + + + /** + * Gets whether this opcode is commutative (the order of its sources are + * unimportant) or not. All commutative Rops have exactly two sources and + * have no branchiness. + * + * @return true if rop is commutative + */ + public boolean isCommutative() { + switch (opcode) { + case RegOps.AND: + case RegOps.OR: + case RegOps.XOR: + case RegOps.ADD: + case RegOps.MUL: + return true; + default: + return false; + } + } + + /** + * Gets the nickname. If this instance has no nickname, this returns + * the result of calling {@link #toString}. + * + * @return {@code non-null;} the nickname + */ + public String getNickname() { + if (nickname != null) { + return nickname; + } + + return toString(); + } + + /** + * Gets whether this operation can possibly throw an exception. This + * is just a convenient wrapper for + * {@code getExceptions().size() != 0}. + * + * @return {@code true} iff this operation can possibly throw + */ + public final boolean canThrow() { + return (exceptions.size() != 0); + } +} diff --git a/dx/src/com/android/jack/dx/rop/code/RopMethod.java b/dx/src/com/android/jack/dx/rop/code/RopMethod.java new file mode 100644 index 00000000..412dc0d6 --- /dev/null +++ b/dx/src/com/android/jack/dx/rop/code/RopMethod.java @@ -0,0 +1,207 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.rop.code; + +import com.android.jack.dx.util.Bits; +import com.android.jack.dx.util.Hex; +import com.android.jack.dx.util.IntList; + +/** + * All of the parts that make up a method at the rop layer. + */ +public final class RopMethod { + /** {@code non-null;} basic block list of the method */ + private final BasicBlockList blocks; + + /** {@code >= 0;} label for the block which starts the method */ + private final int firstLabel; + + /** + * {@code null-ok;} array of predecessors for each block, indexed by block + * label + */ + private IntList[] predecessors; + + /** + * {@code null-ok;} the predecessors for the implicit "exit" block, that is + * the labels for the blocks that return, if calculated + */ + private IntList exitPredecessors; + + /** + * Constructs an instance. + * + * @param blocks {@code non-null;} basic block list of the method + * @param firstLabel {@code >= 0;} the label of the first block to execute + */ + public RopMethod(BasicBlockList blocks, int firstLabel) { + if (blocks == null) { + throw new NullPointerException("blocks == null"); + } + + if (firstLabel < 0) { + throw new IllegalArgumentException("firstLabel < 0"); + } + + this.blocks = blocks; + this.firstLabel = firstLabel; + + this.predecessors = null; + this.exitPredecessors = null; + } + + /** + * Gets the basic block list for this method. + * + * @return {@code non-null;} the list + */ + public BasicBlockList getBlocks() { + return blocks; + } + + /** + * Gets the label for the first block in the method that this list + * represents. + * + * @return {@code >= 0;} the first-block label + */ + public int getFirstLabel() { + return firstLabel; + } + + /** + * Gets the predecessors associated with the given block. This throws + * an exception if there is no block with the given label. + * + * @param label {@code >= 0;} the label of the block in question + * @return {@code non-null;} the predecessors of that block + */ + public IntList labelToPredecessors(int label) { + if (exitPredecessors == null) { + calcPredecessors(); + } + + IntList result = predecessors[label]; + + if (result == null) { + throw new RuntimeException("no such block: " + Hex.u2(label)); + } + + return result; + } + + /** + * Gets the exit predecessors for this instance. + * + * @return {@code non-null;} the exit predecessors + */ + public IntList getExitPredecessors() { + if (exitPredecessors == null) { + calcPredecessors(); + } + + return exitPredecessors; + } + + + /** + * Returns an instance that is identical to this one, except that + * the registers in each instruction are offset by the given + * amount. + * + * @param delta the amount to offset register numbers by + * @return {@code non-null;} an appropriately-constructed instance + */ + public RopMethod withRegisterOffset(int delta) { + RopMethod result = new RopMethod(blocks.withRegisterOffset(delta), + firstLabel); + + if (exitPredecessors != null) { + /* + * The predecessors have been calculated. It's safe to + * inject these into the new instance, since the + * transformation being applied doesn't affect the + * predecessors. + */ + result.exitPredecessors = exitPredecessors; + result.predecessors = predecessors; + } + + return result; + } + + /** + * Calculates the predecessor sets for each block as well as for the + * exit. + */ + private void calcPredecessors() { + int maxLabel = blocks.getMaxLabel(); + IntList[] predecessors = new IntList[maxLabel]; + IntList exitPredecessors = new IntList(10); + int sz = blocks.size(); + + /* + * For each block, find its successors, and add the block's label to + * the successor's predecessors. + */ + for (int i = 0; i < sz; i++) { + BasicBlock one = blocks.get(i); + int label = one.getLabel(); + IntList successors = one.getSuccessors(); + int ssz = successors.size(); + if (ssz == 0) { + // This block exits. + exitPredecessors.add(label); + } else { + for (int j = 0; j < ssz; j++) { + int succLabel = successors.get(j); + IntList succPreds = predecessors[succLabel]; + if (succPreds == null) { + succPreds = new IntList(10); + predecessors[succLabel] = succPreds; + } + succPreds.add(label); + } + } + } + + // Sort and immutablize all the predecessor lists. + for (int i = 0; i < maxLabel; i++) { + IntList preds = predecessors[i]; + if (preds != null) { + preds.sort(); + preds.setImmutable(); + } + } + + exitPredecessors.sort(); + exitPredecessors.setImmutable(); + + /* + * The start label might not ever have had any predecessors + * added to it (probably doesn't, because of how Java gets + * translated into rop form). So, check for this and rectify + * the situation if required. + */ + if (predecessors[firstLabel] == null) { + predecessors[firstLabel] = IntList.EMPTY; + } + + this.predecessors = predecessors; + this.exitPredecessors = exitPredecessors; + } +} diff --git a/dx/src/com/android/jack/dx/rop/code/Rops.java b/dx/src/com/android/jack/dx/rop/code/Rops.java new file mode 100644 index 00000000..23deb8d7 --- /dev/null +++ b/dx/src/com/android/jack/dx/rop/code/Rops.java @@ -0,0 +1,2090 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.rop.code; + +import com.android.jack.dx.rop.cst.Constant; +import com.android.jack.dx.rop.cst.CstBaseMethodRef; +import com.android.jack.dx.rop.cst.CstMethodRef; +import com.android.jack.dx.rop.cst.CstType; +import com.android.jack.dx.rop.type.Prototype; +import com.android.jack.dx.rop.type.StdTypeList; +import com.android.jack.dx.rop.type.Type; +import com.android.jack.dx.rop.type.TypeBearer; +import com.android.jack.dx.rop.type.TypeList; + +/** + * Standard instances of {@link Rop}. + */ +public final class Rops { + /** {@code nop()} */ + public static final Rop NOP = + new Rop(RegOps.NOP, Type.VOID, StdTypeList.EMPTY, "nop"); + + /** {@code r,x: int :: r = x;} */ + public static final Rop MOVE_INT = + new Rop(RegOps.MOVE, Type.INT, StdTypeList.INT, "move-int"); + + /** {@code r,x: long :: r = x;} */ + public static final Rop MOVE_LONG = + new Rop(RegOps.MOVE, Type.LONG, StdTypeList.LONG, "move-long"); + + /** {@code r,x: float :: r = x;} */ + public static final Rop MOVE_FLOAT = + new Rop(RegOps.MOVE, Type.FLOAT, StdTypeList.FLOAT, "move-float"); + + /** {@code r,x: double :: r = x;} */ + public static final Rop MOVE_DOUBLE = + new Rop(RegOps.MOVE, Type.DOUBLE, StdTypeList.DOUBLE, "move-double"); + + /** {@code r,x: Object :: r = x;} */ + public static final Rop MOVE_OBJECT = + new Rop(RegOps.MOVE, Type.OBJECT, StdTypeList.OBJECT, "move-object"); + + /** + * {@code r,x: ReturnAddress :: r = x;} + * + * Note that this rop-form instruction has no dex-form equivilent and + * must be removed before the dex conversion. + */ + public static final Rop MOVE_RETURN_ADDRESS = + new Rop(RegOps.MOVE, Type.RETURN_ADDRESS, + StdTypeList.RETURN_ADDRESS, "move-return-address"); + + /** {@code r,param(x): int :: r = param(x);} */ + public static final Rop MOVE_PARAM_INT = + new Rop(RegOps.MOVE_PARAM, Type.INT, StdTypeList.EMPTY, + "move-param-int"); + + /** {@code r,param(x): long :: r = param(x);} */ + public static final Rop MOVE_PARAM_LONG = + new Rop(RegOps.MOVE_PARAM, Type.LONG, StdTypeList.EMPTY, + "move-param-long"); + + /** {@code r,param(x): float :: r = param(x);} */ + public static final Rop MOVE_PARAM_FLOAT = + new Rop(RegOps.MOVE_PARAM, Type.FLOAT, StdTypeList.EMPTY, + "move-param-float"); + + /** {@code r,param(x): double :: r = param(x);} */ + public static final Rop MOVE_PARAM_DOUBLE = + new Rop(RegOps.MOVE_PARAM, Type.DOUBLE, StdTypeList.EMPTY, + "move-param-double"); + + /** {@code r,param(x): Object :: r = param(x);} */ + public static final Rop MOVE_PARAM_OBJECT = + new Rop(RegOps.MOVE_PARAM, Type.OBJECT, StdTypeList.EMPTY, + "move-param-object"); + + /** {@code r, literal: int :: r = literal;} */ + public static final Rop CONST_INT = + new Rop(RegOps.CONST, Type.INT, StdTypeList.EMPTY, "const-int"); + + /** {@code r, literal: long :: r = literal;} */ + public static final Rop CONST_LONG = + new Rop(RegOps.CONST, Type.LONG, StdTypeList.EMPTY, "const-long"); + + /** {@code r, literal: float :: r = literal;} */ + public static final Rop CONST_FLOAT = + new Rop(RegOps.CONST, Type.FLOAT, StdTypeList.EMPTY, "const-float"); + + /** {@code r, literal: double :: r = literal;} */ + public static final Rop CONST_DOUBLE = + new Rop(RegOps.CONST, Type.DOUBLE, StdTypeList.EMPTY, "const-double"); + + /** {@code r, literal: Object :: r = literal;} */ + public static final Rop CONST_OBJECT = + new Rop(RegOps.CONST, Type.OBJECT, StdTypeList.EMPTY, + Exceptions.LIST_Error, "const-object"); + + /** {@code r, literal: Object :: r = literal;} */ + public static final Rop CONST_OBJECT_NOTHROW = + new Rop(RegOps.CONST, Type.OBJECT, StdTypeList.EMPTY, + "const-object-nothrow"); + + /** {@code goto label} */ + public static final Rop GOTO = + new Rop(RegOps.GOTO, Type.VOID, StdTypeList.EMPTY, Rop.BRANCH_GOTO, + "goto"); + + /** {@code x: int :: if (x == 0) goto label} */ + public static final Rop IF_EQZ_INT = + new Rop(RegOps.IF_EQ, Type.VOID, StdTypeList.INT, Rop.BRANCH_IF, + "if-eqz-int"); + + /** {@code x: int :: if (x != 0) goto label} */ + public static final Rop IF_NEZ_INT = + new Rop(RegOps.IF_NE, Type.VOID, StdTypeList.INT, Rop.BRANCH_IF, + "if-nez-int"); + + /** {@code x: int :: if (x < 0) goto label} */ + public static final Rop IF_LTZ_INT = + new Rop(RegOps.IF_LT, Type.VOID, StdTypeList.INT, Rop.BRANCH_IF, + "if-ltz-int"); + + /** {@code x: int :: if (x >= 0) goto label} */ + public static final Rop IF_GEZ_INT = + new Rop(RegOps.IF_GE, Type.VOID, StdTypeList.INT, Rop.BRANCH_IF, + "if-gez-int"); + + /** {@code x: int :: if (x <= 0) goto label} */ + public static final Rop IF_LEZ_INT = + new Rop(RegOps.IF_LE, Type.VOID, StdTypeList.INT, Rop.BRANCH_IF, + "if-lez-int"); + + /** {@code x: int :: if (x > 0) goto label} */ + public static final Rop IF_GTZ_INT = + new Rop(RegOps.IF_GT, Type.VOID, StdTypeList.INT, Rop.BRANCH_IF, + "if-gtz-int"); + + /** {@code x: Object :: if (x == null) goto label} */ + public static final Rop IF_EQZ_OBJECT = + new Rop(RegOps.IF_EQ, Type.VOID, StdTypeList.OBJECT, Rop.BRANCH_IF, + "if-eqz-object"); + + /** {@code x: Object :: if (x != null) goto label} */ + public static final Rop IF_NEZ_OBJECT = + new Rop(RegOps.IF_NE, Type.VOID, StdTypeList.OBJECT, Rop.BRANCH_IF, + "if-nez-object"); + + /** {@code x,y: int :: if (x == y) goto label} */ + public static final Rop IF_EQ_INT = + new Rop(RegOps.IF_EQ, Type.VOID, StdTypeList.INT_INT, Rop.BRANCH_IF, + "if-eq-int"); + + /** {@code x,y: int :: if (x != y) goto label} */ + public static final Rop IF_NE_INT = + new Rop(RegOps.IF_NE, Type.VOID, StdTypeList.INT_INT, Rop.BRANCH_IF, + "if-ne-int"); + + /** {@code x,y: int :: if (x < y) goto label} */ + public static final Rop IF_LT_INT = + new Rop(RegOps.IF_LT, Type.VOID, StdTypeList.INT_INT, Rop.BRANCH_IF, + "if-lt-int"); + + /** {@code x,y: int :: if (x >= y) goto label} */ + public static final Rop IF_GE_INT = + new Rop(RegOps.IF_GE, Type.VOID, StdTypeList.INT_INT, Rop.BRANCH_IF, + "if-ge-int"); + + /** {@code x,y: int :: if (x <= y) goto label} */ + public static final Rop IF_LE_INT = + new Rop(RegOps.IF_LE, Type.VOID, StdTypeList.INT_INT, Rop.BRANCH_IF, + "if-le-int"); + + /** {@code x,y: int :: if (x > y) goto label} */ + public static final Rop IF_GT_INT = + new Rop(RegOps.IF_GT, Type.VOID, StdTypeList.INT_INT, Rop.BRANCH_IF, + "if-gt-int"); + + /** {@code x,y: Object :: if (x == y) goto label} */ + public static final Rop IF_EQ_OBJECT = + new Rop(RegOps.IF_EQ, Type.VOID, StdTypeList.OBJECT_OBJECT, + Rop.BRANCH_IF, "if-eq-object"); + + /** {@code x,y: Object :: if (x != y) goto label} */ + public static final Rop IF_NE_OBJECT = + new Rop(RegOps.IF_NE, Type.VOID, StdTypeList.OBJECT_OBJECT, + Rop.BRANCH_IF, "if-ne-object"); + + /** {@code x: int :: goto switchtable[x]} */ + public static final Rop SWITCH = + new Rop(RegOps.SWITCH, Type.VOID, StdTypeList.INT, Rop.BRANCH_SWITCH, + "switch"); + + /** {@code r,x,y: int :: r = x + y;} */ + public static final Rop ADD_INT = + new Rop(RegOps.ADD, Type.INT, StdTypeList.INT_INT, "add-int"); + + /** {@code r,x,y: long :: r = x + y;} */ + public static final Rop ADD_LONG = + new Rop(RegOps.ADD, Type.LONG, StdTypeList.LONG_LONG, "add-long"); + + /** {@code r,x,y: float :: r = x + y;} */ + public static final Rop ADD_FLOAT = + new Rop(RegOps.ADD, Type.FLOAT, StdTypeList.FLOAT_FLOAT, "add-float"); + + /** {@code r,x,y: double :: r = x + y;} */ + public static final Rop ADD_DOUBLE = + new Rop(RegOps.ADD, Type.DOUBLE, StdTypeList.DOUBLE_DOUBLE, + Rop.BRANCH_NONE, "add-double"); + + /** {@code r,x,y: int :: r = x - y;} */ + public static final Rop SUB_INT = + new Rop(RegOps.SUB, Type.INT, StdTypeList.INT_INT, "sub-int"); + + /** {@code r,x,y: long :: r = x - y;} */ + public static final Rop SUB_LONG = + new Rop(RegOps.SUB, Type.LONG, StdTypeList.LONG_LONG, "sub-long"); + + /** {@code r,x,y: float :: r = x - y;} */ + public static final Rop SUB_FLOAT = + new Rop(RegOps.SUB, Type.FLOAT, StdTypeList.FLOAT_FLOAT, "sub-float"); + + /** {@code r,x,y: double :: r = x - y;} */ + public static final Rop SUB_DOUBLE = + new Rop(RegOps.SUB, Type.DOUBLE, StdTypeList.DOUBLE_DOUBLE, + Rop.BRANCH_NONE, "sub-double"); + + /** {@code r,x,y: int :: r = x * y;} */ + public static final Rop MUL_INT = + new Rop(RegOps.MUL, Type.INT, StdTypeList.INT_INT, "mul-int"); + + /** {@code r,x,y: long :: r = x * y;} */ + public static final Rop MUL_LONG = + new Rop(RegOps.MUL, Type.LONG, StdTypeList.LONG_LONG, "mul-long"); + + /** {@code r,x,y: float :: r = x * y;} */ + public static final Rop MUL_FLOAT = + new Rop(RegOps.MUL, Type.FLOAT, StdTypeList.FLOAT_FLOAT, "mul-float"); + + /** {@code r,x,y: double :: r = x * y;} */ + public static final Rop MUL_DOUBLE = + new Rop(RegOps.MUL, Type.DOUBLE, StdTypeList.DOUBLE_DOUBLE, + Rop.BRANCH_NONE, "mul-double"); + + /** {@code r,x,y: int :: r = x / y;} */ + public static final Rop DIV_INT = + new Rop(RegOps.DIV, Type.INT, StdTypeList.INT_INT, + Exceptions.LIST_Error_ArithmeticException, "div-int"); + + /** {@code r,x,y: long :: r = x / y;} */ + public static final Rop DIV_LONG = + new Rop(RegOps.DIV, Type.LONG, StdTypeList.LONG_LONG, + Exceptions.LIST_Error_ArithmeticException, "div-long"); + + /** {@code r,x,y: float :: r = x / y;} */ + public static final Rop DIV_FLOAT = + new Rop(RegOps.DIV, Type.FLOAT, StdTypeList.FLOAT_FLOAT, "div-float"); + + /** {@code r,x,y: double :: r = x / y;} */ + public static final Rop DIV_DOUBLE = + new Rop(RegOps.DIV, Type.DOUBLE, StdTypeList.DOUBLE_DOUBLE, + "div-double"); + + /** {@code r,x,y: int :: r = x % y;} */ + public static final Rop REM_INT = + new Rop(RegOps.REM, Type.INT, StdTypeList.INT_INT, + Exceptions.LIST_Error_ArithmeticException, "rem-int"); + + /** {@code r,x,y: long :: r = x % y;} */ + public static final Rop REM_LONG = + new Rop(RegOps.REM, Type.LONG, StdTypeList.LONG_LONG, + Exceptions.LIST_Error_ArithmeticException, "rem-long"); + + /** {@code r,x,y: float :: r = x % y;} */ + public static final Rop REM_FLOAT = + new Rop(RegOps.REM, Type.FLOAT, StdTypeList.FLOAT_FLOAT, "rem-float"); + + /** {@code r,x,y: double :: r = x % y;} */ + public static final Rop REM_DOUBLE = + new Rop(RegOps.REM, Type.DOUBLE, StdTypeList.DOUBLE_DOUBLE, + "rem-double"); + + /** {@code r,x: int :: r = -x;} */ + public static final Rop NEG_INT = + new Rop(RegOps.NEG, Type.INT, StdTypeList.INT, "neg-int"); + + /** {@code r,x: long :: r = -x;} */ + public static final Rop NEG_LONG = + new Rop(RegOps.NEG, Type.LONG, StdTypeList.LONG, "neg-long"); + + /** {@code r,x: float :: r = -x;} */ + public static final Rop NEG_FLOAT = + new Rop(RegOps.NEG, Type.FLOAT, StdTypeList.FLOAT, "neg-float"); + + /** {@code r,x: double :: r = -x;} */ + public static final Rop NEG_DOUBLE = + new Rop(RegOps.NEG, Type.DOUBLE, StdTypeList.DOUBLE, "neg-double"); + + /** {@code r,x,y: int :: r = x & y;} */ + public static final Rop AND_INT = + new Rop(RegOps.AND, Type.INT, StdTypeList.INT_INT, "and-int"); + + /** {@code r,x,y: long :: r = x & y;} */ + public static final Rop AND_LONG = + new Rop(RegOps.AND, Type.LONG, StdTypeList.LONG_LONG, "and-long"); + + /** {@code r,x,y: int :: r = x | y;} */ + public static final Rop OR_INT = + new Rop(RegOps.OR, Type.INT, StdTypeList.INT_INT, "or-int"); + + /** {@code r,x,y: long :: r = x | y;} */ + public static final Rop OR_LONG = + new Rop(RegOps.OR, Type.LONG, StdTypeList.LONG_LONG, "or-long"); + + /** {@code r,x,y: int :: r = x ^ y;} */ + public static final Rop XOR_INT = + new Rop(RegOps.XOR, Type.INT, StdTypeList.INT_INT, "xor-int"); + + /** {@code r,x,y: long :: r = x ^ y;} */ + public static final Rop XOR_LONG = + new Rop(RegOps.XOR, Type.LONG, StdTypeList.LONG_LONG, "xor-long"); + + /** {@code r,x,y: int :: r = x << y;} */ + public static final Rop SHL_INT = + new Rop(RegOps.SHL, Type.INT, StdTypeList.INT_INT, "shl-int"); + + /** {@code r,x: long; y: int :: r = x << y;} */ + public static final Rop SHL_LONG = + new Rop(RegOps.SHL, Type.LONG, StdTypeList.LONG_INT, "shl-long"); + + /** {@code r,x,y: int :: r = x >> y;} */ + public static final Rop SHR_INT = + new Rop(RegOps.SHR, Type.INT, StdTypeList.INT_INT, "shr-int"); + + /** {@code r,x: long; y: int :: r = x >> y;} */ + public static final Rop SHR_LONG = + new Rop(RegOps.SHR, Type.LONG, StdTypeList.LONG_INT, "shr-long"); + + /** {@code r,x,y: int :: r = x >>> y;} */ + public static final Rop USHR_INT = + new Rop(RegOps.USHR, Type.INT, StdTypeList.INT_INT, "ushr-int"); + + /** {@code r,x: long; y: int :: r = x >>> y;} */ + public static final Rop USHR_LONG = + new Rop(RegOps.USHR, Type.LONG, StdTypeList.LONG_INT, "ushr-long"); + + /** {@code r,x: int :: r = ~x;} */ + public static final Rop NOT_INT = + new Rop(RegOps.NOT, Type.INT, StdTypeList.INT, "not-int"); + + /** {@code r,x: long :: r = ~x;} */ + public static final Rop NOT_LONG = + new Rop(RegOps.NOT, Type.LONG, StdTypeList.LONG, "not-long"); + + /** {@code r,x,c: int :: r = x + c;} */ + public static final Rop ADD_CONST_INT = + new Rop(RegOps.ADD, Type.INT, StdTypeList.INT, "add-const-int"); + + /** {@code r,x,c: long :: r = x + c;} */ + public static final Rop ADD_CONST_LONG = + new Rop(RegOps.ADD, Type.LONG, StdTypeList.LONG, "add-const-long"); + + /** {@code r,x,c: float :: r = x + c;} */ + public static final Rop ADD_CONST_FLOAT = + new Rop(RegOps.ADD, Type.FLOAT, StdTypeList.FLOAT, "add-const-float"); + + /** {@code r,x,c: double :: r = x + c;} */ + public static final Rop ADD_CONST_DOUBLE = + new Rop(RegOps.ADD, Type.DOUBLE, StdTypeList.DOUBLE, + "add-const-double"); + + /** {@code r,x,c: int :: r = x - c;} */ + public static final Rop SUB_CONST_INT = + new Rop(RegOps.SUB, Type.INT, StdTypeList.INT, "sub-const-int"); + + /** {@code r,x,c: long :: r = x - c;} */ + public static final Rop SUB_CONST_LONG = + new Rop(RegOps.SUB, Type.LONG, StdTypeList.LONG, "sub-const-long"); + + /** {@code r,x,c: float :: r = x - c;} */ + public static final Rop SUB_CONST_FLOAT = + new Rop(RegOps.SUB, Type.FLOAT, StdTypeList.FLOAT, "sub-const-float"); + + /** {@code r,x,c: double :: r = x - c;} */ + public static final Rop SUB_CONST_DOUBLE = + new Rop(RegOps.SUB, Type.DOUBLE, StdTypeList.DOUBLE, + "sub-const-double"); + + /** {@code r,x,c: int :: r = x * c;} */ + public static final Rop MUL_CONST_INT = + new Rop(RegOps.MUL, Type.INT, StdTypeList.INT, "mul-const-int"); + + /** {@code r,x,c: long :: r = x * c;} */ + public static final Rop MUL_CONST_LONG = + new Rop(RegOps.MUL, Type.LONG, StdTypeList.LONG, "mul-const-long"); + + /** {@code r,x,c: float :: r = x * c;} */ + public static final Rop MUL_CONST_FLOAT = + new Rop(RegOps.MUL, Type.FLOAT, StdTypeList.FLOAT, "mul-const-float"); + + /** {@code r,x,c: double :: r = x * c;} */ + public static final Rop MUL_CONST_DOUBLE = + new Rop(RegOps.MUL, Type.DOUBLE, StdTypeList.DOUBLE, + "mul-const-double"); + + /** {@code r,x,c: int :: r = x / c;} */ + public static final Rop DIV_CONST_INT = + new Rop(RegOps.DIV, Type.INT, StdTypeList.INT, + Exceptions.LIST_Error_ArithmeticException, "div-const-int"); + + /** {@code r,x,c: long :: r = x / c;} */ + public static final Rop DIV_CONST_LONG = + new Rop(RegOps.DIV, Type.LONG, StdTypeList.LONG, + Exceptions.LIST_Error_ArithmeticException, "div-const-long"); + + /** {@code r,x,c: float :: r = x / c;} */ + public static final Rop DIV_CONST_FLOAT = + new Rop(RegOps.DIV, Type.FLOAT, StdTypeList.FLOAT, "div-const-float"); + + /** {@code r,x,c: double :: r = x / c;} */ + public static final Rop DIV_CONST_DOUBLE = + new Rop(RegOps.DIV, Type.DOUBLE, StdTypeList.DOUBLE, + "div-const-double"); + + /** {@code r,x,c: int :: r = x % c;} */ + public static final Rop REM_CONST_INT = + new Rop(RegOps.REM, Type.INT, StdTypeList.INT, + Exceptions.LIST_Error_ArithmeticException, "rem-const-int"); + + /** {@code r,x,c: long :: r = x % c;} */ + public static final Rop REM_CONST_LONG = + new Rop(RegOps.REM, Type.LONG, StdTypeList.LONG, + Exceptions.LIST_Error_ArithmeticException, "rem-const-long"); + + /** {@code r,x,c: float :: r = x % c;} */ + public static final Rop REM_CONST_FLOAT = + new Rop(RegOps.REM, Type.FLOAT, StdTypeList.FLOAT, "rem-const-float"); + + /** {@code r,x,c: double :: r = x % c;} */ + public static final Rop REM_CONST_DOUBLE = + new Rop(RegOps.REM, Type.DOUBLE, StdTypeList.DOUBLE, + "rem-const-double"); + + /** {@code r,x,c: int :: r = x & c;} */ + public static final Rop AND_CONST_INT = + new Rop(RegOps.AND, Type.INT, StdTypeList.INT, "and-const-int"); + + /** {@code r,x,c: long :: r = x & c;} */ + public static final Rop AND_CONST_LONG = + new Rop(RegOps.AND, Type.LONG, StdTypeList.LONG, "and-const-long"); + + /** {@code r,x,c: int :: r = x | c;} */ + public static final Rop OR_CONST_INT = + new Rop(RegOps.OR, Type.INT, StdTypeList.INT, "or-const-int"); + + /** {@code r,x,c: long :: r = x | c;} */ + public static final Rop OR_CONST_LONG = + new Rop(RegOps.OR, Type.LONG, StdTypeList.LONG, "or-const-long"); + + /** {@code r,x,c: int :: r = x ^ c;} */ + public static final Rop XOR_CONST_INT = + new Rop(RegOps.XOR, Type.INT, StdTypeList.INT, "xor-const-int"); + + /** {@code r,x,c: long :: r = x ^ c;} */ + public static final Rop XOR_CONST_LONG = + new Rop(RegOps.XOR, Type.LONG, StdTypeList.LONG, "xor-const-long"); + + /** {@code r,x,c: int :: r = x << c;} */ + public static final Rop SHL_CONST_INT = + new Rop(RegOps.SHL, Type.INT, StdTypeList.INT, "shl-const-int"); + + /** {@code r,x: long; c: int :: r = x << c;} */ + public static final Rop SHL_CONST_LONG = + new Rop(RegOps.SHL, Type.LONG, StdTypeList.INT, "shl-const-long"); + + /** {@code r,x,c: int :: r = x >> c;} */ + public static final Rop SHR_CONST_INT = + new Rop(RegOps.SHR, Type.INT, StdTypeList.INT, "shr-const-int"); + + /** {@code r,x: long; c: int :: r = x >> c;} */ + public static final Rop SHR_CONST_LONG = + new Rop(RegOps.SHR, Type.LONG, StdTypeList.INT, "shr-const-long"); + + /** {@code r,x,c: int :: r = x >>> c;} */ + public static final Rop USHR_CONST_INT = + new Rop(RegOps.USHR, Type.INT, StdTypeList.INT, "ushr-const-int"); + + /** {@code r,x: long; c: int :: r = x >>> c;} */ + public static final Rop USHR_CONST_LONG = + new Rop(RegOps.USHR, Type.LONG, StdTypeList.INT, "ushr-const-long"); + + /** {@code r: int; x,y: long :: r = cmp(x, y);} */ + public static final Rop CMPL_LONG = + new Rop(RegOps.CMPL, Type.INT, StdTypeList.LONG_LONG, "cmpl-long"); + + /** {@code r: int; x,y: float :: r = cmpl(x, y);} */ + public static final Rop CMPL_FLOAT = + new Rop(RegOps.CMPL, Type.INT, StdTypeList.FLOAT_FLOAT, "cmpl-float"); + + /** {@code r: int; x,y: double :: r = cmpl(x, y);} */ + public static final Rop CMPL_DOUBLE = + new Rop(RegOps.CMPL, Type.INT, StdTypeList.DOUBLE_DOUBLE, + "cmpl-double"); + + /** {@code r: int; x,y: float :: r = cmpg(x, y);} */ + public static final Rop CMPG_FLOAT = + new Rop(RegOps.CMPG, Type.INT, StdTypeList.FLOAT_FLOAT, "cmpg-float"); + + /** {@code r: int; x,y: double :: r = cmpg(x, y);} */ + public static final Rop CMPG_DOUBLE = + new Rop(RegOps.CMPG, Type.INT, StdTypeList.DOUBLE_DOUBLE, + "cmpg-double"); + + /** {@code r: int; x: long :: r = (int) x} */ + public static final Rop CONV_L2I = + new Rop(RegOps.CONV, Type.INT, StdTypeList.LONG, "conv-l2i"); + + /** {@code r: int; x: float :: r = (int) x} */ + public static final Rop CONV_F2I = + new Rop(RegOps.CONV, Type.INT, StdTypeList.FLOAT, "conv-f2i"); + + /** {@code r: int; x: double :: r = (int) x} */ + public static final Rop CONV_D2I = + new Rop(RegOps.CONV, Type.INT, StdTypeList.DOUBLE, "conv-d2i"); + + /** {@code r: long; x: int :: r = (long) x} */ + public static final Rop CONV_I2L = + new Rop(RegOps.CONV, Type.LONG, StdTypeList.INT, "conv-i2l"); + + /** {@code r: long; x: float :: r = (long) x} */ + public static final Rop CONV_F2L = + new Rop(RegOps.CONV, Type.LONG, StdTypeList.FLOAT, "conv-f2l"); + + /** {@code r: long; x: double :: r = (long) x} */ + public static final Rop CONV_D2L = + new Rop(RegOps.CONV, Type.LONG, StdTypeList.DOUBLE, "conv-d2l"); + + /** {@code r: float; x: int :: r = (float) x} */ + public static final Rop CONV_I2F = + new Rop(RegOps.CONV, Type.FLOAT, StdTypeList.INT, "conv-i2f"); + + /** {@code r: float; x: long :: r = (float) x} */ + public static final Rop CONV_L2F = + new Rop(RegOps.CONV, Type.FLOAT, StdTypeList.LONG, "conv-l2f"); + + /** {@code r: float; x: double :: r = (float) x} */ + public static final Rop CONV_D2F = + new Rop(RegOps.CONV, Type.FLOAT, StdTypeList.DOUBLE, "conv-d2f"); + + /** {@code r: double; x: int :: r = (double) x} */ + public static final Rop CONV_I2D = + new Rop(RegOps.CONV, Type.DOUBLE, StdTypeList.INT, "conv-i2d"); + + /** {@code r: double; x: long :: r = (double) x} */ + public static final Rop CONV_L2D = + new Rop(RegOps.CONV, Type.DOUBLE, StdTypeList.LONG, "conv-l2d"); + + /** {@code r: double; x: float :: r = (double) x} */ + public static final Rop CONV_F2D = + new Rop(RegOps.CONV, Type.DOUBLE, StdTypeList.FLOAT, "conv-f2d"); + + /** + * {@code r,x: int :: r = (x << 24) >> 24} (Java-style + * convert int to byte) + */ + public static final Rop TO_BYTE = + new Rop(RegOps.TO_BYTE, Type.INT, StdTypeList.INT, "to-byte"); + + /** + * {@code r,x: int :: r = x & 0xffff} (Java-style + * convert int to char) + */ + public static final Rop TO_CHAR = + new Rop(RegOps.TO_CHAR, Type.INT, StdTypeList.INT, "to-char"); + + /** + * {@code r,x: int :: r = (x << 16) >> 16} (Java-style + * convert int to short) + */ + public static final Rop TO_SHORT = + new Rop(RegOps.TO_SHORT, Type.INT, StdTypeList.INT, "to-short"); + + /** {@code return void} */ + public static final Rop RETURN_VOID = + new Rop(RegOps.RETURN, Type.VOID, StdTypeList.EMPTY, Rop.BRANCH_RETURN, + "return-void"); + + /** {@code x: int; return x} */ + public static final Rop RETURN_INT = + new Rop(RegOps.RETURN, Type.VOID, StdTypeList.INT, Rop.BRANCH_RETURN, + "return-int"); + + /** {@code x: long; return x} */ + public static final Rop RETURN_LONG = + new Rop(RegOps.RETURN, Type.VOID, StdTypeList.LONG, Rop.BRANCH_RETURN, + "return-long"); + + /** {@code x: float; return x} */ + public static final Rop RETURN_FLOAT = + new Rop(RegOps.RETURN, Type.VOID, StdTypeList.FLOAT, Rop.BRANCH_RETURN, + "return-float"); + + /** {@code x: double; return x} */ + public static final Rop RETURN_DOUBLE = + new Rop(RegOps.RETURN, Type.VOID, StdTypeList.DOUBLE, + Rop.BRANCH_RETURN, "return-double"); + + /** {@code x: Object; return x} */ + public static final Rop RETURN_OBJECT = + new Rop(RegOps.RETURN, Type.VOID, StdTypeList.OBJECT, + Rop.BRANCH_RETURN, "return-object"); + + /** {@code T: any type; r: int; x: T[]; :: r = x.length} */ + public static final Rop ARRAY_LENGTH = + new Rop(RegOps.ARRAY_LENGTH, Type.INT, StdTypeList.OBJECT, + Exceptions.LIST_Error_NullPointerException, "array-length"); + + /** {@code x: Throwable :: throw(x)} */ + public static final Rop THROW = + new Rop(RegOps.THROW, Type.VOID, StdTypeList.THROWABLE, + StdTypeList.THROWABLE, "throw"); + + /** {@code x: Object :: monitorenter(x)} */ + public static final Rop MONITOR_ENTER = + new Rop(RegOps.MONITOR_ENTER, Type.VOID, StdTypeList.OBJECT, + Exceptions.LIST_Error_NullPointerException, "monitor-enter"); + + /** {@code x: Object :: monitorexit(x)} */ + public static final Rop MONITOR_EXIT = + new Rop(RegOps.MONITOR_EXIT, Type.VOID, StdTypeList.OBJECT, + Exceptions.LIST_Error_Null_IllegalMonitorStateException, + "monitor-exit"); + + /** {@code r,y: int; x: int[] :: r = x[y]} */ + public static final Rop AGET_INT = + new Rop(RegOps.AGET, Type.INT, StdTypeList.INTARR_INT, + Exceptions.LIST_Error_Null_ArrayIndexOutOfBounds, + "aget-int"); + + /** {@code r: long; x: long[]; y: int :: r = x[y]} */ + public static final Rop AGET_LONG = + new Rop(RegOps.AGET, Type.LONG, StdTypeList.LONGARR_INT, + Exceptions.LIST_Error_Null_ArrayIndexOutOfBounds, + "aget-long"); + + /** {@code r: float; x: float[]; y: int :: r = x[y]} */ + public static final Rop AGET_FLOAT = + new Rop(RegOps.AGET, Type.FLOAT, StdTypeList.FLOATARR_INT, + Exceptions.LIST_Error_Null_ArrayIndexOutOfBounds, + "aget-float"); + + /** {@code r: double; x: double[]; y: int :: r = x[y]} */ + public static final Rop AGET_DOUBLE = + new Rop(RegOps.AGET, Type.DOUBLE, StdTypeList.DOUBLEARR_INT, + Exceptions.LIST_Error_Null_ArrayIndexOutOfBounds, + "aget-double"); + + /** {@code r: Object; x: Object[]; y: int :: r = x[y]} */ + public static final Rop AGET_OBJECT = + new Rop(RegOps.AGET, Type.OBJECT, StdTypeList.OBJECTARR_INT, + Exceptions.LIST_Error_Null_ArrayIndexOutOfBounds, + "aget-object"); + + /** {@code r: boolean; x: boolean[]; y: int :: r = x[y]} */ + public static final Rop AGET_BOOLEAN = + new Rop(RegOps.AGET, Type.INT, StdTypeList.BOOLEANARR_INT, + Exceptions.LIST_Error_Null_ArrayIndexOutOfBounds, + "aget-boolean"); + + /** {@code r: byte; x: byte[]; y: int :: r = x[y]} */ + public static final Rop AGET_BYTE = + new Rop(RegOps.AGET, Type.INT, StdTypeList.BYTEARR_INT, + Exceptions.LIST_Error_Null_ArrayIndexOutOfBounds, "aget-byte"); + + /** {@code r: char; x: char[]; y: int :: r = x[y]} */ + public static final Rop AGET_CHAR = + new Rop(RegOps.AGET, Type.INT, StdTypeList.CHARARR_INT, + Exceptions.LIST_Error_Null_ArrayIndexOutOfBounds, "aget-char"); + + /** {@code r: short; x: short[]; y: int :: r = x[y]} */ + public static final Rop AGET_SHORT = + new Rop(RegOps.AGET, Type.INT, StdTypeList.SHORTARR_INT, + Exceptions.LIST_Error_Null_ArrayIndexOutOfBounds, + "aget-short"); + + /** {@code x,z: int; y: int[] :: y[z] = x} */ + public static final Rop APUT_INT = + new Rop(RegOps.APUT, Type.VOID, StdTypeList.INT_INTARR_INT, + Exceptions.LIST_Error_Null_ArrayIndexOutOfBounds, "aput-int"); + + /** {@code x: long; y: long[]; z: int :: y[z] = x} */ + public static final Rop APUT_LONG = + new Rop(RegOps.APUT, Type.VOID, StdTypeList.LONG_LONGARR_INT, + Exceptions.LIST_Error_Null_ArrayIndexOutOfBounds, "aput-long"); + + /** {@code x: float; y: float[]; z: int :: y[z] = x} */ + public static final Rop APUT_FLOAT = + new Rop(RegOps.APUT, Type.VOID, StdTypeList.FLOAT_FLOATARR_INT, + Exceptions.LIST_Error_Null_ArrayIndexOutOfBounds, + "aput-float"); + + /** {@code x: double; y: double[]; z: int :: y[z] = x} */ + public static final Rop APUT_DOUBLE = + new Rop(RegOps.APUT, Type.VOID, StdTypeList.DOUBLE_DOUBLEARR_INT, + Exceptions.LIST_Error_Null_ArrayIndexOutOfBounds, + "aput-double"); + + /** {@code x: Object; y: Object[]; z: int :: y[z] = x} */ + public static final Rop APUT_OBJECT = + new Rop(RegOps.APUT, Type.VOID, StdTypeList.OBJECT_OBJECTARR_INT, + Exceptions.LIST_Error_Null_ArrayIndex_ArrayStore, + "aput-object"); + + /** {@code x: boolean; y: boolean[]; z: int :: y[z] = x} */ + public static final Rop APUT_BOOLEAN = + new Rop(RegOps.APUT, Type.VOID, StdTypeList.INT_BOOLEANARR_INT, + Exceptions.LIST_Error_Null_ArrayIndex_ArrayStore, + "aput-boolean"); + + /** {@code x: byte; y: byte[]; z: int :: y[z] = x} */ + public static final Rop APUT_BYTE = + new Rop(RegOps.APUT, Type.VOID, StdTypeList.INT_BYTEARR_INT, + Exceptions.LIST_Error_Null_ArrayIndex_ArrayStore, "aput-byte"); + + /** {@code x: char; y: char[]; z: int :: y[z] = x} */ + public static final Rop APUT_CHAR = + new Rop(RegOps.APUT, Type.VOID, StdTypeList.INT_CHARARR_INT, + Exceptions.LIST_Error_Null_ArrayIndex_ArrayStore, "aput-char"); + + /** {@code x: short; y: short[]; z: int :: y[z] = x} */ + public static final Rop APUT_SHORT = + new Rop(RegOps.APUT, Type.VOID, StdTypeList.INT_SHORTARR_INT, + Exceptions.LIST_Error_Null_ArrayIndex_ArrayStore, + "aput-short"); + + /** + * {@code T: any non-array object type :: r = + * alloc(T)} (allocate heap space for an object) + */ + public static final Rop NEW_INSTANCE = + new Rop(RegOps.NEW_INSTANCE, Type.OBJECT, StdTypeList.EMPTY, + Exceptions.LIST_Error, "new-instance"); + + /** {@code r: int[]; x: int :: r = new int[x]} */ + public static final Rop NEW_ARRAY_INT = + new Rop(RegOps.NEW_ARRAY, Type.INT_ARRAY, StdTypeList.INT, + Exceptions.LIST_Error_NegativeArraySizeException, + "new-array-int"); + + /** {@code r: long[]; x: int :: r = new long[x]} */ + public static final Rop NEW_ARRAY_LONG = + new Rop(RegOps.NEW_ARRAY, Type.LONG_ARRAY, StdTypeList.INT, + Exceptions.LIST_Error_NegativeArraySizeException, + "new-array-long"); + + /** {@code r: float[]; x: int :: r = new float[x]} */ + public static final Rop NEW_ARRAY_FLOAT = + new Rop(RegOps.NEW_ARRAY, Type.FLOAT_ARRAY, StdTypeList.INT, + Exceptions.LIST_Error_NegativeArraySizeException, + "new-array-float"); + + /** {@code r: double[]; x: int :: r = new double[x]} */ + public static final Rop NEW_ARRAY_DOUBLE = + new Rop(RegOps.NEW_ARRAY, Type.DOUBLE_ARRAY, StdTypeList.INT, + Exceptions.LIST_Error_NegativeArraySizeException, + "new-array-double"); + + /** {@code r: boolean[]; x: int :: r = new boolean[x]} */ + public static final Rop NEW_ARRAY_BOOLEAN = + new Rop(RegOps.NEW_ARRAY, Type.BOOLEAN_ARRAY, StdTypeList.INT, + Exceptions.LIST_Error_NegativeArraySizeException, + "new-array-boolean"); + + /** {@code r: byte[]; x: int :: r = new byte[x]} */ + public static final Rop NEW_ARRAY_BYTE = + new Rop(RegOps.NEW_ARRAY, Type.BYTE_ARRAY, StdTypeList.INT, + Exceptions.LIST_Error_NegativeArraySizeException, + "new-array-byte"); + + /** {@code r: char[]; x: int :: r = new char[x]} */ + public static final Rop NEW_ARRAY_CHAR = + new Rop(RegOps.NEW_ARRAY, Type.CHAR_ARRAY, StdTypeList.INT, + Exceptions.LIST_Error_NegativeArraySizeException, + "new-array-char"); + + /** {@code r: short[]; x: int :: r = new short[x]} */ + public static final Rop NEW_ARRAY_SHORT = + new Rop(RegOps.NEW_ARRAY, Type.SHORT_ARRAY, StdTypeList.INT, + Exceptions.LIST_Error_NegativeArraySizeException, + "new-array-short"); + + /** + * {@code T: any non-array object type; x: Object :: (T) x} (can + * throw {@code ClassCastException}) + */ + public static final Rop CHECK_CAST = + new Rop(RegOps.CHECK_CAST, Type.VOID, StdTypeList.OBJECT, + Exceptions.LIST_Error_ClassCastException, "check-cast"); + + /** + * {@code T: any non-array object type; x: Object :: x instanceof + * T}. Note: This is listed as throwing {@code Error} + * explicitly because the op can throw, but there are no + * other predefined exceptions for it. + */ + public static final Rop INSTANCE_OF = + new Rop(RegOps.INSTANCE_OF, Type.INT, StdTypeList.OBJECT, + Exceptions.LIST_Error, "instance-of"); + + /** + * {@code r: int; x: Object; f: instance field spec of + * type int :: r = x.f} + */ + public static final Rop GET_FIELD_INT = + new Rop(RegOps.GET_FIELD, Type.INT, StdTypeList.OBJECT, + Exceptions.LIST_Error_NullPointerException, "get-field-int"); + + /** + * {@code r: long; x: Object; f: instance field spec of + * type long :: r = x.f} + */ + public static final Rop GET_FIELD_LONG = + new Rop(RegOps.GET_FIELD, Type.LONG, StdTypeList.OBJECT, + Exceptions.LIST_Error_NullPointerException, "get-field-long"); + + /** + * {@code r: float; x: Object; f: instance field spec of + * type float :: r = x.f} + */ + public static final Rop GET_FIELD_FLOAT = + new Rop(RegOps.GET_FIELD, Type.FLOAT, StdTypeList.OBJECT, + Exceptions.LIST_Error_NullPointerException, + "get-field-float"); + + /** + * {@code r: double; x: Object; f: instance field spec of + * type double :: r = x.f} + */ + public static final Rop GET_FIELD_DOUBLE = + new Rop(RegOps.GET_FIELD, Type.DOUBLE, StdTypeList.OBJECT, + Exceptions.LIST_Error_NullPointerException, + "get-field-double"); + + /** + * {@code r: Object; x: Object; f: instance field spec of + * type Object :: r = x.f} + */ + public static final Rop GET_FIELD_OBJECT = + new Rop(RegOps.GET_FIELD, Type.OBJECT, StdTypeList.OBJECT, + Exceptions.LIST_Error_NullPointerException, + "get-field-object"); + + /** + * {@code r: boolean; x: Object; f: instance field spec of + * type boolean :: r = x.f} + */ + public static final Rop GET_FIELD_BOOLEAN = + new Rop(RegOps.GET_FIELD, Type.INT, StdTypeList.OBJECT, + Exceptions.LIST_Error_NullPointerException, + "get-field-boolean"); + + /** + * {@code r: byte; x: Object; f: instance field spec of + * type byte :: r = x.f} + */ + public static final Rop GET_FIELD_BYTE = + new Rop(RegOps.GET_FIELD, Type.INT, StdTypeList.OBJECT, + Exceptions.LIST_Error_NullPointerException, + "get-field-byte"); + + /** + * {@code r: char; x: Object; f: instance field spec of + * type char :: r = x.f} + */ + public static final Rop GET_FIELD_CHAR = + new Rop(RegOps.GET_FIELD, Type.INT, StdTypeList.OBJECT, + Exceptions.LIST_Error_NullPointerException, + "get-field-char"); + + /** + * {@code r: short; x: Object; f: instance field spec of + * type short :: r = x.f} + */ + public static final Rop GET_FIELD_SHORT = + new Rop(RegOps.GET_FIELD, Type.INT, StdTypeList.OBJECT, + Exceptions.LIST_Error_NullPointerException, + "get-field-short"); + + /** {@code r: int; f: static field spec of type int :: r = f} */ + public static final Rop GET_STATIC_INT = + new Rop(RegOps.GET_STATIC, Type.INT, StdTypeList.EMPTY, + Exceptions.LIST_Error, "get-static-int"); + + /** {@code r: long; f: static field spec of type long :: r = f} */ + public static final Rop GET_STATIC_LONG = + new Rop(RegOps.GET_STATIC, Type.LONG, StdTypeList.EMPTY, + Exceptions.LIST_Error, "get-static-long"); + + /** {@code r: float; f: static field spec of type float :: r = f} */ + public static final Rop GET_STATIC_FLOAT = + new Rop(RegOps.GET_STATIC, Type.FLOAT, StdTypeList.EMPTY, + Exceptions.LIST_Error, "get-static-float"); + + /** {@code r: double; f: static field spec of type double :: r = f} */ + public static final Rop GET_STATIC_DOUBLE = + new Rop(RegOps.GET_STATIC, Type.DOUBLE, StdTypeList.EMPTY, + Exceptions.LIST_Error, "get-static-double"); + + /** {@code r: Object; f: static field spec of type Object :: r = f} */ + public static final Rop GET_STATIC_OBJECT = + new Rop(RegOps.GET_STATIC, Type.OBJECT, StdTypeList.EMPTY, + Exceptions.LIST_Error, "get-static-object"); + + /** {@code r: boolean; f: static field spec of type boolean :: r = f} */ + public static final Rop GET_STATIC_BOOLEAN = + new Rop(RegOps.GET_STATIC, Type.INT, StdTypeList.EMPTY, + Exceptions.LIST_Error, "get-field-boolean"); + + /** {@code r: byte; f: static field spec of type byte :: r = f} */ + public static final Rop GET_STATIC_BYTE = + new Rop(RegOps.GET_STATIC, Type.INT, StdTypeList.EMPTY, + Exceptions.LIST_Error, "get-field-byte"); + + /** {@code r: char; f: static field spec of type char :: r = f} */ + public static final Rop GET_STATIC_CHAR = + new Rop(RegOps.GET_STATIC, Type.INT, StdTypeList.EMPTY, + Exceptions.LIST_Error, "get-field-char"); + + /** {@code r: short; f: static field spec of type short :: r = f} */ + public static final Rop GET_STATIC_SHORT = + new Rop(RegOps.GET_STATIC, Type.INT, StdTypeList.EMPTY, + Exceptions.LIST_Error, "get-field-short"); + + /** + * {@code x: int; y: Object; f: instance field spec of type + * int :: y.f = x} + */ + public static final Rop PUT_FIELD_INT = + new Rop(RegOps.PUT_FIELD, Type.VOID, StdTypeList.INT_OBJECT, + Exceptions.LIST_Error_NullPointerException, "put-field-int"); + + /** + * {@code x: long; y: Object; f: instance field spec of type + * long :: y.f = x} + */ + public static final Rop PUT_FIELD_LONG = + new Rop(RegOps.PUT_FIELD, Type.VOID, StdTypeList.LONG_OBJECT, + Exceptions.LIST_Error_NullPointerException, "put-field-long"); + + /** + * {@code x: float; y: Object; f: instance field spec of type + * float :: y.f = x} + */ + public static final Rop PUT_FIELD_FLOAT = + new Rop(RegOps.PUT_FIELD, Type.VOID, StdTypeList.FLOAT_OBJECT, + Exceptions.LIST_Error_NullPointerException, + "put-field-float"); + + /** + * {@code x: double; y: Object; f: instance field spec of type + * double :: y.f = x} + */ + public static final Rop PUT_FIELD_DOUBLE = + new Rop(RegOps.PUT_FIELD, Type.VOID, StdTypeList.DOUBLE_OBJECT, + Exceptions.LIST_Error_NullPointerException, + "put-field-double"); + + /** + * {@code x: Object; y: Object; f: instance field spec of type + * Object :: y.f = x} + */ + public static final Rop PUT_FIELD_OBJECT = + new Rop(RegOps.PUT_FIELD, Type.VOID, StdTypeList.OBJECT_OBJECT, + Exceptions.LIST_Error_NullPointerException, + "put-field-object"); + + /** + * {@code x: int; y: Object; f: instance field spec of type + * boolean :: y.f = x} + */ + public static final Rop PUT_FIELD_BOOLEAN = + new Rop(RegOps.PUT_FIELD, Type.VOID, StdTypeList.INT_OBJECT, + Exceptions.LIST_Error_NullPointerException, + "put-field-boolean"); + + /** + * {@code x: int; y: Object; f: instance field spec of type + * byte :: y.f = x} + */ + public static final Rop PUT_FIELD_BYTE = + new Rop(RegOps.PUT_FIELD, Type.VOID, StdTypeList.INT_OBJECT, + Exceptions.LIST_Error_NullPointerException, + "put-field-byte"); + + /** + * {@code x: int; y: Object; f: instance field spec of type + * char :: y.f = x} + */ + public static final Rop PUT_FIELD_CHAR = + new Rop(RegOps.PUT_FIELD, Type.VOID, StdTypeList.INT_OBJECT, + Exceptions.LIST_Error_NullPointerException, + "put-field-char"); + + /** + * {@code x: int; y: Object; f: instance field spec of type + * short :: y.f = x} + */ + public static final Rop PUT_FIELD_SHORT = + new Rop(RegOps.PUT_FIELD, Type.VOID, StdTypeList.INT_OBJECT, + Exceptions.LIST_Error_NullPointerException, + "put-field-short"); + + /** {@code f: static field spec of type int; x: int :: f = x} */ + public static final Rop PUT_STATIC_INT = + new Rop(RegOps.PUT_STATIC, Type.VOID, StdTypeList.INT, + Exceptions.LIST_Error, "put-static-int"); + + /** {@code f: static field spec of type long; x: long :: f = x} */ + public static final Rop PUT_STATIC_LONG = + new Rop(RegOps.PUT_STATIC, Type.VOID, StdTypeList.LONG, + Exceptions.LIST_Error, "put-static-long"); + + /** {@code f: static field spec of type float; x: float :: f = x} */ + public static final Rop PUT_STATIC_FLOAT = + new Rop(RegOps.PUT_STATIC, Type.VOID, StdTypeList.FLOAT, + Exceptions.LIST_Error, "put-static-float"); + + /** {@code f: static field spec of type double; x: double :: f = x} */ + public static final Rop PUT_STATIC_DOUBLE = + new Rop(RegOps.PUT_STATIC, Type.VOID, StdTypeList.DOUBLE, + Exceptions.LIST_Error, "put-static-double"); + + /** {@code f: static field spec of type Object; x: Object :: f = x} */ + public static final Rop PUT_STATIC_OBJECT = + new Rop(RegOps.PUT_STATIC, Type.VOID, StdTypeList.OBJECT, + Exceptions.LIST_Error, "put-static-object"); + + /** + * {@code f: static field spec of type boolean; x: boolean :: f = + * x} + */ + public static final Rop PUT_STATIC_BOOLEAN = + new Rop(RegOps.PUT_STATIC, Type.VOID, StdTypeList.INT, + Exceptions.LIST_Error, "put-static-boolean"); + + /** {@code f: static field spec of type byte; x: byte :: f = x} */ + public static final Rop PUT_STATIC_BYTE = + new Rop(RegOps.PUT_STATIC, Type.VOID, StdTypeList.INT, + Exceptions.LIST_Error, "put-static-byte"); + + /** {@code f: static field spec of type char; x: char :: f = x} */ + public static final Rop PUT_STATIC_CHAR = + new Rop(RegOps.PUT_STATIC, Type.VOID, StdTypeList.INT, + Exceptions.LIST_Error, "put-static-char"); + + /** {@code f: static field spec of type short; x: short :: f = x} */ + public static final Rop PUT_STATIC_SHORT = + new Rop(RegOps.PUT_STATIC, Type.VOID, StdTypeList.INT, + Exceptions.LIST_Error, "put-static-short"); + + /** {@code x: Int :: local variable begins in x} */ + public static final Rop MARK_LOCAL_INT = + new Rop (RegOps.MARK_LOCAL, Type.VOID, + StdTypeList.INT, "mark-local-int"); + + /** {@code x: Long :: local variable begins in x} */ + public static final Rop MARK_LOCAL_LONG = + new Rop (RegOps.MARK_LOCAL, Type.VOID, + StdTypeList.LONG, "mark-local-long"); + + /** {@code x: Float :: local variable begins in x} */ + public static final Rop MARK_LOCAL_FLOAT = + new Rop (RegOps.MARK_LOCAL, Type.VOID, + StdTypeList.FLOAT, "mark-local-float"); + + /** {@code x: Double :: local variable begins in x} */ + public static final Rop MARK_LOCAL_DOUBLE = + new Rop (RegOps.MARK_LOCAL, Type.VOID, + StdTypeList.DOUBLE, "mark-local-double"); + + /** {@code x: Object :: local variable begins in x} */ + public static final Rop MARK_LOCAL_OBJECT = + new Rop (RegOps.MARK_LOCAL, Type.VOID, + StdTypeList.OBJECT, "mark-local-object"); + + /** {@code T: Any primitive type; v0..vx: T :: {v0, ..., vx}} */ + public static final Rop FILL_ARRAY_DATA = + new Rop(RegOps.FILL_ARRAY_DATA, Type.VOID, StdTypeList.EMPTY, + "fill-array-data"); + + /** + * Returns the appropriate rop for the given opcode, destination, + * and sources. The result is typically, but not necessarily, a + * shared instance. + * + *

Note: This method does not do complete error checking on + * its arguments, and so it may return an instance which seemed "right + * enough" even though in actuality the passed arguments don't quite + * match what is returned. TODO: Revisit this issue.

+ * + * @param opcode the opcode + * @param dest {@code non-null;} destination (result) type, or + * {@link Type#VOID} if none + * @param sources {@code non-null;} list of source types + * @param cst {@code null-ok;} associated constant, if any + * @return {@code non-null;} an appropriate instance + */ + public static Rop ropFor(int opcode, TypeBearer dest, TypeList sources, + Constant cst) { + switch (opcode) { + case RegOps.NOP: return NOP; + case RegOps.MOVE: return opMove(dest); + case RegOps.MOVE_PARAM: return opMoveParam(dest); + case RegOps.MOVE_EXCEPTION: return opMoveException(dest); + case RegOps.CONST: return opConst(dest); + case RegOps.GOTO: return GOTO; + case RegOps.IF_EQ: return opIfEq(sources); + case RegOps.IF_NE: return opIfNe(sources); + case RegOps.IF_LT: return opIfLt(sources); + case RegOps.IF_GE: return opIfGe(sources); + case RegOps.IF_LE: return opIfLe(sources); + case RegOps.IF_GT: return opIfGt(sources); + case RegOps.SWITCH: return SWITCH; + case RegOps.ADD: return opAdd(sources); + case RegOps.SUB: return opSub(sources); + case RegOps.MUL: return opMul(sources); + case RegOps.DIV: return opDiv(sources); + case RegOps.REM: return opRem(sources); + case RegOps.NEG: return opNeg(dest); + case RegOps.AND: return opAnd(sources); + case RegOps.OR: return opOr(sources); + case RegOps.XOR: return opXor(sources); + case RegOps.SHL: return opShl(sources); + case RegOps.SHR: return opShr(sources); + case RegOps.USHR: return opUshr(sources); + case RegOps.NOT: return opNot(dest); + case RegOps.CMPL: return opCmpl(sources.getType(0)); + case RegOps.CMPG: return opCmpg(sources.getType(0)); + case RegOps.CONV: return opConv(dest, sources.getType(0)); + case RegOps.TO_BYTE: return TO_BYTE; + case RegOps.TO_CHAR: return TO_CHAR; + case RegOps.TO_SHORT: return TO_SHORT; + case RegOps.RETURN: { + if (sources.size() == 0) { + return RETURN_VOID; + } + return opReturn(sources.getType(0)); + } + case RegOps.ARRAY_LENGTH: return ARRAY_LENGTH; + case RegOps.THROW: return THROW; + case RegOps.MONITOR_ENTER: return MONITOR_ENTER; + case RegOps.MONITOR_EXIT: return MONITOR_EXIT; + case RegOps.AGET: { + Type source = sources.getType(0); + Type componentType; + if (source == Type.KNOWN_NULL) { + /* + * Treat a known-null as an array of the expected + * result type. + */ + componentType = dest.getType(); + } else { + componentType = source.getComponentType(); + } + return opAget(componentType); + } + case RegOps.APUT: { + Type source = sources.getType(1); + Type componentType; + if (source == Type.KNOWN_NULL) { + /* + * Treat a known-null as an array of the type being + * stored. + */ + componentType = sources.getType(0); + } else { + componentType = source.getComponentType(); + } + return opAput(componentType); + } + case RegOps.NEW_INSTANCE: return NEW_INSTANCE; + case RegOps.NEW_ARRAY: return opNewArray(dest.getType()); + case RegOps.CHECK_CAST: return CHECK_CAST; + case RegOps.INSTANCE_OF: return INSTANCE_OF; + case RegOps.GET_FIELD: return opGetField(dest); + case RegOps.GET_STATIC: return opGetStatic(dest); + case RegOps.PUT_FIELD: return opPutField(sources.getType(0)); + case RegOps.PUT_STATIC: return opPutStatic(sources.getType(0)); + case RegOps.INVOKE_STATIC: { + return opInvokeStatic(((CstMethodRef) cst).getPrototype()); + } + case RegOps.INVOKE_VIRTUAL: { + CstBaseMethodRef cstMeth = (CstMethodRef) cst; + Prototype meth = cstMeth.getPrototype(); + CstType definer = cstMeth.getDefiningClass(); + meth = meth.withFirstParameter(definer.getClassType()); + return opInvokeVirtual(meth); + } + case RegOps.INVOKE_SUPER: { + CstBaseMethodRef cstMeth = (CstMethodRef) cst; + Prototype meth = cstMeth.getPrototype(); + CstType definer = cstMeth.getDefiningClass(); + meth = meth.withFirstParameter(definer.getClassType()); + return opInvokeSuper(meth); + } + case RegOps.INVOKE_DIRECT: { + CstBaseMethodRef cstMeth = (CstMethodRef) cst; + Prototype meth = cstMeth.getPrototype(); + CstType definer = cstMeth.getDefiningClass(); + meth = meth.withFirstParameter(definer.getClassType()); + return opInvokeDirect(meth); + } + case RegOps.INVOKE_INTERFACE: { + CstBaseMethodRef cstMeth = (CstMethodRef) cst; + Prototype meth = cstMeth.getPrototype(); + CstType definer = cstMeth.getDefiningClass(); + meth = meth.withFirstParameter(definer.getClassType()); + return opInvokeInterface(meth); + } + } + + throw new RuntimeException("unknown opcode " + RegOps.opName(opcode)); + } + + /** + * Returns the appropriate {@code move} rop for the given type. The + * result is a shared instance. + * + * @param type {@code non-null;} type of value being moved + * @return {@code non-null;} an appropriate instance + */ + public static Rop opMove(TypeBearer type) { + switch (type.getBasicFrameType()) { + case Type.BT_INT: return MOVE_INT; + case Type.BT_LONG: return MOVE_LONG; + case Type.BT_FLOAT: return MOVE_FLOAT; + case Type.BT_DOUBLE: return MOVE_DOUBLE; + case Type.BT_OBJECT: return MOVE_OBJECT; + case Type.BT_ADDR: return MOVE_RETURN_ADDRESS; + } + + return throwBadType(type); + } + + /** + * Returns the appropriate {@code move-param} rop for the + * given type. The result is a shared instance. + * + * @param type {@code non-null;} type of value being moved + * @return {@code non-null;} an appropriate instance + */ + public static Rop opMoveParam(TypeBearer type) { + switch (type.getBasicFrameType()) { + case Type.BT_INT: return MOVE_PARAM_INT; + case Type.BT_LONG: return MOVE_PARAM_LONG; + case Type.BT_FLOAT: return MOVE_PARAM_FLOAT; + case Type.BT_DOUBLE: return MOVE_PARAM_DOUBLE; + case Type.BT_OBJECT: return MOVE_PARAM_OBJECT; + } + + return throwBadType(type); + } + + /** + * Returns the appropriate {@code move-exception} rop for the + * given type. The result may be a shared instance. + * + * @param type {@code non-null;} type of the exception + * @return {@code non-null;} an appropriate instance + */ + public static Rop opMoveException(TypeBearer type) { + return new Rop(RegOps.MOVE_EXCEPTION, type.getType(), + StdTypeList.EMPTY, (String) null); + } + + /** + * Returns the appropriate {@code move-result} rop for the + * given type. The result may be a shared instance. + * + * @param type {@code non-null;} type of the parameter + * @return {@code non-null;} an appropriate instance + */ + public static Rop opMoveResult(TypeBearer type) { + return new Rop(RegOps.MOVE_RESULT, type.getType(), + StdTypeList.EMPTY, (String) null); + } + + /** + * Returns the appropriate {@code move-result-pseudo} rop for the + * given type. The result may be a shared instance. + * + * @param type {@code non-null;} type of the parameter + * @return {@code non-null;} an appropriate instance + */ + public static Rop opMoveResultPseudo(TypeBearer type) { + return new Rop(RegOps.MOVE_RESULT_PSEUDO, type.getType(), + StdTypeList.EMPTY, (String) null); + } + + /** + * Returns the appropriate {@code const} rop for the given + * type. The result is a shared instance. + * + * @param type {@code non-null;} type of the constant + * @return {@code non-null;} an appropriate instance + */ + public static Rop opConst(TypeBearer type) { + if (type.getType() == Type.KNOWN_NULL) { + return CONST_OBJECT_NOTHROW; + } + + switch (type.getBasicFrameType()) { + case Type.BT_INT: return CONST_INT; + case Type.BT_LONG: return CONST_LONG; + case Type.BT_FLOAT: return CONST_FLOAT; + case Type.BT_DOUBLE: return CONST_DOUBLE; + case Type.BT_OBJECT: return CONST_OBJECT; + } + + return throwBadType(type); + } + + /** + * Returns the appropriate {@code if-eq} rop for the given + * sources. The result is a shared instance. + * + * @param types {@code non-null;} source types + * @return {@code non-null;} an appropriate instance + */ + public static Rop opIfEq(TypeList types) { + return pickIf(types, IF_EQZ_INT, IF_EQZ_OBJECT, + IF_EQ_INT, IF_EQ_OBJECT); + } + + /** + * Returns the appropriate {@code if-ne} rop for the given + * sources. The result is a shared instance. + * + * @param types {@code non-null;} source types + * @return {@code non-null;} an appropriate instance + */ + public static Rop opIfNe(TypeList types) { + return pickIf(types, IF_NEZ_INT, IF_NEZ_OBJECT, + IF_NE_INT, IF_NE_OBJECT); + } + + /** + * Returns the appropriate {@code if-lt} rop for the given + * sources. The result is a shared instance. + * + * @param types {@code non-null;} source types + * @return {@code non-null;} an appropriate instance + */ + public static Rop opIfLt(TypeList types) { + return pickIf(types, IF_LTZ_INT, null, IF_LT_INT, null); + } + + /** + * Returns the appropriate {@code if-ge} rop for the given + * sources. The result is a shared instance. + * + * @param types {@code non-null;} source types + * @return {@code non-null;} an appropriate instance + */ + public static Rop opIfGe(TypeList types) { + return pickIf(types, IF_GEZ_INT, null, IF_GE_INT, null); + } + + /** + * Returns the appropriate {@code if-gt} rop for the given + * sources. The result is a shared instance. + * + * @param types {@code non-null;} source types + * @return {@code non-null;} an appropriate instance + */ + public static Rop opIfGt(TypeList types) { + return pickIf(types, IF_GTZ_INT, null, IF_GT_INT, null); + } + + /** + * Returns the appropriate {@code if-le} rop for the given + * sources. The result is a shared instance. + * + * @param types {@code non-null;} source types + * @return {@code non-null;} an appropriate instance + */ + public static Rop opIfLe(TypeList types) { + return pickIf(types, IF_LEZ_INT, null, IF_LE_INT, null); + } + + /** + * Helper for all the {@code if*}-related methods, which + * checks types and picks one of the four variants, throwing if + * there's a problem. + * + * @param types {@code non-null;} the types + * @param intZ {@code non-null;} the int-to-0 comparison + * @param objZ {@code null-ok;} the object-to-null comparison + * @param intInt {@code non-null;} the int-to-int comparison + * @param objObj {@code non-null;} the object-to-object comparison + * @return {@code non-null;} the appropriate instance + */ + private static Rop pickIf(TypeList types, Rop intZ, Rop objZ, Rop intInt, + Rop objObj) { + switch(types.size()) { + case 1: { + switch (types.getType(0).getBasicFrameType()) { + case Type.BT_INT: { + return intZ; + } + case Type.BT_OBJECT: { + if (objZ != null) { + return objZ; + } + } + } + break; + } + case 2: { + int bt = types.getType(0).getBasicFrameType(); + if (bt == types.getType(1).getBasicFrameType()) { + switch (bt) { + case Type.BT_INT: { + return intInt; + } + case Type.BT_OBJECT: { + if (objObj != null) { + return objObj; + } + } + } + } + break; + } + } + + return throwBadTypes(types); + } + + /** + * Returns the appropriate {@code add} rop for the given + * types. The result is a shared instance. + * + * @param types {@code non-null;} types of the sources + * @return {@code non-null;} an appropriate instance + */ + public static Rop opAdd(TypeList types) { + return pickBinaryOp(types, ADD_CONST_INT, ADD_CONST_LONG, + ADD_CONST_FLOAT, ADD_CONST_DOUBLE, ADD_INT, + ADD_LONG, ADD_FLOAT, ADD_DOUBLE); + } + + /** + * Returns the appropriate {@code sub} rop for the given + * types. The result is a shared instance. + * + * @param types {@code non-null;} types of the sources + * @return {@code non-null;} an appropriate instance + */ + public static Rop opSub(TypeList types) { + return pickBinaryOp(types, SUB_CONST_INT, SUB_CONST_LONG, + SUB_CONST_FLOAT, SUB_CONST_DOUBLE, SUB_INT, + SUB_LONG, SUB_FLOAT, SUB_DOUBLE); + } + + /** + * Returns the appropriate {@code mul} rop for the given + * types. The result is a shared instance. + * + * @param types {@code non-null;} types of the sources + * @return {@code non-null;} an appropriate instance + */ + public static Rop opMul(TypeList types) { + return pickBinaryOp(types, MUL_CONST_INT, MUL_CONST_LONG, + MUL_CONST_FLOAT, MUL_CONST_DOUBLE, MUL_INT, + MUL_LONG, MUL_FLOAT, MUL_DOUBLE); + } + + /** + * Returns the appropriate {@code div} rop for the given + * types. The result is a shared instance. + * + * @param types {@code non-null;} types of the sources + * @return {@code non-null;} an appropriate instance + */ + public static Rop opDiv(TypeList types) { + return pickBinaryOp(types, DIV_CONST_INT, DIV_CONST_LONG, + DIV_CONST_FLOAT, DIV_CONST_DOUBLE, DIV_INT, + DIV_LONG, DIV_FLOAT, DIV_DOUBLE); + } + + /** + * Returns the appropriate {@code rem} rop for the given + * types. The result is a shared instance. + * + * @param types {@code non-null;} types of the sources + * @return {@code non-null;} an appropriate instance + */ + public static Rop opRem(TypeList types) { + return pickBinaryOp(types, REM_CONST_INT, REM_CONST_LONG, + REM_CONST_FLOAT, REM_CONST_DOUBLE, REM_INT, + REM_LONG, REM_FLOAT, REM_DOUBLE); + } + + /** + * Returns the appropriate {@code and} rop for the given + * types. The result is a shared instance. + * + * @param types {@code non-null;} types of the sources + * @return {@code non-null;} an appropriate instance + */ + public static Rop opAnd(TypeList types) { + return pickBinaryOp(types, AND_CONST_INT, AND_CONST_LONG, null, null, + AND_INT, AND_LONG, null, null); + } + + /** + * Returns the appropriate {@code or} rop for the given + * types. The result is a shared instance. + * + * @param types {@code non-null;} types of the sources + * @return {@code non-null;} an appropriate instance + */ + public static Rop opOr(TypeList types) { + return pickBinaryOp(types, OR_CONST_INT, OR_CONST_LONG, null, null, + OR_INT, OR_LONG, null, null); + } + + /** + * Returns the appropriate {@code xor} rop for the given + * types. The result is a shared instance. + * + * @param types {@code non-null;} types of the sources + * @return {@code non-null;} an appropriate instance + */ + public static Rop opXor(TypeList types) { + return pickBinaryOp(types, XOR_CONST_INT, XOR_CONST_LONG, null, null, + XOR_INT, XOR_LONG, null, null); + } + + /** + * Returns the appropriate {@code shl} rop for the given + * types. The result is a shared instance. + * + * @param types {@code non-null;} types of the sources + * @return {@code non-null;} an appropriate instance + */ + public static Rop opShl(TypeList types) { + return pickBinaryOp(types, SHL_CONST_INT, SHL_CONST_LONG, null, null, + SHL_INT, SHL_LONG, null, null); + } + + /** + * Returns the appropriate {@code shr} rop for the given + * types. The result is a shared instance. + * + * @param types {@code non-null;} types of the sources + * @return {@code non-null;} an appropriate instance + */ + public static Rop opShr(TypeList types) { + return pickBinaryOp(types, SHR_CONST_INT, SHR_CONST_LONG, null, null, + SHR_INT, SHR_LONG, null, null); + } + + /** + * Returns the appropriate {@code ushr} rop for the given + * types. The result is a shared instance. + * + * @param types {@code non-null;} types of the sources + * @return {@code non-null;} an appropriate instance + */ + public static Rop opUshr(TypeList types) { + return pickBinaryOp(types, USHR_CONST_INT, USHR_CONST_LONG, null, null, + USHR_INT, USHR_LONG, null, null); + } + + /** + * Returns the appropriate binary arithmetic rop for the given type + * and arguments. The result is a shared instance. + * + * @param types {@code non-null;} sources of the operation + * @param int1 {@code non-null;} the int-to-constant rop + * @param long1 {@code non-null;} the long-to-constant rop + * @param float1 {@code null-ok;} the float-to-constant rop, if any + * @param double1 {@code null-ok;} the double-to-constant rop, if any + * @param int2 {@code non-null;} the int-to-int rop + * @param long2 {@code non-null;} the long-to-long or long-to-int rop + * @param float2 {@code null-ok;} the float-to-float rop, if any + * @param double2 {@code null-ok;} the double-to-double rop, if any + * @return {@code non-null;} an appropriate instance + */ + private static Rop pickBinaryOp(TypeList types, Rop int1, Rop long1, + Rop float1, Rop double1, Rop int2, + Rop long2, Rop float2, Rop double2) { + int bt1 = types.getType(0).getBasicFrameType(); + Rop result = null; + + switch (types.size()) { + case 1: { + switch(bt1) { + case Type.BT_INT: return int1; + case Type.BT_LONG: return long1; + case Type.BT_FLOAT: result = float1; break; + case Type.BT_DOUBLE: result = double1; break; + } + break; + } + case 2: { + switch(bt1) { + case Type.BT_INT: return int2; + case Type.BT_LONG: return long2; + case Type.BT_FLOAT: result = float2; break; + case Type.BT_DOUBLE: result = double2; break; + } + break; + } + } + + if (result == null) { + return throwBadTypes(types); + } + + return result; + } + + /** + * Returns the appropriate {@code neg} rop for the given type. The + * result is a shared instance. + * + * @param type {@code non-null;} type of value being operated on + * @return {@code non-null;} an appropriate instance + */ + public static Rop opNeg(TypeBearer type) { + switch (type.getBasicFrameType()) { + case Type.BT_INT: return NEG_INT; + case Type.BT_LONG: return NEG_LONG; + case Type.BT_FLOAT: return NEG_FLOAT; + case Type.BT_DOUBLE: return NEG_DOUBLE; + } + + return throwBadType(type); + } + + /** + * Returns the appropriate {@code not} rop for the given type. The + * result is a shared instance. + * + * @param type {@code non-null;} type of value being operated on + * @return {@code non-null;} an appropriate instance + */ + public static Rop opNot(TypeBearer type) { + switch (type.getBasicFrameType()) { + case Type.BT_INT: return NOT_INT; + case Type.BT_LONG: return NOT_LONG; + } + + return throwBadType(type); + } + + /** + * Returns the appropriate {@code cmpl} rop for the given type. The + * result is a shared instance. + * + * @param type {@code non-null;} type of value being compared + * @return {@code non-null;} an appropriate instance + */ + public static Rop opCmpl(TypeBearer type) { + switch (type.getBasicType()) { + case Type.BT_LONG: return CMPL_LONG; + case Type.BT_FLOAT: return CMPL_FLOAT; + case Type.BT_DOUBLE: return CMPL_DOUBLE; + } + + return throwBadType(type); + } + + /** + * Returns the appropriate {@code cmpg} rop for the given type. The + * result is a shared instance. + * + * @param type {@code non-null;} type of value being compared + * @return {@code non-null;} an appropriate instance + */ + public static Rop opCmpg(TypeBearer type) { + switch (type.getBasicType()) { + case Type.BT_FLOAT: return CMPG_FLOAT; + case Type.BT_DOUBLE: return CMPG_DOUBLE; + } + + return throwBadType(type); + } + + /** + * Returns the appropriate {@code conv} rop for the given types. The + * result is a shared instance. + * + * @param dest {@code non-null;} target value type + * @param source {@code non-null;} source value type + * @return {@code non-null;} an appropriate instance + */ + public static Rop opConv(TypeBearer dest, TypeBearer source) { + int dbt = dest.getBasicFrameType(); + switch (source.getBasicFrameType()) { + case Type.BT_INT: { + switch (dbt) { + case Type.BT_LONG: return CONV_I2L; + case Type.BT_FLOAT: return CONV_I2F; + case Type.BT_DOUBLE: return CONV_I2D; + default: break; + } + } + case Type.BT_LONG: { + switch (dbt) { + case Type.BT_INT: return CONV_L2I; + case Type.BT_FLOAT: return CONV_L2F; + case Type.BT_DOUBLE: return CONV_L2D; + default: break; + } + } + case Type.BT_FLOAT: { + switch (dbt) { + case Type.BT_INT: return CONV_F2I; + case Type.BT_LONG: return CONV_F2L; + case Type.BT_DOUBLE: return CONV_F2D; + default: break; + } + } + case Type.BT_DOUBLE: { + switch (dbt) { + case Type.BT_INT: return CONV_D2I; + case Type.BT_LONG: return CONV_D2L; + case Type.BT_FLOAT: return CONV_D2F; + default: break; + } + } + } + + return throwBadTypes(StdTypeList.make(dest.getType(), + source.getType())); + } + + /** + * Returns the appropriate {@code return} rop for the given type. The + * result is a shared instance. + * + * @param type {@code non-null;} type of value being returned + * @return {@code non-null;} an appropriate instance + */ + public static Rop opReturn(TypeBearer type) { + switch (type.getBasicFrameType()) { + case Type.BT_INT: return RETURN_INT; + case Type.BT_LONG: return RETURN_LONG; + case Type.BT_FLOAT: return RETURN_FLOAT; + case Type.BT_DOUBLE: return RETURN_DOUBLE; + case Type.BT_OBJECT: return RETURN_OBJECT; + case Type.BT_VOID: return RETURN_VOID; + } + + return throwBadType(type); + } + + /** + * Returns the appropriate {@code aget} rop for the given type. The + * result is a shared instance. + * + * @param type {@code non-null;} element type of array being accessed + * @return {@code non-null;} an appropriate instance + */ + public static Rop opAget(TypeBearer type) { + switch (type.getBasicType()) { + case Type.BT_INT: return AGET_INT; + case Type.BT_LONG: return AGET_LONG; + case Type.BT_FLOAT: return AGET_FLOAT; + case Type.BT_DOUBLE: return AGET_DOUBLE; + case Type.BT_OBJECT: return AGET_OBJECT; + case Type.BT_BOOLEAN: return AGET_BOOLEAN; + case Type.BT_BYTE: return AGET_BYTE; + case Type.BT_CHAR: return AGET_CHAR; + case Type.BT_SHORT: return AGET_SHORT; + } + + return throwBadType(type); + } + + /** + * Returns the appropriate {@code aput} rop for the given type. The + * result is a shared instance. + * + * @param type {@code non-null;} element type of array being accessed + * @return {@code non-null;} an appropriate instance + */ + public static Rop opAput(TypeBearer type) { + switch (type.getBasicType()) { + case Type.BT_INT: return APUT_INT; + case Type.BT_LONG: return APUT_LONG; + case Type.BT_FLOAT: return APUT_FLOAT; + case Type.BT_DOUBLE: return APUT_DOUBLE; + case Type.BT_OBJECT: return APUT_OBJECT; + case Type.BT_BOOLEAN: return APUT_BOOLEAN; + case Type.BT_BYTE: return APUT_BYTE; + case Type.BT_CHAR: return APUT_CHAR; + case Type.BT_SHORT: return APUT_SHORT; + } + + return throwBadType(type); + } + + /** + * Returns the appropriate {@code new-array} rop for the given + * type. The result is a shared instance. + * + * @param arrayType {@code non-null;} array type of array being created + * @return {@code non-null;} an appropriate instance + */ + public static Rop opNewArray(TypeBearer arrayType) { + Type type = arrayType.getType(); + Type elementType = type.getComponentType(); + + switch (elementType.getBasicType()) { + case Type.BT_INT: return NEW_ARRAY_INT; + case Type.BT_LONG: return NEW_ARRAY_LONG; + case Type.BT_FLOAT: return NEW_ARRAY_FLOAT; + case Type.BT_DOUBLE: return NEW_ARRAY_DOUBLE; + case Type.BT_BOOLEAN: return NEW_ARRAY_BOOLEAN; + case Type.BT_BYTE: return NEW_ARRAY_BYTE; + case Type.BT_CHAR: return NEW_ARRAY_CHAR; + case Type.BT_SHORT: return NEW_ARRAY_SHORT; + case Type.BT_OBJECT: { + return new Rop(RegOps.NEW_ARRAY, type, StdTypeList.INT, + Exceptions.LIST_Error_NegativeArraySizeException, + "new-array-object"); + } + } + + return throwBadType(type); + } + + /** + * Returns the appropriate {@code filled-new-array} rop for the given + * type. The result may be a shared instance. + * + * @param arrayType {@code non-null;} type of array being created + * @param count {@code >= 0;} number of elements that the array should have + * @return {@code non-null;} an appropriate instance + */ + public static Rop opFilledNewArray(TypeBearer arrayType, int count) { + Type type = arrayType.getType(); + Type elementType = type.getComponentType(); + + if (elementType.isCategory2()) { + return throwBadType(arrayType); + } + + if (count < 0) { + throw new IllegalArgumentException("count < 0"); + } + + StdTypeList sourceTypes = new StdTypeList(count); + + for (int i = 0; i < count; i++) { + sourceTypes.set(i, elementType); + } + + // Note: The resulting rop is considered call-like. + return new Rop(RegOps.FILLED_NEW_ARRAY, + sourceTypes, + Exceptions.LIST_Error); + } + + /** + * Returns the appropriate {@code get-field} rop for the given + * type. The result is a shared instance. + * + * @param type {@code non-null;} type of the field in question + * @return {@code non-null;} an appropriate instance + */ + public static Rop opGetField(TypeBearer type) { + switch (type.getBasicType()) { + case Type.BT_INT: return GET_FIELD_INT; + case Type.BT_LONG: return GET_FIELD_LONG; + case Type.BT_FLOAT: return GET_FIELD_FLOAT; + case Type.BT_DOUBLE: return GET_FIELD_DOUBLE; + case Type.BT_OBJECT: return GET_FIELD_OBJECT; + case Type.BT_BOOLEAN: return GET_FIELD_BOOLEAN; + case Type.BT_BYTE: return GET_FIELD_BYTE; + case Type.BT_CHAR: return GET_FIELD_CHAR; + case Type.BT_SHORT: return GET_FIELD_SHORT; + } + + return throwBadType(type); + } + + /** + * Returns the appropriate {@code put-field} rop for the given + * type. The result is a shared instance. + * + * @param type {@code non-null;} type of the field in question + * @return {@code non-null;} an appropriate instance + */ + public static Rop opPutField(TypeBearer type) { + switch (type.getBasicType()) { + case Type.BT_INT: return PUT_FIELD_INT; + case Type.BT_LONG: return PUT_FIELD_LONG; + case Type.BT_FLOAT: return PUT_FIELD_FLOAT; + case Type.BT_DOUBLE: return PUT_FIELD_DOUBLE; + case Type.BT_OBJECT: return PUT_FIELD_OBJECT; + case Type.BT_BOOLEAN: return PUT_FIELD_BOOLEAN; + case Type.BT_BYTE: return PUT_FIELD_BYTE; + case Type.BT_CHAR: return PUT_FIELD_CHAR; + case Type.BT_SHORT: return PUT_FIELD_SHORT; + } + + return throwBadType(type); + } + + /** + * Returns the appropriate {@code get-static} rop for the given + * type. The result is a shared instance. + * + * @param type {@code non-null;} type of the field in question + * @return {@code non-null;} an appropriate instance + */ + public static Rop opGetStatic(TypeBearer type) { + switch (type.getBasicType()) { + case Type.BT_INT: return GET_STATIC_INT; + case Type.BT_LONG: return GET_STATIC_LONG; + case Type.BT_FLOAT: return GET_STATIC_FLOAT; + case Type.BT_DOUBLE: return GET_STATIC_DOUBLE; + case Type.BT_OBJECT: return GET_STATIC_OBJECT; + case Type.BT_BOOLEAN: return GET_STATIC_BOOLEAN; + case Type.BT_BYTE: return GET_STATIC_BYTE; + case Type.BT_CHAR: return GET_STATIC_CHAR; + case Type.BT_SHORT: return GET_STATIC_SHORT; + } + + return throwBadType(type); + } + + /** + * Returns the appropriate {@code put-static} rop for the given + * type. The result is a shared instance. + * + * @param type {@code non-null;} type of the field in question + * @return {@code non-null;} an appropriate instance + */ + public static Rop opPutStatic(TypeBearer type) { + switch (type.getBasicType()) { + case Type.BT_INT: return PUT_STATIC_INT; + case Type.BT_LONG: return PUT_STATIC_LONG; + case Type.BT_FLOAT: return PUT_STATIC_FLOAT; + case Type.BT_DOUBLE: return PUT_STATIC_DOUBLE; + case Type.BT_OBJECT: return PUT_STATIC_OBJECT; + case Type.BT_BOOLEAN: return PUT_STATIC_BOOLEAN; + case Type.BT_BYTE: return PUT_STATIC_BYTE; + case Type.BT_CHAR: return PUT_STATIC_CHAR; + case Type.BT_SHORT: return PUT_STATIC_SHORT; + } + + return throwBadType(type); + } + + /** + * Returns the appropriate {@code invoke-static} rop for the + * given type. The result is typically a newly-allocated instance. + * + * @param meth {@code non-null;} descriptor of the method + * @return {@code non-null;} an appropriate instance + */ + public static Rop opInvokeStatic(Prototype meth) { + return new Rop(RegOps.INVOKE_STATIC, + meth.getParameterFrameTypes(), + StdTypeList.THROWABLE); + } + + /** + * Returns the appropriate {@code invoke-virtual} rop for the + * given type. The result is typically a newly-allocated instance. + * + * @param meth {@code non-null;} descriptor of the method, including the + * {@code this} parameter + * @return {@code non-null;} an appropriate instance + */ + public static Rop opInvokeVirtual(Prototype meth) { + return new Rop(RegOps.INVOKE_VIRTUAL, + meth.getParameterFrameTypes(), + StdTypeList.THROWABLE); + } + + /** + * Returns the appropriate {@code invoke-super} rop for the + * given type. The result is typically a newly-allocated instance. + * + * @param meth {@code non-null;} descriptor of the method, including the + * {@code this} parameter + * @return {@code non-null;} an appropriate instance + */ + public static Rop opInvokeSuper(Prototype meth) { + return new Rop(RegOps.INVOKE_SUPER, + meth.getParameterFrameTypes(), + StdTypeList.THROWABLE); + } + + /** + * Returns the appropriate {@code invoke-direct} rop for the + * given type. The result is typically a newly-allocated instance. + * + * @param meth {@code non-null;} descriptor of the method, including the + * {@code this} parameter + * @return {@code non-null;} an appropriate instance + */ + public static Rop opInvokeDirect(Prototype meth) { + return new Rop(RegOps.INVOKE_DIRECT, + meth.getParameterFrameTypes(), + StdTypeList.THROWABLE); + } + + /** + * Returns the appropriate {@code invoke-interface} rop for the + * given type. The result is typically a newly-allocated instance. + * + * @param meth {@code non-null;} descriptor of the method, including the + * {@code this} parameter + * @return {@code non-null;} an appropriate instance + */ + public static Rop opInvokeInterface(Prototype meth) { + return new Rop(RegOps.INVOKE_INTERFACE, + meth.getParameterFrameTypes(), + StdTypeList.THROWABLE); + } + + /** + * Returns the appropriate {@code mark-local} rop for the given type. + * The result is a shared instance. + * + * @param type {@code non-null;} type of value being marked + * @return {@code non-null;} an appropriate instance + */ + public static Rop opMarkLocal(TypeBearer type) { + switch (type.getBasicFrameType()) { + case Type.BT_INT: return MARK_LOCAL_INT; + case Type.BT_LONG: return MARK_LOCAL_LONG; + case Type.BT_FLOAT: return MARK_LOCAL_FLOAT; + case Type.BT_DOUBLE: return MARK_LOCAL_DOUBLE; + case Type.BT_OBJECT: return MARK_LOCAL_OBJECT; + } + + return throwBadType(type); + } + + /** + * This class is uninstantiable. + */ + private Rops() { + // This space intentionally left blank. + } + + /** + * Throws the right exception to complain about a bogus type. + * + * @param type {@code non-null;} the bad type + * @return never + */ + private static Rop throwBadType(TypeBearer type) { + throw new IllegalArgumentException("bad type: " + type); + } + + /** + * Throws the right exception to complain about a bogus list of types. + * + * @param types {@code non-null;} the bad types + * @return never + */ + private static Rop throwBadTypes(TypeList types) { + throw new IllegalArgumentException("bad types: " + types); + } +} diff --git a/dx/src/com/android/jack/dx/rop/code/SourcePosition.java b/dx/src/com/android/jack/dx/rop/code/SourcePosition.java new file mode 100644 index 00000000..0c4c1226 --- /dev/null +++ b/dx/src/com/android/jack/dx/rop/code/SourcePosition.java @@ -0,0 +1,168 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.rop.code; + +import com.android.jack.dx.rop.cst.CstString; +import com.android.jack.dx.util.Hex; + +/** + * Information about a source position for code, which includes both a + * line number and original bytecode address. + */ +public final class SourcePosition { + /** {@code non-null;} convenient "no information known" instance */ + public static final SourcePosition NO_INFO = + new SourcePosition(null, -1, -1); + + /** {@code null-ok;} name of the file of origin or {@code null} if unknown */ + private final CstString sourceFile; + + /** + * {@code >= -1;} the bytecode address, or {@code -1} if that + * information is unknown + */ + private final int address; + + /** + * {@code >= -1;} the line number, or {@code -1} if that + * information is unknown + */ + private final int line; + + /** + * Constructs an instance. + * + * @param sourceFile {@code null-ok;} name of the file of origin or + * {@code null} if unknown + * @param address {@code >= -1;} original bytecode address or {@code -1} + * if unknown + * @param line {@code >= -1;} original line number or {@code -1} if + * unknown + */ + public SourcePosition(CstString sourceFile, int address, int line) { + if (address < -1) { + throw new IllegalArgumentException("address < -1"); + } + + if (line < -1) { + throw new IllegalArgumentException("line < -1"); + } + + this.sourceFile = sourceFile; + this.address = address; + this.line = line; + } + + /** {@inheritDoc} */ + @Override + public String toString() { + StringBuffer sb = new StringBuffer(50); + + if (sourceFile != null) { + sb.append(sourceFile.toHuman()); + sb.append(":"); + } + + if (line >= 0) { + sb.append(line); + } + + sb.append('@'); + + if (address < 0) { + sb.append("????"); + } else { + sb.append(Hex.u2(address)); + } + + return sb.toString(); + } + + /** {@inheritDoc} */ + @Override + public boolean equals(Object other) { + if (!(other instanceof SourcePosition)) { + return false; + } + + if (this == other) { + return true; + } + + SourcePosition pos = (SourcePosition) other; + + return (address == pos.address) && sameLineAndFile(pos); + } + + /** {@inheritDoc} */ + @Override + public int hashCode() { + return sourceFile.hashCode() + address + line; + } + + /** + * Returns whether the lines match between this instance and + * the one given. + * + * @param other {@code non-null;} the instance to compare to + * @return {@code true} iff the lines match + */ + public boolean sameLine(SourcePosition other) { + return (line == other.line); + } + + /** + * Returns whether the lines and files match between this instance and + * the one given. + * + * @param other {@code non-null;} the instance to compare to + * @return {@code true} iff the lines and files match + */ + public boolean sameLineAndFile(SourcePosition other) { + return (line == other.line) && + ((sourceFile == other.sourceFile) || + ((sourceFile != null) && sourceFile.equals(other.sourceFile))); + } + + /** + * Gets the source file, if known. + * + * @return {@code null-ok;} the source file or {@code null} if unknown + */ + public CstString getSourceFile() { + return sourceFile; + } + + /** + * Gets the original bytecode address. + * + * @return {@code >= -1;} the address or {@code -1} if unknown + */ + public int getAddress() { + return address; + } + + /** + * Gets the original line number. + * + * @return {@code >= -1;} the original line number or {@code -1} if + * unknown + */ + public int getLine() { + return line; + } +} diff --git a/dx/src/com/android/jack/dx/rop/code/SwitchInsn.java b/dx/src/com/android/jack/dx/rop/code/SwitchInsn.java new file mode 100644 index 00000000..a91b6242 --- /dev/null +++ b/dx/src/com/android/jack/dx/rop/code/SwitchInsn.java @@ -0,0 +1,119 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.rop.code; + +import com.android.jack.dx.rop.type.StdTypeList; +import com.android.jack.dx.rop.type.Type; +import com.android.jack.dx.rop.type.TypeList; +import com.android.jack.dx.util.IntList; + +/** + * Instruction which contains switch cases. + */ +public final class SwitchInsn + extends Insn { + /** {@code non-null;} list of switch cases */ + private final IntList cases; + + /** + * Constructs an instance. + * + * @param opcode {@code non-null;} the opcode + * @param position {@code non-null;} source position + * @param result {@code null-ok;} spec for the result, if any + * @param sources {@code non-null;} specs for all the sources + * @param cases {@code non-null;} list of switch cases + */ + public SwitchInsn(Rop opcode, SourcePosition position, RegisterSpec result, + RegisterSpecList sources, IntList cases) { + super(opcode, position, result, sources); + + if (opcode.getBranchingness() != Rop.BRANCH_SWITCH) { + throw new IllegalArgumentException("bogus branchingness"); + } + + if (cases == null) { + throw new NullPointerException("cases == null"); + } + + this.cases = cases; + } + + /** {@inheritDoc} */ + @Override + public String getInlineString() { + return cases.toString(); + } + + /** {@inheritDoc} */ + @Override + public TypeList getCatches() { + return StdTypeList.EMPTY; + } + + /** {@inheritDoc} */ + @Override + public void accept(Visitor visitor) { + visitor.visitSwitchInsn(this); + } + + /** {@inheritDoc} */ + @Override + public Insn withAddedCatch(Type type) { + throw new UnsupportedOperationException("unsupported"); + } + + /** {@inheritDoc} */ + @Override + public Insn withRegisterOffset(int delta) { + return new SwitchInsn(getOpcode(), getPosition(), + getResult().withOffset(delta), + getSources().withOffset(delta), + cases); + } + + /** + * {@inheritDoc} + * + *

SwitchInsn always compares false. The current use for this method + * never encounters {@code SwitchInsn}s + */ + @Override + public boolean contentEquals(Insn b) { + return false; + } + + /** {@inheritDoc} */ + @Override + public Insn withNewRegisters(RegisterSpec result, + RegisterSpecList sources) { + + return new SwitchInsn(getOpcode(), getPosition(), + result, + sources, + cases); + } + + /** + * Gets the list of switch cases. + * + * @return {@code non-null;} the case list + */ + public IntList getCases() { + return cases; + } +} diff --git a/dx/src/com/android/jack/dx/rop/code/ThrowingCstInsn.java b/dx/src/com/android/jack/dx/rop/code/ThrowingCstInsn.java new file mode 100644 index 00000000..aad2a03a --- /dev/null +++ b/dx/src/com/android/jack/dx/rop/code/ThrowingCstInsn.java @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.rop.code; + +import com.android.jack.dx.rop.cst.Constant; +import com.android.jack.dx.rop.cst.CstString; +import com.android.jack.dx.rop.type.Type; +import com.android.jack.dx.rop.type.TypeList; + +/** + * Instruction which contains an explicit reference to a constant + * and which might throw an exception. + */ +public final class ThrowingCstInsn + extends CstInsn { + /** {@code non-null;} list of exceptions caught */ + private final TypeList catches; + + /** + * Constructs an instance. + * + * @param opcode {@code non-null;} the opcode + * @param position {@code non-null;} source position + * @param sources {@code non-null;} specs for all the sources + * @param catches {@code non-null;} list of exceptions caught + * @param cst {@code non-null;} the constant + */ + public ThrowingCstInsn(Rop opcode, SourcePosition position, + RegisterSpecList sources, + TypeList catches, Constant cst) { + super(opcode, position, null, sources, cst); + + if (opcode.getBranchingness() != Rop.BRANCH_THROW) { + throw new IllegalArgumentException("bogus branchingness"); + } + + if (catches == null) { + throw new NullPointerException("catches == null"); + } + + this.catches = catches; + } + + /** {@inheritDoc} */ + @Override + public String getInlineString() { + Constant cst = getConstant(); + String constantString = cst.toHuman(); + if (cst instanceof CstString) { + constantString = ((CstString) cst).toQuoted(); + } + return constantString + " " + ThrowingInsn.toCatchString(catches); + } + + /** {@inheritDoc} */ + @Override + public TypeList getCatches() { + return catches; + } + + /** {@inheritDoc} */ + @Override + public void accept(Visitor visitor) { + visitor.visitThrowingCstInsn(this); + } + + /** {@inheritDoc} */ + @Override + public Insn withAddedCatch(Type type) { + return new ThrowingCstInsn(getOpcode(), getPosition(), + getSources(), catches.withAddedType(type), + getConstant()); + } + + /** {@inheritDoc} */ + @Override + public Insn withRegisterOffset(int delta) { + return new ThrowingCstInsn(getOpcode(), getPosition(), + getSources().withOffset(delta), + catches, + getConstant()); + } + + /** {@inheritDoc} */ + @Override + public Insn withNewRegisters(RegisterSpec result, + RegisterSpecList sources) { + + return new ThrowingCstInsn(getOpcode(), getPosition(), + sources, + catches, + getConstant()); + } + + +} diff --git a/dx/src/com/android/jack/dx/rop/code/ThrowingInsn.java b/dx/src/com/android/jack/dx/rop/code/ThrowingInsn.java new file mode 100644 index 00000000..cc12194d --- /dev/null +++ b/dx/src/com/android/jack/dx/rop/code/ThrowingInsn.java @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.rop.code; + +import com.android.jack.dx.rop.type.Type; +import com.android.jack.dx.rop.type.TypeList; + +/** + * Instruction which possibly throws. The {@code successors} list in the + * basic block an instance of this class is inside corresponds in-order to + * the list of exceptions handled by this instruction, with the + * no-exception case appended as the final target. + */ +public final class ThrowingInsn + extends Insn { + /** {@code non-null;} list of exceptions caught */ + private final TypeList catches; + + /** + * Gets the string form of a register spec list to be used as a catches + * list. + * + * @param catches {@code non-null;} the catches list + * @return {@code non-null;} the string form + */ + public static String toCatchString(TypeList catches) { + StringBuffer sb = new StringBuffer(100); + + sb.append("catch"); + + int sz = catches.size(); + for (int i = 0; i < sz; i++) { + sb.append(" "); + sb.append(catches.getType(i).toHuman()); + } + + return sb.toString(); + } + + /** + * Constructs an instance. + * + * @param opcode {@code non-null;} the opcode + * @param position {@code non-null;} source position + * @param sources {@code non-null;} specs for all the sources + * @param catches {@code non-null;} list of exceptions caught + */ + public ThrowingInsn(Rop opcode, SourcePosition position, + RegisterSpecList sources, + TypeList catches) { + super(opcode, position, null, sources); + + if (opcode.getBranchingness() != Rop.BRANCH_THROW) { + throw new IllegalArgumentException("bogus branchingness"); + } + + if (catches == null) { + throw new NullPointerException("catches == null"); + } + + this.catches = catches; + } + + /** {@inheritDoc} */ + @Override + public String getInlineString() { + return toCatchString(catches); + } + + /** {@inheritDoc} */ + @Override + public TypeList getCatches() { + return catches; + } + + /** {@inheritDoc} */ + @Override + public void accept(Visitor visitor) { + visitor.visitThrowingInsn(this); + } + + /** {@inheritDoc} */ + @Override + public Insn withAddedCatch(Type type) { + return new ThrowingInsn(getOpcode(), getPosition(), + getSources(), catches.withAddedType(type)); + } + + /** {@inheritDoc} */ + @Override + public Insn withRegisterOffset(int delta) { + return new ThrowingInsn(getOpcode(), getPosition(), + getSources().withOffset(delta), + catches); + } + + /** {@inheritDoc} */ + @Override + public Insn withNewRegisters(RegisterSpec result, + RegisterSpecList sources) { + + return new ThrowingInsn(getOpcode(), getPosition(), + sources, + catches); + } +} diff --git a/dx/src/com/android/jack/dx/rop/code/TranslationAdvice.java b/dx/src/com/android/jack/dx/rop/code/TranslationAdvice.java new file mode 100644 index 00000000..32936ba5 --- /dev/null +++ b/dx/src/com/android/jack/dx/rop/code/TranslationAdvice.java @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.rop.code; + +/** + * Interface for "advice" passed from the late stage of translation back + * to the early stage. This allows for the final target architecture to + * exert its influence early in the translation process without having + * the early stage code be explicitly tied to the target. + */ +public interface TranslationAdvice { + /** + * Returns an indication of whether the target can directly represent an + * instruction with the given opcode operating on the given arguments, + * where the last source argument is used as a constant. (That is, the + * last argument must have a type which indicates it is a known constant.) + * The instruction associated must have exactly two sources. + * + * @param opcode {@code non-null;} the opcode + * @param sourceA {@code non-null;} the first source + * @param sourceB {@code non-null;} the second source + * @return {@code true} iff the target can represent the operation + * using a constant for the last argument + */ + public boolean hasConstantOperation(Rop opcode, + RegisterSpec sourceA, RegisterSpec sourceB); + + /** + * Returns true if the translation target requires the sources of the + * specified opcode to be in order and contiguous (eg, for an invoke-range) + * + * @param opcode {@code non-null;} opcode + * @param sources {@code non-null;} source list + * @return {@code true} iff the target requires the sources to be + * in order and contiguous. + */ + public boolean requiresSourcesInOrder(Rop opcode, RegisterSpecList sources); + + /** + * Gets the maximum register width that can be represented optimally. + * For example, Dex bytecode does not have instruction forms that take + * register numbers larger than 15 for all instructions so + * DexTranslationAdvice returns 15 here. + * + * @return register count noted above + */ + public int getMaxOptimalRegisterCount(); +} diff --git a/dx/src/com/android/jack/dx/rop/code/package.html b/dx/src/com/android/jack/dx/rop/code/package.html new file mode 100644 index 00000000..8baa104e --- /dev/null +++ b/dx/src/com/android/jack/dx/rop/code/package.html @@ -0,0 +1,8 @@ + +

Classes relating to a register-based opcode system.

+ +

PACKAGES USED: +

    +
  • com.android.jack.dx.util
  • +
+ diff --git a/dx/src/com/android/jack/dx/rop/cst/Constant.java b/dx/src/com/android/jack/dx/rop/cst/Constant.java new file mode 100644 index 00000000..7c756e21 --- /dev/null +++ b/dx/src/com/android/jack/dx/rop/cst/Constant.java @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.rop.cst; + +import com.android.jack.dx.util.ToHuman; + +/** + * Base class for constants of all sorts. + */ +public abstract class Constant + implements ToHuman, Comparable { + /** + * Returns {@code true} if this instance is a category-2 constant, + * meaning it takes up two slots in the constant pool, or + * {@code false} if this instance is category-1. + * + * @return {@code true} iff this instance is category-2 + */ + public abstract boolean isCategory2(); + + /** + * Returns the human name for the particular type of constant + * this instance is. + * + * @return {@code non-null;} the name + */ + public abstract String typeName(); + + /** + * {@inheritDoc} + * + * This compares in class-major and value-minor order. + */ + public final int compareTo(Constant other) { + Class clazz = getClass(); + Class otherClazz = other.getClass(); + + if (clazz != otherClazz) { + return clazz.getName().compareTo(otherClazz.getName()); + } + + return compareTo0(other); + } + + /** + * Compare the values of this and another instance, which are guaranteed + * to be of the same class. Subclasses must implement this. + * + * @param other {@code non-null;} the instance to compare to + * @return {@code -1}, {@code 0}, or {@code 1}, as usual + * for a comparison + */ + protected abstract int compareTo0(Constant other); +} diff --git a/dx/src/com/android/jack/dx/rop/cst/ConstantPool.java b/dx/src/com/android/jack/dx/rop/cst/ConstantPool.java new file mode 100644 index 00000000..218f3c41 --- /dev/null +++ b/dx/src/com/android/jack/dx/rop/cst/ConstantPool.java @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.rop.cst; + +/** + * Interface for constant pools, which are, more or less, just lists of + * {@link Constant} objects. + */ +public interface ConstantPool { + /** + * Get the "size" of the constant pool. This corresponds to the + * class file field {@code constant_pool_count}, and is in fact + * always at least one more than the actual size of the constant pool, + * as element {@code 0} is always invalid. + * + * @return {@code >= 1;} the size + */ + public int size(); + + /** + * Get the {@code n}th entry in the constant pool, which must + * be valid. + * + * @param n {@code n >= 0, n < size();} the constant pool index + * @return {@code non-null;} the corresponding entry + * @throws IllegalArgumentException thrown if {@code n} is + * in-range but invalid + */ + public Constant get(int n); + + /** + * Get the {@code n}th entry in the constant pool, which must + * be valid unless {@code n == 0}, in which case {@code null} + * is returned. + * + * @param n {@code n >= 0, n < size();} the constant pool index + * @return {@code null-ok;} the corresponding entry, if {@code n != 0} + * @throws IllegalArgumentException thrown if {@code n} is + * in-range and non-zero but invalid + */ + public Constant get0Ok(int n); + + /** + * Get the {@code n}th entry in the constant pool, or + * {@code null} if the index is in-range but invalid. In + * particular, {@code null} is returned for index {@code 0} + * as well as the index after any entry which is defined to take up + * two slots (that is, {@code Long} and {@code Double} + * entries). + * + * @param n {@code n >= 0, n < size();} the constant pool index + * @return {@code null-ok;} the corresponding entry, or {@code null} if + * the index is in-range but invalid + */ + public Constant getOrNull(int n); +} diff --git a/dx/src/com/android/jack/dx/rop/cst/CstAnnotation.java b/dx/src/com/android/jack/dx/rop/cst/CstAnnotation.java new file mode 100644 index 00000000..84dd61f8 --- /dev/null +++ b/dx/src/com/android/jack/dx/rop/cst/CstAnnotation.java @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.rop.cst; + +import com.android.jack.dx.rop.annotation.Annotation; + +/** + * Constant type that represents an annotation. + */ +public final class CstAnnotation extends Constant { + /** {@code non-null;} the actual annotation */ + private final Annotation annotation; + + /** + * Constructs an instance. + * + * @param annotation {@code non-null;} the annotation to hold + */ + public CstAnnotation(Annotation annotation) { + if (annotation == null) { + throw new NullPointerException("annotation == null"); + } + + annotation.throwIfMutable(); + + this.annotation = annotation; + } + + /** {@inheritDoc} */ + @Override + public boolean equals(Object other) { + if (! (other instanceof CstAnnotation)) { + return false; + } + + return annotation.equals(((CstAnnotation) other).annotation); + } + + /** {@inheritDoc} */ + @Override + public int hashCode() { + return annotation.hashCode(); + } + + /** {@inheritDoc} */ + @Override + protected int compareTo0(Constant other) { + return annotation.compareTo(((CstAnnotation) other).annotation); + } + + /** {@inheritDoc} */ + @Override + public String toString() { + return annotation.toString(); + } + + /** {@inheritDoc} */ + @Override + public String typeName() { + return "annotation"; + } + + /** {@inheritDoc} */ + @Override + public boolean isCategory2() { + return false; + } + + /** {@inheritDoc} */ + public String toHuman() { + return annotation.toString(); + } + + /** + * Get the underlying annotation. + * + * @return {@code non-null;} the annotation + */ + public Annotation getAnnotation() { + return annotation; + } +} diff --git a/dx/src/com/android/jack/dx/rop/cst/CstArray.java b/dx/src/com/android/jack/dx/rop/cst/CstArray.java new file mode 100644 index 00000000..080bb696 --- /dev/null +++ b/dx/src/com/android/jack/dx/rop/cst/CstArray.java @@ -0,0 +1,159 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.rop.cst; + +import com.android.jack.dx.rop.type.Type; +import com.android.jack.dx.util.FixedSizeList; + +/** + * Constant type to represent a fixed array of other constants. + */ +public final class CstArray extends Constant { + /** {@code non-null;} the actual list of contents */ + private final List list; + + /** + * Constructs an instance. + * + * @param list {@code non-null;} the actual list of contents + */ + public CstArray(List list) { + if (list == null) { + throw new NullPointerException("list == null"); + } + + list.throwIfMutable(); + + this.list = list; + } + + /** {@inheritDoc} */ + @Override + public boolean equals(Object other) { + if (! (other instanceof CstArray)) { + return false; + } + + return list.equals(((CstArray) other).list); + } + + /** {@inheritDoc} */ + @Override + public int hashCode() { + return list.hashCode(); + } + + /** {@inheritDoc} */ + @Override + protected int compareTo0(Constant other) { + return list.compareTo(((CstArray) other).list); + } + + /** {@inheritDoc} */ + @Override + public String toString() { + return list.toString("array{", ", ", "}"); + } + + /** {@inheritDoc} */ + @Override + public String typeName() { + return "array"; + } + + /** {@inheritDoc} */ + @Override + public boolean isCategory2() { + return false; + } + + /** {@inheritDoc} */ + public String toHuman() { + return list.toHuman("{", ", ", "}"); + } + + /** + * Get the underlying list. + * + * @return {@code non-null;} the list + */ + public List getList() { + return list; + } + + /** + * List of {@link Constant} instances. + */ + public static final class List + extends FixedSizeList implements Comparable { + /** + * Constructs an instance. All indices initially contain + * {@code null}. + * + * @param size the size of the list + */ + public List(int size) { + super(size); + } + + /** {@inheritDoc} */ + public int compareTo(List other) { + int thisSize = size(); + int otherSize = other.size(); + int compareSize = (thisSize < otherSize) ? thisSize : otherSize; + + for (int i = 0; i < compareSize; i++) { + Constant thisItem = (Constant) get0(i); + Constant otherItem = (Constant) other.get0(i); + int compare = thisItem.compareTo(otherItem); + if (compare != 0) { + return compare; + } + } + + if (thisSize < otherSize) { + return -1; + } else if (thisSize > otherSize) { + return 1; + } + + return 0; + } + + /** + * Gets the element at the given index. It is an error to call + * this with the index for an element which was never set; if you + * do that, this will throw {@code NullPointerException}. + * + * @param n {@code >= 0, < size();} which index + * @return {@code non-null;} element at that index + */ + public Constant get(int n) { + return (Constant) get0(n); + } + + /** + * Sets the element at the given index. + * + * @param n {@code >= 0, < size();} which index + * @param a {@code null-ok;} the element to set at {@code n} + */ + public void set(int n, Constant a) { + set0(n, a); + } + } +} diff --git a/dx/src/com/android/jack/dx/rop/cst/CstBaseMethodRef.java b/dx/src/com/android/jack/dx/rop/cst/CstBaseMethodRef.java new file mode 100644 index 00000000..fedb208d --- /dev/null +++ b/dx/src/com/android/jack/dx/rop/cst/CstBaseMethodRef.java @@ -0,0 +1,151 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.rop.cst; + +import com.android.jack.dx.rop.type.Prototype; +import com.android.jack.dx.rop.type.Type; +import com.android.jack.dx.rop.type.TypeBearer; + +/** + * Base class for constants of "methodish" type. + * + *

Note: As a {@link TypeBearer}, this class bears the return type + * of the method.

+ */ +public abstract class CstBaseMethodRef + extends CstMemberRef { + /** {@code non-null;} the raw prototype for this method */ + private final Prototype prototype; + + /** + * {@code null-ok;} the prototype for this method taken to be an instance + * method, or {@code null} if not yet calculated + */ + private Prototype instancePrototype; + + /** + * Constructs an instance. + * + * @param definingClass {@code non-null;} the type of the defining class + * @param nat {@code non-null;} the name-and-type + */ + /*package*/ CstBaseMethodRef(CstType definingClass, CstNat nat) { + super(definingClass, nat); + + String descriptor = getNat().getDescriptor().getString(); + this.prototype = Prototype.intern(descriptor); + this.instancePrototype = null; + } + + /** + * Gets the raw prototype of this method. This doesn't include a + * {@code this} argument. + * + * @return {@code non-null;} the method prototype + */ + public final Prototype getPrototype() { + return prototype; + } + + /** + * Gets the prototype of this method as either a + * {@code static} or instance method. In the case of a + * {@code static} method, this is the same as the raw + * prototype. In the case of an instance method, this has an + * appropriately-typed {@code this} argument as the first + * one. + * + * @param isStatic whether the method should be considered static + * @return {@code non-null;} the method prototype + */ + public final Prototype getPrototype(boolean isStatic) { + if (isStatic) { + return prototype; + } else { + if (instancePrototype == null) { + Type thisType = getDefiningClass().getClassType(); + instancePrototype = prototype.withFirstParameter(thisType); + } + return instancePrototype; + } + } + + /** {@inheritDoc} */ + @Override + protected final int compareTo0(Constant other) { + int cmp = super.compareTo0(other); + + if (cmp != 0) { + return cmp; + } + + CstBaseMethodRef otherMethod = (CstBaseMethodRef) other; + return prototype.compareTo(otherMethod.prototype); + } + + /** + * {@inheritDoc} + * + * In this case, this method returns the return type of this method. + * + * @return {@code non-null;} the method's return type + */ + public final Type getType() { + return prototype.getReturnType(); + } + + /** + * Gets the number of words of parameters required by this + * method's descriptor. Since instances of this class have no way + * to know if they will be used in a {@code static} or + * instance context, one has to indicate this explicitly as an + * argument. This method is just a convenient shorthand for + * {@code getPrototype().getParameterTypes().getWordCount()}, + * plus {@code 1} if the method is to be treated as an + * instance method. + * + * @param isStatic whether the method should be considered static + * @return {@code >= 0;} the argument word count + */ + public final int getParameterWordCount(boolean isStatic) { + return getPrototype(isStatic).getParameterTypes().getWordCount(); + } + + /** + * Gets whether this is a reference to an instance initialization + * method. This is just a convenient shorthand for + * {@code getNat().isInstanceInit()}. + * + * @return {@code true} iff this is a reference to an + * instance initialization method + */ + public final boolean isInstanceInit() { + return getNat().isInstanceInit(); + } + + /** + * Gets whether this is a reference to a class initialization + * method. This is just a convenient shorthand for + * {@code getNat().isClassInit()}. + * + * @return {@code true} iff this is a reference to an + * instance initialization method + */ + public final boolean isClassInit() { + return getNat().isClassInit(); + } +} diff --git a/dx/src/com/android/jack/dx/rop/cst/CstBoolean.java b/dx/src/com/android/jack/dx/rop/cst/CstBoolean.java new file mode 100644 index 00000000..81a0e274 --- /dev/null +++ b/dx/src/com/android/jack/dx/rop/cst/CstBoolean.java @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.rop.cst; + +import com.android.jack.dx.rop.type.Type; + +/** + * Constants of type {@code boolean}. + */ +public final class CstBoolean + extends CstLiteral32 { + /** {@code non-null;} instance representing {@code false} */ + public static final CstBoolean VALUE_FALSE = new CstBoolean(false); + + /** {@code non-null;} instance representing {@code true} */ + public static final CstBoolean VALUE_TRUE = new CstBoolean(true); + + /** + * Makes an instance for the given value. This will return an + * already-allocated instance. + * + * @param value the {@code boolean} value + * @return {@code non-null;} the appropriate instance + */ + public static CstBoolean make(boolean value) { + return value ? VALUE_TRUE : VALUE_FALSE; + } + + /** + * Makes an instance for the given {@code int} value. This + * will return an already-allocated instance. + * + * @param value must be either {@code 0} or {@code 1} + * @return {@code non-null;} the appropriate instance + */ + public static CstBoolean make(int value) { + if (value == 0) { + return VALUE_FALSE; + } else if (value == 1) { + return VALUE_TRUE; + } else { + throw new IllegalArgumentException("bogus value: " + value); + } + } + + /** + * Constructs an instance. This constructor is private; use {@link #make}. + * + * @param value the {@code boolean} value + */ + private CstBoolean(boolean value) { + super(value ? 1 : 0); + } + + /** {@inheritDoc} */ + @Override + public String toString() { + return getValue() ? "boolean{true}" : "boolean{false}"; + } + + /** {@inheritDoc} */ + public Type getType() { + return Type.BOOLEAN; + } + + /** {@inheritDoc} */ + @Override + public String typeName() { + return "boolean"; + } + + /** {@inheritDoc} */ + public String toHuman() { + return getValue() ? "true" : "false"; + } + + /** + * Gets the {@code boolean} value. + * + * @return the value + */ + public boolean getValue() { + return (getIntBits() == 0) ? false : true; + } +} diff --git a/dx/src/com/android/jack/dx/rop/cst/CstByte.java b/dx/src/com/android/jack/dx/rop/cst/CstByte.java new file mode 100644 index 00000000..5ab41883 --- /dev/null +++ b/dx/src/com/android/jack/dx/rop/cst/CstByte.java @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.rop.cst; + +import com.android.jack.dx.rop.type.Type; +import com.android.jack.dx.util.Hex; + +/** + * Constants of type {@code byte}. + */ +public final class CstByte + extends CstLiteral32 { + /** {@code non-null;} the value {@code 0} as an instance of this class */ + public static final CstByte VALUE_0 = make((byte) 0); + + /** + * Makes an instance for the given value. This may (but does not + * necessarily) return an already-allocated instance. + * + * @param value the {@code byte} value + */ + public static CstByte make(byte value) { + return new CstByte(value); + } + + /** + * Makes an instance for the given {@code int} value. This + * may (but does not necessarily) return an already-allocated + * instance. + * + * @param value the value, which must be in range for a {@code byte} + * @return {@code non-null;} the appropriate instance + */ + public static CstByte make(int value) { + byte cast = (byte) value; + + if (cast != value) { + throw new IllegalArgumentException("bogus byte value: " + + value); + } + + return make(cast); + } + + /** + * Constructs an instance. This constructor is private; use {@link #make}. + * + * @param value the {@code byte} value + */ + private CstByte(byte value) { + super(value); + } + + /** {@inheritDoc} */ + @Override + public String toString() { + int value = getIntBits(); + return "byte{0x" + Hex.u1(value) + " / " + value + '}'; + } + + /** {@inheritDoc} */ + public Type getType() { + return Type.BYTE; + } + + /** {@inheritDoc} */ + @Override + public String typeName() { + return "byte"; + } + + /** {@inheritDoc} */ + public String toHuman() { + return Integer.toString(getIntBits()); + } + + /** + * Gets the {@code byte} value. + * + * @return the value + */ + public byte getValue() { + return (byte) getIntBits(); + } +} diff --git a/dx/src/com/android/jack/dx/rop/cst/CstChar.java b/dx/src/com/android/jack/dx/rop/cst/CstChar.java new file mode 100644 index 00000000..b4cd303d --- /dev/null +++ b/dx/src/com/android/jack/dx/rop/cst/CstChar.java @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.rop.cst; + +import com.android.jack.dx.rop.type.Type; +import com.android.jack.dx.util.Hex; + +/** + * Constants of type {@code char}. + */ +public final class CstChar + extends CstLiteral32 { + /** {@code non-null;} the value {@code 0} as an instance of this class */ + public static final CstChar VALUE_0 = make((char) 0); + + /** + * Makes an instance for the given value. This may (but does not + * necessarily) return an already-allocated instance. + * + * @param value the {@code char} value + */ + public static CstChar make(char value) { + return new CstChar(value); + } + + /** + * Makes an instance for the given {@code int} value. This + * may (but does not necessarily) return an already-allocated + * instance. + * + * @param value the value, which must be in range for a {@code char} + * @return {@code non-null;} the appropriate instance + */ + public static CstChar make(int value) { + char cast = (char) value; + + if (cast != value) { + throw new IllegalArgumentException("bogus char value: " + + value); + } + + return make(cast); + } + + /** + * Constructs an instance. This constructor is private; use {@link #make}. + * + * @param value the {@code char} value + */ + private CstChar(char value) { + super(value); + } + + /** {@inheritDoc} */ + @Override + public String toString() { + int value = getIntBits(); + return "char{0x" + Hex.u2(value) + " / " + value + '}'; + } + + /** {@inheritDoc} */ + public Type getType() { + return Type.CHAR; + } + + /** {@inheritDoc} */ + @Override + public String typeName() { + return "char"; + } + + /** {@inheritDoc} */ + public String toHuman() { + return Integer.toString(getIntBits()); + } + + /** + * Gets the {@code char} value. + * + * @return the value + */ + public char getValue() { + return (char) getIntBits(); + } +} diff --git a/dx/src/com/android/jack/dx/rop/cst/CstDouble.java b/dx/src/com/android/jack/dx/rop/cst/CstDouble.java new file mode 100644 index 00000000..c000e6a4 --- /dev/null +++ b/dx/src/com/android/jack/dx/rop/cst/CstDouble.java @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.rop.cst; + +import com.android.jack.dx.rop.type.Type; +import com.android.jack.dx.util.Hex; + +/** + * Constants of type {@code CONSTANT_Double_info}. + */ +public final class CstDouble + extends CstLiteral64 { + /** {@code non-null;} instance representing {@code 0} */ + public static final CstDouble VALUE_0 = + new CstDouble(Double.doubleToLongBits(0.0)); + + /** {@code non-null;} instance representing {@code 1} */ + public static final CstDouble VALUE_1 = + new CstDouble(Double.doubleToLongBits(1.0)); + + /** + * Makes an instance for the given value. This may (but does not + * necessarily) return an already-allocated instance. + * + * @param bits the {@code double} value as {@code long} bits + */ + public static CstDouble make(long bits) { + /* + * Note: Javadoc notwithstanding, this implementation always + * allocates. + */ + return new CstDouble(bits); + } + + /** + * Constructs an instance. This constructor is private; use {@link #make}. + * + * @param bits the {@code double} value as {@code long} bits + */ + private CstDouble(long bits) { + super(bits); + } + + /** {@inheritDoc} */ + @Override + public String toString() { + long bits = getLongBits(); + return "double{0x" + Hex.u8(bits) + " / " + + Double.longBitsToDouble(bits) + '}'; + } + + /** {@inheritDoc} */ + public Type getType() { + return Type.DOUBLE; + } + + /** {@inheritDoc} */ + @Override + public String typeName() { + return "double"; + } + + /** {@inheritDoc} */ + public String toHuman() { + return Double.toString(Double.longBitsToDouble(getLongBits())); + } + + /** + * Gets the {@code double} value. + * + * @return the value + */ + public double getValue() { + return Double.longBitsToDouble(getLongBits()); + } +} diff --git a/dx/src/com/android/jack/dx/rop/cst/CstEnumRef.java b/dx/src/com/android/jack/dx/rop/cst/CstEnumRef.java new file mode 100644 index 00000000..9be46343 --- /dev/null +++ b/dx/src/com/android/jack/dx/rop/cst/CstEnumRef.java @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.rop.cst; + +import com.android.jack.dx.rop.type.Type; + +/** + * Constant type to represent a reference to a particular constant + * value of an enumerated type. + */ +public final class CstEnumRef extends CstMemberRef { + /** {@code null-ok;} the corresponding field ref, lazily initialized */ + private CstFieldRef fieldRef; + + /** + * Constructs an instance. + * + * @param nat {@code non-null;} the name-and-type; the defining class is derived + * from this + */ + public CstEnumRef(CstNat nat) { + super(new CstType(nat.getFieldType()), nat); + + fieldRef = null; + } + + /** {@inheritDoc} */ + @Override + public String typeName() { + return "enum"; + } + + /** + * {@inheritDoc} + * + * Note: This returns the enumerated type. + */ + public Type getType() { + return getDefiningClass().getClassType(); + } + + /** + * Get a {@link CstFieldRef} that corresponds with this instance. + * + * @return {@code non-null;} the corresponding field reference + */ + public CstFieldRef getFieldRef() { + if (fieldRef == null) { + fieldRef = new CstFieldRef(getDefiningClass(), getNat()); + } + + return fieldRef; + } +} diff --git a/dx/src/com/android/jack/dx/rop/cst/CstFieldRef.java b/dx/src/com/android/jack/dx/rop/cst/CstFieldRef.java new file mode 100644 index 00000000..76e7f25a --- /dev/null +++ b/dx/src/com/android/jack/dx/rop/cst/CstFieldRef.java @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.rop.cst; + +import com.android.jack.dx.rop.type.Type; + +/** + * Constants of type {@code CONSTANT_Fieldref_info}. + */ +public final class CstFieldRef extends CstMemberRef { + /** + * Returns an instance of this class that represents the static + * field which should hold the class corresponding to a given + * primitive type. For example, if given {@link Type#INT}, this + * method returns an instance corresponding to the field + * {@code java.lang.Integer.TYPE}. + * + * @param primitiveType {@code non-null;} the primitive type + * @return {@code non-null;} the corresponding static field + */ + public static CstFieldRef forPrimitiveType(Type primitiveType) { + return new CstFieldRef(CstType.forBoxedPrimitiveType(primitiveType), + CstNat.PRIMITIVE_TYPE_NAT); + } + + /** + * Constructs an instance. + * + * @param definingClass {@code non-null;} the type of the defining class + * @param nat {@code non-null;} the name-and-type + */ + public CstFieldRef(CstType definingClass, CstNat nat) { + super(definingClass, nat); + } + + /** {@inheritDoc} */ + @Override + public String typeName() { + return "field"; + } + + /** + * Returns the type of this field. + * + * @return {@code non-null;} the field's type + */ + public Type getType() { + return getNat().getFieldType(); + } + + /** {@inheritDoc} */ + @Override + protected int compareTo0(Constant other) { + int cmp = super.compareTo0(other); + + if (cmp != 0) { + return cmp; + } + + CstFieldRef otherField = (CstFieldRef) other; + CstString thisDescriptor = getNat().getDescriptor(); + CstString otherDescriptor = otherField.getNat().getDescriptor(); + return thisDescriptor.compareTo(otherDescriptor); + } +} diff --git a/dx/src/com/android/jack/dx/rop/cst/CstFloat.java b/dx/src/com/android/jack/dx/rop/cst/CstFloat.java new file mode 100644 index 00000000..e257fae0 --- /dev/null +++ b/dx/src/com/android/jack/dx/rop/cst/CstFloat.java @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.rop.cst; + +import com.android.jack.dx.rop.type.Type; +import com.android.jack.dx.util.Hex; + +/** + * Constants of type {@code CONSTANT_Float_info}. + */ +public final class CstFloat + extends CstLiteral32 { + /** {@code non-null;} instance representing {@code 0} */ + public static final CstFloat VALUE_0 = make(Float.floatToIntBits(0.0f)); + + /** {@code non-null;} instance representing {@code 1} */ + public static final CstFloat VALUE_1 = make(Float.floatToIntBits(1.0f)); + + /** {@code non-null;} instance representing {@code 2} */ + public static final CstFloat VALUE_2 = make(Float.floatToIntBits(2.0f)); + + /** + * Makes an instance for the given value. This may (but does not + * necessarily) return an already-allocated instance. + * + * @param bits the {@code float} value as {@code int} bits + */ + public static CstFloat make(int bits) { + /* + * Note: Javadoc notwithstanding, this implementation always + * allocates. + */ + return new CstFloat(bits); + } + + /** + * Constructs an instance. This constructor is private; use {@link #make}. + * + * @param bits the {@code float} value as {@code int} bits + */ + private CstFloat(int bits) { + super(bits); + } + + /** {@inheritDoc} */ + @Override + public String toString() { + int bits = getIntBits(); + return "float{0x" + Hex.u4(bits) + " / " + + Float.intBitsToFloat(bits) + '}'; + } + + /** {@inheritDoc} */ + public Type getType() { + return Type.FLOAT; + } + + /** {@inheritDoc} */ + @Override + public String typeName() { + return "float"; + } + + /** {@inheritDoc} */ + public String toHuman() { + return Float.toString(Float.intBitsToFloat(getIntBits())); + } + + /** + * Gets the {@code float} value. + * + * @return the value + */ + public float getValue() { + return Float.intBitsToFloat(getIntBits()); + } +} diff --git a/dx/src/com/android/jack/dx/rop/cst/CstIndexMap.java b/dx/src/com/android/jack/dx/rop/cst/CstIndexMap.java new file mode 100644 index 00000000..3d84830f --- /dev/null +++ b/dx/src/com/android/jack/dx/rop/cst/CstIndexMap.java @@ -0,0 +1,198 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.jack.dx.rop.cst; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +import com.android.jack.dx.dex.file.DexFile; +import com.android.jack.dx.dex.file.IndexedItem; + +/** + * Maps {@link TypedConstant} index offsets from a dex file to those into another. + */ +public class CstIndexMap { + + /** Mapping between index and {@link CstString} value of a dex file.*/ + private final Map stringsIndexMap = new HashMap(); + + /** Mapping between index and {@link CstType} value of a dex file.*/ + private final Map typesIndexMap = new HashMap(); + + /** Mapping between index and {@link CstBaseMethodRef} value of a dex file.*/ + private final Map methodsIndexMap = + new HashMap(); + + /** Mapping between index and {@link CstFieldRef} value of a dex file.*/ + private final Map fieldsIndexMap = + new HashMap(); + + /** + * Keeps string mapping of a dex file. + * @param index String index. + * @param cstString The string. + */ + public void addStringMapping(int index, CstString cstString) { + Integer key = new Integer (index); + assert index >= 0; + assert stringsIndexMap.get(key) == null + || stringsIndexMap.get(key).compareTo(cstString) == 0; + + if (!stringsIndexMap.containsKey(key)) { + stringsIndexMap.put(key, cstString); + } + } + + /** + * Keeps type mapping of a dex file. + * @param index Type index. + * @param cstType The type. + */ + public void addTypeMapping(int index, CstType cstType) { + Integer key = new Integer (index); + assert index >= 0; + assert typesIndexMap.get(key) == null + || typesIndexMap.get(key).compareTo(cstType) == 0; + + if (!typesIndexMap.containsKey(key)) { + typesIndexMap.put(key, cstType); + } + } + + /** + * Keeps method mapping of a dex file. + * @param index Method index. + * @param methodRef The method. + */ + public void addMethodMapping(int index, CstBaseMethodRef methodRef) { + Integer key = new Integer (index); + assert index >= 0; + assert methodsIndexMap.get(key) == null + || methodsIndexMap.get(key).compareTo(methodRef) == 0; + + if (!methodsIndexMap.containsKey(key)) { + methodsIndexMap.put(key, methodRef); + } + } + + /** + * Keeps field mapping of a dex file. + * @param index Field index. + * @param fieldRef The Field. + */ + public void addFieldMapping(int index, CstFieldRef fieldRef) { + Integer key = new Integer (index); + assert index >= 0; + assert fieldsIndexMap.get(key) == null + || fieldsIndexMap.get(key).compareTo(fieldRef) == 0; + + if (!fieldsIndexMap.containsKey(key)) { + fieldsIndexMap.put(key, fieldRef); + } + } + + /** + * Merge all {@link TypedConstant} of one dex file into another. + * @param dex The dex file where values are merged. + */ + public void mergeConstantsIntoDexFile(DexFile dex) { + for (CstString cst : stringsIndexMap.values()) { + dex.getStringIds().intern(cst); + } + + for (CstBaseMethodRef cst : methodsIndexMap.values()) { + dex.getMethodIds().intern(cst); + } + + for (CstFieldRef cst : fieldsIndexMap.values()) { + dex.getFieldIds().intern(cst); + } + + for (CstType cst : typesIndexMap.values()) { + dex.getTypeIds().intern(cst); + } + } + + + /** + * Return the remapped index of a {@code CstString} into {@code file}. + * @param file The file where the remapped index apply to. + * @param index The old index to remap into {@code file}. + * @return The new index remapped into {@code file}. + */ + public int getRemappedCstStringIndex(DexFile file, int index) { + Integer key = new Integer(index); + assert index >= 0; + assert stringsIndexMap.containsKey(key); + IndexedItem indexedItem = file.findItemOrNull(stringsIndexMap.get(key)); + assert indexedItem != null; + return indexedItem.getIndex(); + } + + /** + * Return the remapped index of a {@code CstType} into {@code file}. + * @param file The file where the remapped index apply to. + * @param index The old index to remap into {@code file}. + * @return The new index remapped into {@code file}. + */ + public int getRemappedCstTypeIndex(DexFile file, int index) { + Integer key = new Integer(index); + assert index >= 0; + assert typesIndexMap.containsKey(key); + IndexedItem indexedItem = file.findItemOrNull(typesIndexMap.get(key)); + assert indexedItem != null; + return indexedItem.getIndex(); + } + + /** + * Return the remapped index of a {@code CstBaseMethodRef} into {@code file}. + * @param file The file where the remapped index apply to. + * @param index The old index to remap into {@code file}. + * @return The new index remapped into {@code file}. + */ + public int getRemappedCstBaseMethodRefIndex(DexFile file, int index) { + Integer key = new Integer(index); + assert index >= 0; + assert methodsIndexMap.containsKey(key); + IndexedItem indexedItem = file.findItemOrNull(methodsIndexMap.get(key)); + assert indexedItem != null; + return indexedItem.getIndex(); + } + + /** + * Return the remapped index of a {@code CstFieldRef} into {@code file}. + * @param file The file where the remapped index apply to. + * @param index The old index to remap into {@code file}. + * @return The new index remapped into {@code file}. + */ + public int getRemappedCstFieldRefIndex(DexFile file, int index) { + Integer key = new Integer(index); + assert index >= 0; + assert fieldsIndexMap.containsKey(key); + IndexedItem indexedItem = file.findItemOrNull(fieldsIndexMap.get(key)); + assert indexedItem != null; + return indexedItem.getIndex(); + } + + /** + * Returns all strings that must be remapped. + * @return All string to remap. + */ + public Collection getStrings() { + return (stringsIndexMap.values()); + } +} diff --git a/dx/src/com/android/jack/dx/rop/cst/CstInteger.java b/dx/src/com/android/jack/dx/rop/cst/CstInteger.java new file mode 100644 index 00000000..10ae236d --- /dev/null +++ b/dx/src/com/android/jack/dx/rop/cst/CstInteger.java @@ -0,0 +1,116 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.rop.cst; + +import com.android.jack.dx.rop.type.Type; +import com.android.jack.dx.util.Hex; + +/** + * Constants of type {@code CONSTANT_Integer_info}. + */ +public final class CstInteger + extends CstLiteral32 { + /** {@code non-null;} array of cached instances */ + private static final CstInteger[] cache = new CstInteger[511]; + + /** {@code non-null;} instance representing {@code -1} */ + public static final CstInteger VALUE_M1 = make(-1); + + /** {@code non-null;} instance representing {@code 0} */ + public static final CstInteger VALUE_0 = make(0); + + /** {@code non-null;} instance representing {@code 1} */ + public static final CstInteger VALUE_1 = make(1); + + /** {@code non-null;} instance representing {@code 2} */ + public static final CstInteger VALUE_2 = make(2); + + /** {@code non-null;} instance representing {@code 3} */ + public static final CstInteger VALUE_3 = make(3); + + /** {@code non-null;} instance representing {@code 4} */ + public static final CstInteger VALUE_4 = make(4); + + /** {@code non-null;} instance representing {@code 5} */ + public static final CstInteger VALUE_5 = make(5); + + /** + * Makes an instance for the given value. This may (but does not + * necessarily) return an already-allocated instance. + * + * @param value the {@code int} value + * @return {@code non-null;} the appropriate instance + */ + public static CstInteger make(int value) { + /* + * Note: No need to synchronize, since we don't make any sort + * of guarantee about ==, and it's okay to overwrite existing + * entries too. + */ + int idx = (value & 0x7fffffff) % cache.length; + CstInteger obj = cache[idx]; + + if ((obj != null) && (obj.getValue() == value)) { + return obj; + } + + obj = new CstInteger(value); + cache[idx] = obj; + return obj; + } + + /** + * Constructs an instance. This constructor is private; use {@link #make}. + * + * @param value the {@code int} value + */ + private CstInteger(int value) { + super(value); + } + + /** {@inheritDoc} */ + @Override + public String toString() { + int value = getIntBits(); + return "int{0x" + Hex.u4(value) + " / " + value + '}'; + } + + /** {@inheritDoc} */ + public Type getType() { + return Type.INT; + } + + /** {@inheritDoc} */ + @Override + public String typeName() { + return "int"; + } + + /** {@inheritDoc} */ + public String toHuman() { + return Integer.toString(getIntBits()); + } + + /** + * Gets the {@code int} value. + * + * @return the value + */ + public int getValue() { + return getIntBits(); + } +} diff --git a/dx/src/com/android/jack/dx/rop/cst/CstInterfaceMethodRef.java b/dx/src/com/android/jack/dx/rop/cst/CstInterfaceMethodRef.java new file mode 100644 index 00000000..66c3dd64 --- /dev/null +++ b/dx/src/com/android/jack/dx/rop/cst/CstInterfaceMethodRef.java @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.rop.cst; + +/** + * Constants of type {@code CONSTANT_InterfaceMethodref_info}. + */ +public final class CstInterfaceMethodRef + extends CstBaseMethodRef { + /** + * {@code null-ok;} normal {@link CstMethodRef} that corresponds to this + * instance, if calculated + */ + private CstMethodRef methodRef; + + /** + * Constructs an instance. + * + * @param definingClass {@code non-null;} the type of the defining class + * @param nat {@code non-null;} the name-and-type + */ + public CstInterfaceMethodRef(CstType definingClass, CstNat nat) { + super(definingClass, nat); + methodRef = null; + } + + /** {@inheritDoc} */ + @Override + public String typeName() { + return "ifaceMethod"; + } + + /** + * Gets a normal (non-interface) {@link CstMethodRef} that corresponds to + * this instance. + * + * @return {@code non-null;} an appropriate instance + */ + public CstMethodRef toMethodRef() { + if (methodRef == null) { + methodRef = new CstMethodRef(getDefiningClass(), getNat()); + } + + return methodRef; + } +} diff --git a/dx/src/com/android/jack/dx/rop/cst/CstKnownNull.java b/dx/src/com/android/jack/dx/rop/cst/CstKnownNull.java new file mode 100644 index 00000000..fe471f31 --- /dev/null +++ b/dx/src/com/android/jack/dx/rop/cst/CstKnownNull.java @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.rop.cst; + +import com.android.jack.dx.rop.type.Type; + +/** + * Constant type to represent a known-{@code null} value. + */ +public final class CstKnownNull extends CstLiteralBits { + /** {@code non-null;} unique instance of this class */ + public static final CstKnownNull THE_ONE = new CstKnownNull(); + + /** + * Constructs an instance. This class is not publicly instantiable. Use + * {@link #THE_ONE}. + */ + private CstKnownNull() { + // This space intentionally left blank. + } + + /** {@inheritDoc} */ + @Override + public boolean equals(Object other) { + return (other instanceof CstKnownNull); + } + + /** {@inheritDoc} */ + @Override + public int hashCode() { + return 0x4466757a; + } + + /** {@inheritDoc} */ + @Override + protected int compareTo0(Constant other) { + return 0; + } + + /** {@inheritDoc} */ + @Override + public String toString() { + return "known-null"; + } + + /** {@inheritDoc} */ + public Type getType() { + return Type.KNOWN_NULL; + } + + /** {@inheritDoc} */ + @Override + public String typeName() { + return "known-null"; + } + + /** {@inheritDoc} */ + @Override + public boolean isCategory2() { + return false; + } + + /** {@inheritDoc} */ + public String toHuman() { + return "null"; + } + + /** {@inheritDoc} */ + @Override + public boolean fitsInInt() { + // See comment in getIntBits(). + return true; + } + + /** + * {@inheritDoc} + * + * As "literal bits," a known-null is always represented as the + * number zero. + */ + @Override + public int getIntBits() { + return 0; + } + + /** + * {@inheritDoc} + * + * As "literal bits," a known-null is always represented as the + * number zero. + */ + @Override + public long getLongBits() { + return 0; + } +} diff --git a/dx/src/com/android/jack/dx/rop/cst/CstLiteral32.java b/dx/src/com/android/jack/dx/rop/cst/CstLiteral32.java new file mode 100644 index 00000000..488c0150 --- /dev/null +++ b/dx/src/com/android/jack/dx/rop/cst/CstLiteral32.java @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.rop.cst; + +/** + * Constants which are literal 32-bit values of some sort. + */ +public abstract class CstLiteral32 + extends CstLiteralBits { + /** the value as {@code int} bits */ + private final int bits; + + /** + * Constructs an instance. + * + * @param bits the value as {@code int} bits + */ + /*package*/ CstLiteral32(int bits) { + this.bits = bits; + } + + /** {@inheritDoc} */ + @Override + public final boolean equals(Object other) { + return (other != null) && + (getClass() == other.getClass()) && + bits == ((CstLiteral32) other).bits; + } + + /** {@inheritDoc} */ + @Override + public final int hashCode() { + return bits; + } + + /** {@inheritDoc} */ + @Override + protected int compareTo0(Constant other) { + int otherBits = ((CstLiteral32) other).bits; + + if (bits < otherBits) { + return -1; + } else if (bits > otherBits) { + return 1; + } else { + return 0; + } + } + + /** {@inheritDoc} */ + @Override + public final boolean isCategory2() { + return false; + } + + /** {@inheritDoc} */ + @Override + public final boolean fitsInInt() { + return true; + } + + /** {@inheritDoc} */ + @Override + public final int getIntBits() { + return bits; + } + + /** {@inheritDoc} */ + @Override + public final long getLongBits() { + return (long) bits; + } +} diff --git a/dx/src/com/android/jack/dx/rop/cst/CstLiteral64.java b/dx/src/com/android/jack/dx/rop/cst/CstLiteral64.java new file mode 100644 index 00000000..e72b0895 --- /dev/null +++ b/dx/src/com/android/jack/dx/rop/cst/CstLiteral64.java @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.rop.cst; + +/** + * Constants which are literal 64-bit values of some sort. + */ +public abstract class CstLiteral64 + extends CstLiteralBits { + /** the value as {@code long} bits */ + private final long bits; + + /** + * Constructs an instance. + * + * @param bits the value as {@code long} bits + */ + /*package*/ CstLiteral64(long bits) { + this.bits = bits; + } + + /** {@inheritDoc} */ + @Override + public final boolean equals(Object other) { + return (other != null) && + (getClass() == other.getClass()) && + bits == ((CstLiteral64) other).bits; + } + + /** {@inheritDoc} */ + @Override + public final int hashCode() { + return (int) bits ^ (int) (bits >> 32); + } + + /** {@inheritDoc} */ + @Override + protected int compareTo0(Constant other) { + long otherBits = ((CstLiteral64) other).bits; + + if (bits < otherBits) { + return -1; + } else if (bits > otherBits) { + return 1; + } else { + return 0; + } + } + + /** {@inheritDoc} */ + @Override + public final boolean isCategory2() { + return true; + } + + /** {@inheritDoc} */ + @Override + public final boolean fitsInInt() { + return (int) bits == bits; + } + + /** {@inheritDoc} */ + @Override + public final int getIntBits() { + return (int) bits; + } + + /** {@inheritDoc} */ + @Override + public final long getLongBits() { + return bits; + } +} diff --git a/dx/src/com/android/jack/dx/rop/cst/CstLiteralBits.java b/dx/src/com/android/jack/dx/rop/cst/CstLiteralBits.java new file mode 100644 index 00000000..0615aa75 --- /dev/null +++ b/dx/src/com/android/jack/dx/rop/cst/CstLiteralBits.java @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.rop.cst; + +/** + * Constants which are literal bitwise values of some sort. + */ +public abstract class CstLiteralBits + extends TypedConstant { + /** + * Returns whether or not this instance's value may be accurately + * represented as an {@code int}. The rule is that if there + * is an {@code int} which may be sign-extended to yield this + * instance's value, then this method returns {@code true}. + * Otherwise, it returns {@code false}. + * + * @return {@code true} iff this instance fits in an {@code int} + */ + public abstract boolean fitsInInt(); + + /** + * Gets the value as {@code int} bits. If this instance contains + * more bits than fit in an {@code int}, then this returns only + * the low-order bits. + * + * @return the bits + */ + public abstract int getIntBits(); + + /** + * Gets the value as {@code long} bits. If this instance contains + * fewer bits than fit in a {@code long}, then the result of this + * method is the sign extension of the value. + * + * @return the bits + */ + public abstract long getLongBits(); + + /** + * Returns true if this value can fit in 16 bits with sign-extension. + * + * @return true if the sign-extended lower 16 bits are the same as + * the value. + */ + public boolean fitsIn16Bits() { + if (! fitsInInt()) { + return false; + } + + int bits = getIntBits(); + return (short) bits == bits; + } + + /** + * Returns true if this value can fit in 8 bits with sign-extension. + * + * @return true if the sign-extended lower 8 bits are the same as + * the value. + */ + public boolean fitsIn8Bits() { + if (! fitsInInt()) { + return false; + } + + int bits = getIntBits(); + return (byte) bits == bits; + } +} diff --git a/dx/src/com/android/jack/dx/rop/cst/CstLong.java b/dx/src/com/android/jack/dx/rop/cst/CstLong.java new file mode 100644 index 00000000..58c2b9b2 --- /dev/null +++ b/dx/src/com/android/jack/dx/rop/cst/CstLong.java @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.rop.cst; + +import com.android.jack.dx.rop.type.Type; +import com.android.jack.dx.util.Hex; + +/** + * Constants of type {@code CONSTANT_Long_info}. + */ +public final class CstLong + extends CstLiteral64 { + /** {@code non-null;} instance representing {@code 0} */ + public static final CstLong VALUE_0 = make(0); + + /** {@code non-null;} instance representing {@code 1} */ + public static final CstLong VALUE_1 = make(1); + + /** + * Makes an instance for the given value. This may (but does not + * necessarily) return an already-allocated instance. + * + * @param value the {@code long} value + */ + public static CstLong make(long value) { + /* + * Note: Javadoc notwithstanding, this implementation always + * allocates. + */ + return new CstLong(value); + } + + /** + * Constructs an instance. This constructor is private; use {@link #make}. + * + * @param value the {@code long} value + */ + private CstLong(long value) { + super(value); + } + + /** {@inheritDoc} */ + @Override + public String toString() { + long value = getLongBits(); + return "long{0x" + Hex.u8(value) + " / " + value + '}'; + } + + /** {@inheritDoc} */ + public Type getType() { + return Type.LONG; + } + + /** {@inheritDoc} */ + @Override + public String typeName() { + return "long"; + } + + /** {@inheritDoc} */ + public String toHuman() { + return Long.toString(getLongBits()); + } + + /** + * Gets the {@code long} value. + * + * @return the value + */ + public long getValue() { + return getLongBits(); + } +} diff --git a/dx/src/com/android/jack/dx/rop/cst/CstMemberRef.java b/dx/src/com/android/jack/dx/rop/cst/CstMemberRef.java new file mode 100644 index 00000000..3415946e --- /dev/null +++ b/dx/src/com/android/jack/dx/rop/cst/CstMemberRef.java @@ -0,0 +1,122 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.rop.cst; + +/** + * Constants of type {@code CONSTANT_*ref_info}. + */ +public abstract class CstMemberRef extends TypedConstant { + /** {@code non-null;} the type of the defining class */ + private final CstType definingClass; + + /** {@code non-null;} the name-and-type */ + private final CstNat nat; + + /** + * Constructs an instance. + * + * @param definingClass {@code non-null;} the type of the defining class + * @param nat {@code non-null;} the name-and-type + */ + /*package*/ CstMemberRef(CstType definingClass, CstNat nat) { + if (definingClass == null) { + throw new NullPointerException("definingClass == null"); + } + + if (nat == null) { + throw new NullPointerException("nat == null"); + } + + this.definingClass = definingClass; + this.nat = nat; + } + + /** {@inheritDoc} */ + @Override + public final boolean equals(Object other) { + if ((other == null) || (getClass() != other.getClass())) { + return false; + } + + CstMemberRef otherRef = (CstMemberRef) other; + return definingClass.equals(otherRef.definingClass) && + nat.equals(otherRef.nat); + } + + /** {@inheritDoc} */ + @Override + public final int hashCode() { + return (definingClass.hashCode() * 31) ^ nat.hashCode(); + } + + /** + * {@inheritDoc} + * + *

Note: This implementation just compares the defining + * class and name, and it is up to subclasses to compare the rest + * after calling {@code super.compareTo0()}.

+ */ + @Override + protected int compareTo0(Constant other) { + CstMemberRef otherMember = (CstMemberRef) other; + int cmp = definingClass.compareTo(otherMember.definingClass); + + if (cmp != 0) { + return cmp; + } + + CstString thisName = nat.getName(); + CstString otherName = otherMember.nat.getName(); + + return thisName.compareTo(otherName); + } + + /** {@inheritDoc} */ + @Override + public final String toString() { + return typeName() + '{' + toHuman() + '}'; + } + + /** {@inheritDoc} */ + @Override + public final boolean isCategory2() { + return false; + } + + /** {@inheritDoc} */ + public final String toHuman() { + return definingClass.toHuman() + '.' + nat.toHuman(); + } + + /** + * Gets the type of the defining class. + * + * @return {@code non-null;} the type of defining class + */ + public final CstType getDefiningClass() { + return definingClass; + } + + /** + * Gets the defining name-and-type. + * + * @return {@code non-null;} the name-and-type + */ + public final CstNat getNat() { + return nat; + } +} diff --git a/dx/src/com/android/jack/dx/rop/cst/CstMethodRef.java b/dx/src/com/android/jack/dx/rop/cst/CstMethodRef.java new file mode 100644 index 00000000..3b6d2a48 --- /dev/null +++ b/dx/src/com/android/jack/dx/rop/cst/CstMethodRef.java @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.rop.cst; + +/** + * Constants of type {@code CONSTANT_Methodref_info}. + */ +public final class CstMethodRef + extends CstBaseMethodRef { + /** + * Constructs an instance. + * + * @param definingClass {@code non-null;} the type of the defining class + * @param nat {@code non-null;} the name-and-type + */ + public CstMethodRef(CstType definingClass, CstNat nat) { + super(definingClass, nat); + } + + /** {@inheritDoc} */ + @Override + public String typeName() { + return "method"; + } +} diff --git a/dx/src/com/android/jack/dx/rop/cst/CstNat.java b/dx/src/com/android/jack/dx/rop/cst/CstNat.java new file mode 100644 index 00000000..75c521e5 --- /dev/null +++ b/dx/src/com/android/jack/dx/rop/cst/CstNat.java @@ -0,0 +1,170 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.rop.cst; + +import com.android.jack.dx.rop.type.Type; + +/** + * Constants of type {@code CONSTANT_NameAndType_info}. + */ +public final class CstNat extends Constant { + /** + * {@code non-null;} the instance for name {@code TYPE} and descriptor + * {@code java.lang.Class}, which is useful when dealing with + * wrapped primitives + */ + public static final CstNat PRIMITIVE_TYPE_NAT = + new CstNat(new CstString("TYPE"), + new CstString("Ljava/lang/Class;")); + + /** {@code non-null;} the name */ + private final CstString name; + + /** {@code non-null;} the descriptor (type) */ + private final CstString descriptor; + + /** + * Constructs an instance. + * + * @param name {@code non-null;} the name + * @param descriptor {@code non-null;} the descriptor + */ + public CstNat(CstString name, CstString descriptor) { + if (name == null) { + throw new NullPointerException("name == null"); + } + + if (descriptor == null) { + throw new NullPointerException("descriptor == null"); + } + + this.name = name; + this.descriptor = descriptor; + } + + /** {@inheritDoc} */ + @Override + public boolean equals(Object other) { + if (!(other instanceof CstNat)) { + return false; + } + + CstNat otherNat = (CstNat) other; + return name.equals(otherNat.name) && + descriptor.equals(otherNat.descriptor); + } + + /** {@inheritDoc} */ + @Override + public int hashCode() { + return (name.hashCode() * 31) ^ descriptor.hashCode(); + } + + /** {@inheritDoc} */ + @Override + protected int compareTo0(Constant other) { + CstNat otherNat = (CstNat) other; + int cmp = name.compareTo(otherNat.name); + + if (cmp != 0) { + return cmp; + } + + return descriptor.compareTo(otherNat.descriptor); + } + + /** {@inheritDoc} */ + @Override + public String toString() { + return "nat{" + toHuman() + '}'; + } + + /** {@inheritDoc} */ + @Override + public String typeName() { + return "nat"; + } + + /** {@inheritDoc} */ + @Override + public boolean isCategory2() { + return false; + } + + /** + * Gets the name. + * + * @return {@code non-null;} the name + */ + public CstString getName() { + return name; + } + + /** + * Gets the descriptor. + * + * @return {@code non-null;} the descriptor + */ + public CstString getDescriptor() { + return descriptor; + } + + /** + * Returns an unadorned but human-readable version of the name-and-type + * value. + * + * @return {@code non-null;} the human form + */ + public String toHuman() { + return name.toHuman() + ':' + descriptor.toHuman(); + } + + /** + * Gets the field type corresponding to this instance's descriptor. + * This method is only valid to call if the descriptor in fact describes + * a field (and not a method). + * + * @return {@code non-null;} the field type + */ + public Type getFieldType() { + return Type.intern(descriptor.getString()); + } + + /** + * Gets whether this instance has the name of a standard instance + * initialization method. This is just a convenient shorthand for + * {@code getName().getString().equals("")}. + * + * @return {@code true} iff this is a reference to an + * instance initialization method + */ + public final boolean isInstanceInit() { + return name.getString().equals(""); + } + + /** + * Gets whether this instance has the name of a standard class + * initialization method. This is just a convenient shorthand for + * {@code getName().getString().equals("")}. + * + * @return {@code true} iff this is a reference to an + * instance initialization method + */ + public final boolean isClassInit() { + return name.getString().equals(""); + } +} diff --git a/dx/src/com/android/jack/dx/rop/cst/CstShort.java b/dx/src/com/android/jack/dx/rop/cst/CstShort.java new file mode 100644 index 00000000..688ae6d1 --- /dev/null +++ b/dx/src/com/android/jack/dx/rop/cst/CstShort.java @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.rop.cst; + +import com.android.jack.dx.rop.type.Type; +import com.android.jack.dx.util.Hex; + +/** + * Constants of type {@code short}. + */ +public final class CstShort + extends CstLiteral32 { + /** {@code non-null;} the value {@code 0} as an instance of this class */ + public static final CstShort VALUE_0 = make((short) 0); + + /** + * Makes an instance for the given value. This may (but does not + * necessarily) return an already-allocated instance. + * + * @param value the {@code short} value + * @return {@code non-null;} the appropriate instance + */ + public static CstShort make(short value) { + return new CstShort(value); + } + + /** + * Makes an instance for the given {@code int} value. This + * may (but does not necessarily) return an already-allocated + * instance. + * + * @param value the value, which must be in range for a {@code short} + * @return {@code non-null;} the appropriate instance + */ + public static CstShort make(int value) { + short cast = (short) value; + + if (cast != value) { + throw new IllegalArgumentException("bogus short value: " + + value); + } + + return make(cast); + } + + /** + * Constructs an instance. This constructor is private; use {@link #make}. + * + * @param value the {@code short} value + */ + private CstShort(short value) { + super(value); + } + + /** {@inheritDoc} */ + @Override + public String toString() { + int value = getIntBits(); + return "short{0x" + Hex.u2(value) + " / " + value + '}'; + } + + /** {@inheritDoc} */ + public Type getType() { + return Type.SHORT; + } + + /** {@inheritDoc} */ + @Override + public String typeName() { + return "short"; + } + + /** {@inheritDoc} */ + public String toHuman() { + return Integer.toString(getIntBits()); + } + + /** + * Gets the {@code short} value. + * + * @return the value + */ + public short getValue() { + return (short) getIntBits(); + } +} diff --git a/dx/src/com/android/jack/dx/rop/cst/CstString.java b/dx/src/com/android/jack/dx/rop/cst/CstString.java new file mode 100644 index 00000000..42cf62d4 --- /dev/null +++ b/dx/src/com/android/jack/dx/rop/cst/CstString.java @@ -0,0 +1,375 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.rop.cst; + +import com.android.jack.dx.rop.type.Type; +import com.android.jack.dx.util.ByteArray; +import com.android.jack.dx.util.Hex; + +/** + * Constants of type {@code CONSTANT_Utf8_info} or {@code CONSTANT_String_info}. + */ +public final class CstString extends TypedConstant { + /** + * {@code non-null;} instance representing {@code ""}, that is, the + * empty string + */ + public static final CstString EMPTY_STRING = new CstString(""); + + /** {@code non-null;} the UTF-8 value as a string */ + private final String string; + + /** {@code non-null;} the UTF-8 value as bytes */ + private final ByteArray bytes; + + /** + * Converts a string into its MUTF-8 form. MUTF-8 differs from normal UTF-8 + * in the handling of character '\0' and surrogate pairs. + * + * @param string {@code non-null;} the string to convert + * @return {@code non-null;} the UTF-8 bytes for it + */ + public static byte[] stringToUtf8Bytes(String string) { + int len = string.length(); + byte[] bytes = new byte[len * 3]; // Avoid having to reallocate. + int outAt = 0; + + for (int i = 0; i < len; i++) { + char c = string.charAt(i); + if ((c != 0) && (c < 0x80)) { + bytes[outAt] = (byte) c; + outAt++; + } else if (c < 0x800) { + bytes[outAt] = (byte) (((c >> 6) & 0x1f) | 0xc0); + bytes[outAt + 1] = (byte) ((c & 0x3f) | 0x80); + outAt += 2; + } else { + bytes[outAt] = (byte) (((c >> 12) & 0x0f) | 0xe0); + bytes[outAt + 1] = (byte) (((c >> 6) & 0x3f) | 0x80); + bytes[outAt + 2] = (byte) ((c & 0x3f) | 0x80); + outAt += 3; + } + } + + byte[] result = new byte[outAt]; + System.arraycopy(bytes, 0, result, 0, outAt); + return result; + } + + /** + * Converts an array of UTF-8 bytes into a string. + * + * @param bytes {@code non-null;} the bytes to convert + * @return {@code non-null;} the converted string + */ + public static String utf8BytesToString(ByteArray bytes) { + int length = bytes.size(); + char[] chars = new char[length]; // This is sized to avoid a realloc. + int outAt = 0; + + for (int at = 0; length > 0; /*at*/) { + int v0 = bytes.getUnsignedByte(at); + char out; + switch (v0 >> 4) { + case 0x00: case 0x01: case 0x02: case 0x03: + case 0x04: case 0x05: case 0x06: case 0x07: { + // 0XXXXXXX -- single-byte encoding + length--; + if (v0 == 0) { + // A single zero byte is illegal. + return throwBadUtf8(v0, at); + } + out = (char) v0; + at++; + break; + } + case 0x0c: case 0x0d: { + // 110XXXXX -- two-byte encoding + length -= 2; + if (length < 0) { + return throwBadUtf8(v0, at); + } + int v1 = bytes.getUnsignedByte(at + 1); + if ((v1 & 0xc0) != 0x80) { + return throwBadUtf8(v1, at + 1); + } + int value = ((v0 & 0x1f) << 6) | (v1 & 0x3f); + if ((value != 0) && (value < 0x80)) { + /* + * This should have been represented with + * one-byte encoding. + */ + return throwBadUtf8(v1, at + 1); + } + out = (char) value; + at += 2; + break; + } + case 0x0e: { + // 1110XXXX -- three-byte encoding + length -= 3; + if (length < 0) { + return throwBadUtf8(v0, at); + } + int v1 = bytes.getUnsignedByte(at + 1); + if ((v1 & 0xc0) != 0x80) { + return throwBadUtf8(v1, at + 1); + } + int v2 = bytes.getUnsignedByte(at + 2); + if ((v1 & 0xc0) != 0x80) { + return throwBadUtf8(v2, at + 2); + } + int value = ((v0 & 0x0f) << 12) | ((v1 & 0x3f) << 6) | + (v2 & 0x3f); + if (value < 0x800) { + /* + * This should have been represented with one- or + * two-byte encoding. + */ + return throwBadUtf8(v2, at + 2); + } + out = (char) value; + at += 3; + break; + } + default: { + // 10XXXXXX, 1111XXXX -- illegal + return throwBadUtf8(v0, at); + } + } + chars[outAt] = out; + outAt++; + } + + return new String(chars, 0, outAt); + } + + /** + * Helper for {@link #utf8BytesToString}, which throws the right + * exception for a bogus utf-8 byte. + * + * @param value the byte value + * @param offset the file offset + * @return never + * @throws IllegalArgumentException always thrown + */ + private static String throwBadUtf8(int value, int offset) { + throw new IllegalArgumentException("bad utf-8 byte " + Hex.u1(value) + + " at offset " + Hex.u4(offset)); + } + + /** + * Constructs an instance from a {@code String}. + * + * @param string {@code non-null;} the UTF-8 value as a string + */ + public CstString(String string) { + if (string == null) { + throw new NullPointerException("string == null"); + } + + this.string = string.intern(); + this.bytes = new ByteArray(stringToUtf8Bytes(string)); + } + + /** + * Constructs an instance from some UTF-8 bytes. + * + * @param bytes {@code non-null;} array of the UTF-8 bytes + */ + public CstString(ByteArray bytes) { + if (bytes == null) { + throw new NullPointerException("bytes == null"); + } + + this.bytes = bytes; + this.string = utf8BytesToString(bytes).intern(); + } + + /** {@inheritDoc} */ + @Override + public boolean equals(Object other) { + if (!(other instanceof CstString)) { + return false; + } + + return string.equals(((CstString) other).string); + } + + /** {@inheritDoc} */ + @Override + public int hashCode() { + return string.hashCode(); + } + + /** {@inheritDoc} */ + @Override + protected int compareTo0(Constant other) { + return string.compareTo(((CstString) other).string); + } + + /** {@inheritDoc} */ + @Override + public String toString() { + return "string{\"" + toHuman() + "\"}"; + } + + /** {@inheritDoc} */ + @Override + public String typeName() { + return "utf8"; + } + + /** {@inheritDoc} */ + @Override + public boolean isCategory2() { + return false; + } + + /** {@inheritDoc} */ + public String toHuman() { + int len = string.length(); + StringBuilder sb = new StringBuilder(len * 3 / 2); + + for (int i = 0; i < len; i++) { + char c = string.charAt(i); + if ((c >= ' ') && (c < 0x7f)) { + if ((c == '\'') || (c == '\"') || (c == '\\')) { + sb.append('\\'); + } + sb.append(c); + } else if (c <= 0x7f) { + switch (c) { + case '\n': sb.append("\\n"); break; + case '\r': sb.append("\\r"); break; + case '\t': sb.append("\\t"); break; + default: { + /* + * Represent the character as an octal escape. + * If the next character is a valid octal + * digit, disambiguate by using the + * three-digit form. + */ + char nextChar = + (i < (len - 1)) ? string.charAt(i + 1) : 0; + boolean displayZero = + (nextChar >= '0') && (nextChar <= '7'); + sb.append('\\'); + for (int shift = 6; shift >= 0; shift -= 3) { + char outChar = (char) (((c >> shift) & 7) + '0'); + if ((outChar != '0') || displayZero) { + sb.append(outChar); + displayZero = true; + } + } + if (! displayZero) { + // Ironic edge case: The original value was 0. + sb.append('0'); + } + break; + } + } + } else { + sb.append("\\u"); + sb.append(Character.forDigit(c >> 12, 16)); + sb.append(Character.forDigit((c >> 8) & 0x0f, 16)); + sb.append(Character.forDigit((c >> 4) & 0x0f, 16)); + sb.append(Character.forDigit(c & 0x0f, 16)); + } + } + + return sb.toString(); + } + + /** + * Gets the value as a human-oriented string, surrounded by double + * quotes. + * + * @return {@code non-null;} the quoted string + */ + public String toQuoted() { + return '\"' + toHuman() + '\"'; + } + + /** + * Gets the value as a human-oriented string, surrounded by double + * quotes, but ellipsizes the result if it is longer than the given + * maximum length + * + * @param maxLength {@code >= 5;} the maximum length of the string to return + * @return {@code non-null;} the quoted string + */ + public String toQuoted(int maxLength) { + String string = toHuman(); + int length = string.length(); + String ellipses; + + if (length <= (maxLength - 2)) { + ellipses = ""; + } else { + string = string.substring(0, maxLength - 5); + ellipses = "..."; + } + + return '\"' + string + ellipses + '\"'; + } + + /** + * Gets the UTF-8 value as a string. + * The returned string is always already interned. + * + * @return {@code non-null;} the UTF-8 value as a string + */ + public String getString() { + return string; + } + + /** + * Gets the UTF-8 value as UTF-8 encoded bytes. + * + * @return {@code non-null;} an array of the UTF-8 bytes + */ + public ByteArray getBytes() { + return bytes; + } + + /** + * Gets the size of this instance as UTF-8 code points. That is, + * get the number of bytes in the UTF-8 encoding of this instance. + * + * @return {@code >= 0;} the UTF-8 size + */ + public int getUtf8Size() { + return bytes.size(); + } + + /** + * Gets the size of this instance as UTF-16 code points. That is, + * get the number of 16-bit chars in the UTF-16 encoding of this + * instance. This is the same as the {@code length} of the + * Java {@code String} representation of this instance. + * + * @return {@code >= 0;} the UTF-16 size + */ + public int getUtf16Size() { + return string.length(); + } + + public Type getType() { + return Type.STRING; + } +} diff --git a/dx/src/com/android/jack/dx/rop/cst/CstType.java b/dx/src/com/android/jack/dx/rop/cst/CstType.java new file mode 100644 index 00000000..26d5a070 --- /dev/null +++ b/dx/src/com/android/jack/dx/rop/cst/CstType.java @@ -0,0 +1,250 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.rop.cst; + +import com.android.jack.dx.rop.type.Type; + +import java.util.HashMap; + +/** + * Constants that represent an arbitrary type (reference or primitive). + */ +public final class CstType extends TypedConstant { + /** {@code non-null;} map of interned types */ + private static final HashMap interns = + new HashMap(100); + + /** {@code non-null;} instance corresponding to the class {@code Object} */ + public static final CstType OBJECT = intern(Type.OBJECT); + + /** {@code non-null;} instance corresponding to the class {@code Boolean} */ + public static final CstType BOOLEAN = intern(Type.BOOLEAN_CLASS); + + /** {@code non-null;} instance corresponding to the class {@code Byte} */ + public static final CstType BYTE = intern(Type.BYTE_CLASS); + + /** {@code non-null;} instance corresponding to the class {@code Character} */ + public static final CstType CHARACTER = intern(Type.CHARACTER_CLASS); + + /** {@code non-null;} instance corresponding to the class {@code Double} */ + public static final CstType DOUBLE = intern(Type.DOUBLE_CLASS); + + /** {@code non-null;} instance corresponding to the class {@code Float} */ + public static final CstType FLOAT = intern(Type.FLOAT_CLASS); + + /** {@code non-null;} instance corresponding to the class {@code Long} */ + public static final CstType LONG = intern(Type.LONG_CLASS); + + /** {@code non-null;} instance corresponding to the class {@code Integer} */ + public static final CstType INTEGER = intern(Type.INTEGER_CLASS); + + /** {@code non-null;} instance corresponding to the class {@code Short} */ + public static final CstType SHORT = intern(Type.SHORT_CLASS); + + /** {@code non-null;} instance corresponding to the class {@code Void} */ + public static final CstType VOID = intern(Type.VOID_CLASS); + + /** {@code non-null;} instance corresponding to the type {@code boolean[]} */ + public static final CstType BOOLEAN_ARRAY = intern(Type.BOOLEAN_ARRAY); + + /** {@code non-null;} instance corresponding to the type {@code byte[]} */ + public static final CstType BYTE_ARRAY = intern(Type.BYTE_ARRAY); + + /** {@code non-null;} instance corresponding to the type {@code char[]} */ + public static final CstType CHAR_ARRAY = intern(Type.CHAR_ARRAY); + + /** {@code non-null;} instance corresponding to the type {@code double[]} */ + public static final CstType DOUBLE_ARRAY = intern(Type.DOUBLE_ARRAY); + + /** {@code non-null;} instance corresponding to the type {@code float[]} */ + public static final CstType FLOAT_ARRAY = intern(Type.FLOAT_ARRAY); + + /** {@code non-null;} instance corresponding to the type {@code long[]} */ + public static final CstType LONG_ARRAY = intern(Type.LONG_ARRAY); + + /** {@code non-null;} instance corresponding to the type {@code int[]} */ + public static final CstType INT_ARRAY = intern(Type.INT_ARRAY); + + /** {@code non-null;} instance corresponding to the type {@code short[]} */ + public static final CstType SHORT_ARRAY = intern(Type.SHORT_ARRAY); + + /** {@code non-null;} the underlying type */ + private final Type type; + + /** + * {@code null-ok;} the type descriptor corresponding to this instance, if + * calculated + */ + private CstString descriptor; + + /** + * Returns an instance of this class that represents the wrapper + * class corresponding to a given primitive type. For example, if + * given {@link Type#INT}, this method returns the class reference + * {@code java.lang.Integer}. + * + * @param primitiveType {@code non-null;} the primitive type + * @return {@code non-null;} the corresponding wrapper class + */ + public static CstType forBoxedPrimitiveType(Type primitiveType) { + switch (primitiveType.getBasicType()) { + case Type.BT_BOOLEAN: return BOOLEAN; + case Type.BT_BYTE: return BYTE; + case Type.BT_CHAR: return CHARACTER; + case Type.BT_DOUBLE: return DOUBLE; + case Type.BT_FLOAT: return FLOAT; + case Type.BT_INT: return INTEGER; + case Type.BT_LONG: return LONG; + case Type.BT_SHORT: return SHORT; + case Type.BT_VOID: return VOID; + } + + throw new IllegalArgumentException("not primitive: " + primitiveType); + } + + /** + * Returns an interned instance of this class for the given type. + * + * @param type {@code non-null;} the underlying type + * @return {@code non-null;} an appropriately-constructed instance + */ + public static CstType intern(Type type) { + synchronized (interns) { + CstType cst = interns.get(type); + + if (cst == null) { + cst = new CstType(type); + interns.put(type, cst); + } + + return cst; + } + } + + /** + * Constructs an instance. + * + * @param type {@code non-null;} the underlying type + */ + public CstType(Type type) { + if (type == null) { + throw new NullPointerException("type == null"); + } + + if (type == type.KNOWN_NULL) { + throw new UnsupportedOperationException( + "KNOWN_NULL is not representable"); + } + + this.type = type; + this.descriptor = null; + } + + /** {@inheritDoc} */ + @Override + public boolean equals(Object other) { + if (!(other instanceof CstType)) { + return false; + } + + return type == ((CstType) other).type; + } + + /** {@inheritDoc} */ + @Override + public int hashCode() { + return type.hashCode(); + } + + /** {@inheritDoc} */ + @Override + protected int compareTo0(Constant other) { + String thisDescriptor = type.getDescriptor(); + String otherDescriptor = ((CstType) other).type.getDescriptor(); + return thisDescriptor.compareTo(otherDescriptor); + } + + /** {@inheritDoc} */ + @Override + public String toString() { + return "type{" + toHuman() + '}'; + } + + /** {@inheritDoc} */ + public Type getType() { + return Type.CLASS; + } + + /** {@inheritDoc} */ + @Override + public String typeName() { + return "type"; + } + + /** {@inheritDoc} */ + @Override + public boolean isCategory2() { + return false; + } + + /** {@inheritDoc} */ + public String toHuman() { + return type.toHuman(); + } + + /** + * Gets the underlying type (as opposed to the type corresponding + * to this instance as a constant, which is always + * {@code Class}). + * + * @return {@code non-null;} the type corresponding to the name + */ + public Type getClassType() { + return type; + } + + /** + * Gets the type descriptor for this instance. + * + * @return {@code non-null;} the descriptor + */ + public CstString getDescriptor() { + if (descriptor == null) { + descriptor = new CstString(type.getDescriptor()); + } + + return descriptor; + } + + /** + * Returns a human readable package name for this type, like "java.util". + * If this is an array type, this returns the package name of the array's + * component type. If this is a primitive type, this returns "default". + */ + public String getPackageName() { + // descriptor is a string like "[[Ljava/util/String;" + String descriptor = getDescriptor().getString(); + int lastSlash = descriptor.lastIndexOf('/'); + int lastLeftSquare = descriptor.lastIndexOf('['); // -1 unless this is an array + if (lastSlash == -1) { + return "default"; + } else { + // +2 to skip the '[' and the 'L' prefix + return descriptor.substring(lastLeftSquare + 2, lastSlash).replace('/', '.'); + } + } +} diff --git a/dx/src/com/android/jack/dx/rop/cst/StdConstantPool.java b/dx/src/com/android/jack/dx/rop/cst/StdConstantPool.java new file mode 100644 index 00000000..ab16a2ef --- /dev/null +++ b/dx/src/com/android/jack/dx/rop/cst/StdConstantPool.java @@ -0,0 +1,139 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.rop.cst; + +import com.android.jack.dx.util.ExceptionWithContext; +import com.android.jack.dx.util.Hex; +import com.android.jack.dx.util.MutabilityControl; + +/** + * Standard implementation of {@link ConstantPool}, which directly stores + * an array of {@link Constant} objects and can be made immutable. + */ +public final class StdConstantPool + extends MutabilityControl implements ConstantPool { + /** {@code non-null;} array of entries */ + private final Constant[] entries; + + /** + * Constructs an instance. All indices initially contain {@code null}. + * + * @param size the size of the pool; this corresponds to the + * class file field {@code constant_pool_count}, and is in fact + * always at least one more than the actual size of the constant pool, + * as element {@code 0} is always invalid. + */ + public StdConstantPool(int size) { + super(size > 1); + + if (size < 1) { + throw new IllegalArgumentException("size < 1"); + } + + entries = new Constant[size]; + } + + /** {@inheritDoc} */ + public int size() { + return entries.length; + } + + /** {@inheritDoc} */ + public Constant getOrNull(int n) { + try { + return entries[n]; + } catch (IndexOutOfBoundsException ex) { + // Translate the exception. + return throwInvalid(n); + } + } + + /** {@inheritDoc} */ + public Constant get0Ok(int n) { + if (n == 0) { + return null; + } + + return get(n); + } + + /** {@inheritDoc} */ + public Constant get(int n) { + try { + Constant result = entries[n]; + + if (result == null) { + throwInvalid(n); + } + + return result; + } catch (IndexOutOfBoundsException ex) { + // Translate the exception. + return throwInvalid(n); + } + } + + /** + * Sets the entry at the given index. + * + * @param n {@code >= 1, < size();} which entry + * @param cst {@code null-ok;} the constant to store + */ + public void set(int n, Constant cst) { + throwIfImmutable(); + + boolean cat2 = (cst != null) && cst.isCategory2(); + + if (n < 1) { + throw new IllegalArgumentException("n < 1"); + } + + if (cat2) { + // Storing a category-2 entry nulls out the next index. + if (n == (entries.length - 1)) { + throw new IllegalArgumentException("(n == size - 1) && " + + "cst.isCategory2()"); + } + entries[n + 1] = null; + } + + if ((cst != null) && (entries[n] == null)) { + /* + * Overwriting the second half of a category-2 entry nulls out + * the first half. + */ + Constant prev = entries[n - 1]; + if ((prev != null) && prev.isCategory2()) { + entries[n - 1] = null; + } + } + + entries[n] = cst; + } + + /** + * Throws the right exception for an invalid cpi. + * + * @param idx the bad cpi + * @return never + * @throws ExceptionWithContext always thrown + */ + private static Constant throwInvalid(int idx) { + throw new ExceptionWithContext("invalid constant pool index " + + Hex.u2(idx)); + } +} diff --git a/dx/src/com/android/jack/dx/rop/cst/TypedConstant.java b/dx/src/com/android/jack/dx/rop/cst/TypedConstant.java new file mode 100644 index 00000000..b7b236eb --- /dev/null +++ b/dx/src/com/android/jack/dx/rop/cst/TypedConstant.java @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.rop.cst; + +import com.android.jack.dx.rop.type.TypeBearer; + +/** + * Base class for constants which implement {@link TypeBearer}. + */ +public abstract class TypedConstant + extends Constant implements TypeBearer { + /** + * {@inheritDoc} + * + * This implementation always returns {@code this}. + */ + public final TypeBearer getFrameType() { + return this; + } + + /** {@inheritDoc} */ + public final int getBasicType() { + return getType().getBasicType(); + } + + /** {@inheritDoc} */ + public final int getBasicFrameType() { + return getType().getBasicFrameType(); + } + + /** {@inheritDoc} */ + public final boolean isConstant() { + return true; + } +} diff --git a/dx/src/com/android/jack/dx/rop/cst/Zeroes.java b/dx/src/com/android/jack/dx/rop/cst/Zeroes.java new file mode 100644 index 00000000..00842cbf --- /dev/null +++ b/dx/src/com/android/jack/dx/rop/cst/Zeroes.java @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.rop.cst; + +import com.android.jack.dx.rop.type.Type; + +/** + * Utility for turning types into zeroes. + */ +public final class Zeroes { + /** + * This class is uninstantiable. + */ + private Zeroes() { + // This space intentionally left blank. + } + + /** + * Gets the "zero" (or {@code null}) value for the given type. + * + * @param type {@code non-null;} the type in question + * @return {@code non-null;} its "zero" value + */ + public static Constant zeroFor(Type type) { + switch (type.getBasicType()) { + case Type.BT_BOOLEAN: return CstBoolean.VALUE_FALSE; + case Type.BT_BYTE: return CstByte.VALUE_0; + case Type.BT_CHAR: return CstChar.VALUE_0; + case Type.BT_DOUBLE: return CstDouble.VALUE_0; + case Type.BT_FLOAT: return CstFloat.VALUE_0; + case Type.BT_INT: return CstInteger.VALUE_0; + case Type.BT_LONG: return CstLong.VALUE_0; + case Type.BT_SHORT: return CstShort.VALUE_0; + case Type.BT_OBJECT: return CstKnownNull.THE_ONE; + default: { + throw new UnsupportedOperationException("no zero for type: " + + type.toHuman()); + } + } + } +} diff --git a/dx/src/com/android/jack/dx/rop/cst/package.html b/dx/src/com/android/jack/dx/rop/cst/package.html new file mode 100644 index 00000000..dc4ec62f --- /dev/null +++ b/dx/src/com/android/jack/dx/rop/cst/package.html @@ -0,0 +1,9 @@ + +

Interfaces and implementation of things related to the constant pool.

+ +

PACKAGES USED: +

    +
  • com.android.jack.dx.rop.type
  • +
  • com.android.jack.dx.util
  • +
+ diff --git a/dx/src/com/android/jack/dx/rop/package-info.java b/dx/src/com/android/jack/dx/rop/package-info.java new file mode 100644 index 00000000..8909146b --- /dev/null +++ b/dx/src/com/android/jack/dx/rop/package-info.java @@ -0,0 +1,200 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.rop; + +/** + *

An Introduction to Rop Form

+ * + * This package contains classes associated with dx's {@code Rop} + * intermediate form.

+ * + * The Rop form is intended to represent the instructions and the control-flow + * graph in a reasonably programmatically useful form while closely mirroring + * the dex instruction set.

+ * + *

Key Classes

+ * + *
    + *
  • {@link RopMethod}, the representation of an individual method + *
  • {@link BasicBlock} and its per-method container, {@link BasicBlockList}, + * the representation of control flow elements. + *
  • {@link Insn} and its subclasses along with its per-basic block + * container {@link InsnList}. {@code Insn} instances represent + * individual instructions in the abstract register machine. + *
  • {@link RegisterSpec} and its container {@link RegisterSpecList}. A + * register spec encodes register number, register width, type information, + * and potentially local variable information as well for instruction sources + * and results. + *
  • {@link Rop} instances represent opcodes in the abstract machine. Many + * {@code Rop} instances are singletons defined in static fields in + * {@link Rops}. The rest are constructed dynamically using static methods + * in {@code Rops} + *
  • {@link RegOps} lists numeric constants for the opcodes + *
  • {@link Constant} and its subclasses represent constant data values + * that opcodes may refer to. + *
  • {@link Type} instances represent the core data types that can be + * handled by the abstract machine. + *
  • The {@link TypeBearer} interface is implemented by classes that + * represent a core data type, but may also have secondary information + * (such as constant value) associated with them. + *
      + * + *

      Control-Flow Graph

      + * + * Each method is separated into a list of basic blocks. For the most part, + * basic blocks are referred to by a positive integer + * {@link BasicBlock#getLabel label}, which is always unique per method. The + * label value is typically derived from a bytecode address from the source + * bytecode. Blocks that don't originate directly from source bytecode have + * labels generated for them in a mostly arbitrary order.

      + * + * Blocks are referred to by their label, for the most part, because + * {@code BasicBlock} instances are immutable and thus any modification to + * the control flow graph or the instruction list results in replacement + * instances (with identical labels) being created.

      + * + * A method has a single {@link RopMethod#getFirstLabel entry block} and 0 + * to N {@link RopMethod#getExitPredecessors exit predecessor blocks} which + * will return. All blocks that are not the entry block will have at least + * one predecessor (or are unreachable and should be removed from the block + * list). All blocks that are not exit predecessors must have at least one + * successor.

      + * + * Since all blocks must branch, all blocks must have, as their final + * instruction, an instruction whose opcode has a {@link Rop#getBranchingness + * branchingness} other than {@link Rop.BRANCH_NONE}. Furthermore, branching + * instructions may only be the final instruction in any basic block. If + * no other terminating opcode is appropriate, use a {@link Rops#GOTO GOTO}.

      + * + * Typically a block will have a {@link BasicBlock#getPrimarySuccessor + * primary successor} which distinguishes a particular control flow path. + * For {Rops#isCallLike}call or call-like} opcodes, this is the path taken + * in the non-exceptional case, where all other successors represent + * various exception paths. For comparison operators such as + * {@link Rops#IF_EQZ_INT}, the primary successor represents the path taken + * if the condition evaluates to false. For {@link SwitchInsn switch + * instructions}, the primary successor is the default case.

      + * + * A basic block's successor list is ordered and may refer to unique labels + * multiple times. For example, if a switch statement contains multiple case + * statements for the same code path, a single basic block will likely + * appear in the successor list multiple times. In general, the + * significance of the successor list's order (like the significance of + * the primary successor) is a property of the final instruction of the basic + * block. A basic block containing a {@link ThrowingInsn}, for example, has + * its successor list in an order identical to the + * {@link ThrowingInsn#getCatches} instruction's catches list, with the + * primary successor (the no-exception case) listed at the end. + * + * It is legal for a basic block to have no primary successor. An obvious + * example of this is a block that terminates in a {@link Rops#THROW throw} + * instruction where a catch block exists inside the current method for that + * exception class. Since the only possible path is the exception path, only + * the exception path (which cannot be a primary successor) is a successor. + * An example of this is shown in {@code dx/tests/092-ssa-cfg-edge-cases}. + * + *

      Rop Instructions

      + * + *

      move-result and move-result-pseudo

      + * + * An instruction that may throw an exception may not specify a result. This + * is necessary because the result register will not be assigned to if an + * exception occurs while processing said instruction and a result assignment + * may not occur. Since result assignments only occur in the non-exceptional + * case, the result assignments for throwing instructions can be said to occur + * at the beginning of the primary successor block rather than at the end of + * the current block. The Rop form represents the result assignments this way. + * Throwing instructions may not directly specify results. Instead, result + * assignments are represented by {@link + * Rops#MOVE_RESULT move-result} or {@link Rops#MOVE_RESULT_PSEUDO + * move-result-pseudo} instructions at the top of the primary successor block. + * + * Only a single {@code move-result} or {@code move-result-pseudo} + * may exist in any block and it must be exactly the first instruction in the + * block. + * + * A {@code move-result} instruction is used for the results of call-like + * instructions. If the value produced by a {@code move-result} is not + * used by the method, it may be eliminated as dead code. + * + * A {@code move-result-pseudo} instruction is used for the results of + * non-call-like throwing instructions. It may never be considered dead code + * since the final dex instruction will always indicate a result register. + * If a required {@code move-result-pseudo} instruction is not found + * during conversion to dex bytecode, an exception will be thrown. + * + *

      move-exception

      + * + * A {@link RegOps.MOVE_EXCEPTION move-exception} instruction may appear at + * the start of a catch block, and represents the obtaining of the thrown + * exception instance. It may only occur as the first instruction in a + * basic block, and any basic block in which it occurs must be reachable only + * as an exception successor. + * + *

      move-param

      + * + * A {@link RegOps.MOVE_PARAM move-param} instruction represents a method + * parameter. Every {@code move-param} instruction is a + * {@link PlainCstInsn}. The index of the method parameter they refer to is + * carried as the {@link CstInteger integer constant} associated with the + * instruction. + * + * Any number of {@code move-param} instructions referring to the same + * parameter index may be included in a method's instruction lists. They + * have no restrictions on placement beyond those of any other + * {@link Rop.BRANCH_NONE} instruction. Note that the SSA optimizer arranges the + * parameter assignments to align with the dex bytecode calling conventions. + * With parameter assignments so arranged, the + * {@link com.android.jack.dx.dex.code.RopTranslator} sees Rop {@code move-param} + * instructions as unnecessary in dex form and eliminates them. + * + *

      mark-local

      + * + * A {@link RegOps.MARK_LOCAL mark-local} instruction indicates that a local + * variable becomes live in a specified register specified register for the + * purposes of debug information. A {@code mark-local} instruction has + * a single source (the register which will now be considered a local variable) + * and no results. The instruction has no side effect.

      + * + * In a typical case, a local variable's lifetime begins with an + * assignment. The local variable whose information is present in a result's + * {@link RegisterSpec#getLocalItem LocalItem} is considered to begin (or move + * between registers) when the instruction is executed.

      + * + * However, sometimes a local variable can begin its life or move without + * an assignment occurring. A common example of this is occurs in the Rop + * representation of the following code:

      + * + *

      + * try {
      + *     Object foo = null;
      + *     foo = new Object();
      + * } catch (Throwable ex) { }
      + * 
      + * + * An object's initialization occurs in two steps. First, a + * {@code new-instance} instruction is executed, whose result is stored in a + * register. However, that register can not yet be considered to contain + * "foo". That's because the instance's constructor method must be called + * via an {@code invoke} instruction. The constructor method, however, may + * throw an exception. And if an exception occurs, then "foo" should remain + * null. So "foo" becomes the value of the result of the {@code new-instance} + * instruction after the (void) constructor method is invoked and + * returns successfully. In such a case, a {@code mark-local} will + * typically occur at the beginning of the primary successor block following + * the invocation to the constructor. + */ diff --git a/dx/src/com/android/jack/dx/rop/type/Prototype.java b/dx/src/com/android/jack/dx/rop/type/Prototype.java new file mode 100644 index 00000000..547f1ca1 --- /dev/null +++ b/dx/src/com/android/jack/dx/rop/type/Prototype.java @@ -0,0 +1,400 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.rop.type; + +import java.util.HashMap; + +/** + * Representation of a method descriptor. Instances of this class are + * generally interned and may be usefully compared with each other + * using {@code ==}. + */ +public final class Prototype implements Comparable { + /** {@code non-null;} intern table mapping string descriptors to instances */ + private static final HashMap internTable = + new HashMap(500); + + /** {@code non-null;} method descriptor */ + private final String descriptor; + + /** {@code non-null;} return type */ + private final Type returnType; + + /** {@code non-null;} list of parameter types */ + private final StdTypeList parameterTypes; + + /** {@code null-ok;} list of parameter frame types, if calculated */ + private StdTypeList parameterFrameTypes; + + /** + * Returns the unique instance corresponding to the + * given method descriptor. See vmspec-2 sec4.3.3 for details on the + * field descriptor syntax. + * + * @param descriptor {@code non-null;} the descriptor + * @return {@code non-null;} the corresponding instance + * @throws IllegalArgumentException thrown if the descriptor has + * invalid syntax + */ + public static Prototype intern(String descriptor) { + if (descriptor == null) { + throw new NullPointerException("descriptor == null"); + } + + Prototype result; + synchronized (internTable) { + result = internTable.get(descriptor); + } + if (result != null) { + return result; + } + + Type[] params = makeParameterArray(descriptor); + int paramCount = 0; + int at = 1; + + for (;;) { + int startAt = at; + char c = descriptor.charAt(at); + if (c == ')') { + at++; + break; + } + + // Skip array markers. + while (c == '[') { + at++; + c = descriptor.charAt(at); + } + + if (c == 'L') { + // It looks like the start of a class name; find the end. + int endAt = descriptor.indexOf(';', at); + if (endAt == -1) { + throw new IllegalArgumentException("bad descriptor"); + } + at = endAt + 1; + } else { + at++; + } + + params[paramCount] = + Type.intern(descriptor.substring(startAt, at)); + paramCount++; + } + + Type returnType = Type.internReturnType(descriptor.substring(at)); + StdTypeList parameterTypes = new StdTypeList(paramCount); + + for (int i = 0; i < paramCount; i++) { + parameterTypes.set(i, params[i]); + } + + result = new Prototype(descriptor, returnType, parameterTypes); + return putIntern(result); + } + + /** + * Helper for {@link #intern} which returns an empty array to + * populate with parsed parameter types, and which also ensures + * that there is a '(' at the start of the descriptor and a + * single ')' somewhere before the end. + * + * @param descriptor {@code non-null;} the descriptor string + * @return {@code non-null;} array large enough to hold all parsed parameter + * types, but which is likely actually larger than needed + */ + private static Type[] makeParameterArray(String descriptor) { + int length = descriptor.length(); + + if (descriptor.charAt(0) != '(') { + throw new IllegalArgumentException("bad descriptor"); + } + + /* + * This is a cheesy way to establish an upper bound on the + * number of parameters: Just count capital letters. + */ + int closeAt = 0; + int maxParams = 0; + for (int i = 1; i < length; i++) { + char c = descriptor.charAt(i); + if (c == ')') { + closeAt = i; + break; + } + if ((c >= 'A') && (c <= 'Z')) { + maxParams++; + } + } + + if ((closeAt == 0) || (closeAt == (length - 1))) { + throw new IllegalArgumentException("bad descriptor"); + } + + if (descriptor.indexOf(')', closeAt + 1) != -1) { + throw new IllegalArgumentException("bad descriptor"); + } + + return new Type[maxParams]; + } + + /** + * Interns an instance, adding to the descriptor as necessary based + * on the given definer, name, and flags. For example, an init + * method has an uninitialized object of type {@code definer} + * as its first argument. + * + * @param descriptor {@code non-null;} the descriptor string + * @param definer {@code non-null;} class the method is defined on + * @param isStatic whether this is a static method + * @param isInit whether this is an init method + * @return {@code non-null;} the interned instance + */ + public static Prototype intern(String descriptor, Type definer, + boolean isStatic, boolean isInit) { + Prototype base = intern(descriptor); + + if (isStatic) { + return base; + } + + if (isInit) { + definer = definer.asUninitialized(Integer.MAX_VALUE); + } + + return base.withFirstParameter(definer); + } + + /** + * Interns an instance which consists of the given number of + * {@code int}s along with the given return type + * + * @param returnType {@code non-null;} the return type + * @param count {@code > 0;} the number of elements in the prototype + * @return {@code non-null;} the interned instance + */ + public static Prototype internInts(Type returnType, int count) { + // Make the descriptor... + + StringBuffer sb = new StringBuffer(100); + + sb.append('('); + + for (int i = 0; i < count; i++) { + sb.append('I'); + } + + sb.append(')'); + sb.append(returnType.getDescriptor()); + + // ...and intern it. + return intern(sb.toString()); + } + + /** + * Constructs an instance. This is a private constructor; use one + * of the public static methods to get instances. + * + * @param descriptor {@code non-null;} the descriptor string + */ + private Prototype(String descriptor, Type returnType, + StdTypeList parameterTypes) { + if (descriptor == null) { + throw new NullPointerException("descriptor == null"); + } + + if (returnType == null) { + throw new NullPointerException("returnType == null"); + } + + if (parameterTypes == null) { + throw new NullPointerException("parameterTypes == null"); + } + + this.descriptor = descriptor; + this.returnType = returnType; + this.parameterTypes = parameterTypes; + this.parameterFrameTypes = null; + } + + /** {@inheritDoc} */ + @Override + public boolean equals(Object other) { + if (this == other) { + /* + * Since externally-visible instances are interned, this + * check helps weed out some easy cases. + */ + return true; + } + + if (!(other instanceof Prototype)) { + return false; + } + + return descriptor.equals(((Prototype) other).descriptor); + } + + /** {@inheritDoc} */ + @Override + public int hashCode() { + return descriptor.hashCode(); + } + + /** {@inheritDoc} */ + public int compareTo(Prototype other) { + if (this == other) { + return 0; + } + + /* + * The return type is the major order, and then args in order, + * and then the shorter list comes first (similar to string + * sorting). + */ + + int result = returnType.compareTo(other.returnType); + + if (result != 0) { + return result; + } + + int thisSize = parameterTypes.size(); + int otherSize = other.parameterTypes.size(); + int size = Math.min(thisSize, otherSize); + + for (int i = 0; i < size; i++) { + Type thisType = parameterTypes.get(i); + Type otherType = other.parameterTypes.get(i); + + result = thisType.compareTo(otherType); + + if (result != 0) { + return result; + } + } + + if (thisSize < otherSize) { + return -1; + } else if (thisSize > otherSize) { + return 1; + } else { + return 0; + } + } + + /** {@inheritDoc} */ + @Override + public String toString() { + return descriptor; + } + + /** + * Gets the descriptor string. + * + * @return {@code non-null;} the descriptor + */ + public String getDescriptor() { + return descriptor; + } + + /** + * Gets the return type. + * + * @return {@code non-null;} the return type + */ + public Type getReturnType() { + return returnType; + } + + /** + * Gets the list of parameter types. + * + * @return {@code non-null;} the list of parameter types + */ + public StdTypeList getParameterTypes() { + return parameterTypes; + } + + /** + * Gets the list of frame types corresponding to the list of parameter + * types. The difference between the two lists (if any) is that all + * "intlike" types (see {@link Type#isIntlike}) are replaced by + * {@link Type#INT}. + * + * @return {@code non-null;} the list of parameter frame types + */ + public StdTypeList getParameterFrameTypes() { + if (parameterFrameTypes == null) { + int sz = parameterTypes.size(); + StdTypeList list = new StdTypeList(sz); + boolean any = false; + for (int i = 0; i < sz; i++) { + Type one = parameterTypes.get(i); + if (one.isIntlike()) { + any = true; + one = Type.INT; + } + list.set(i, one); + } + parameterFrameTypes = any ? list : parameterTypes; + } + + return parameterFrameTypes; + } + + /** + * Returns a new interned instance, which is the same as this instance, + * except that it has an additional parameter prepended to the original's + * argument list. + * + * @param param {@code non-null;} the new first parameter + * @return {@code non-null;} an appropriately-constructed instance + */ + public Prototype withFirstParameter(Type param) { + String newDesc = "(" + param.getDescriptor() + descriptor.substring(1); + StdTypeList newParams = parameterTypes.withFirst(param); + + newParams.setImmutable(); + + Prototype result = + new Prototype(newDesc, returnType, newParams); + + return putIntern(result); + } + + /** + * Puts the given instance in the intern table if it's not already + * there. If a conflicting value is already in the table, then leave it. + * Return the interned value. + * + * @param desc {@code non-null;} instance to make interned + * @return {@code non-null;} the actual interned object + */ + private static Prototype putIntern(Prototype desc) { + synchronized (internTable) { + String descriptor = desc.getDescriptor(); + Prototype already = internTable.get(descriptor); + if (already != null) { + return already; + } + internTable.put(descriptor, desc); + return desc; + } + } +} diff --git a/dx/src/com/android/jack/dx/rop/type/StdTypeList.java b/dx/src/com/android/jack/dx/rop/type/StdTypeList.java new file mode 100644 index 00000000..fe8242eb --- /dev/null +++ b/dx/src/com/android/jack/dx/rop/type/StdTypeList.java @@ -0,0 +1,407 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.rop.type; + +import com.android.jack.dx.util.FixedSizeList; + +/** + * Standard implementation of {@link TypeList}. + */ +public final class StdTypeList + extends FixedSizeList implements TypeList { + /** {@code non-null;} no-element instance */ + public static final StdTypeList EMPTY = new StdTypeList(0); + + /** {@code non-null;} the list {@code [int]} */ + public static final StdTypeList INT = StdTypeList.make(Type.INT); + + /** {@code non-null;} the list {@code [long]} */ + public static final StdTypeList LONG = StdTypeList.make(Type.LONG); + + /** {@code non-null;} the list {@code [float]} */ + public static final StdTypeList FLOAT = StdTypeList.make(Type.FLOAT); + + /** {@code non-null;} the list {@code [double]} */ + public static final StdTypeList DOUBLE = StdTypeList.make(Type.DOUBLE); + + /** {@code non-null;} the list {@code [Object]} */ + public static final StdTypeList OBJECT = StdTypeList.make(Type.OBJECT); + + /** {@code non-null;} the list {@code [ReturnAddress]} */ + public static final StdTypeList RETURN_ADDRESS + = StdTypeList.make(Type.RETURN_ADDRESS); + + /** {@code non-null;} the list {@code [Throwable]} */ + public static final StdTypeList THROWABLE = + StdTypeList.make(Type.THROWABLE); + + /** {@code non-null;} the list {@code [int, int]} */ + public static final StdTypeList INT_INT = + StdTypeList.make(Type.INT, Type.INT); + + /** {@code non-null;} the list {@code [long, long]} */ + public static final StdTypeList LONG_LONG = + StdTypeList.make(Type.LONG, Type.LONG); + + /** {@code non-null;} the list {@code [float, float]} */ + public static final StdTypeList FLOAT_FLOAT = + StdTypeList.make(Type.FLOAT, Type.FLOAT); + + /** {@code non-null;} the list {@code [double, double]} */ + public static final StdTypeList DOUBLE_DOUBLE = + StdTypeList.make(Type.DOUBLE, Type.DOUBLE); + + /** {@code non-null;} the list {@code [Object, Object]} */ + public static final StdTypeList OBJECT_OBJECT = + StdTypeList.make(Type.OBJECT, Type.OBJECT); + + /** {@code non-null;} the list {@code [int, Object]} */ + public static final StdTypeList INT_OBJECT = + StdTypeList.make(Type.INT, Type.OBJECT); + + /** {@code non-null;} the list {@code [long, Object]} */ + public static final StdTypeList LONG_OBJECT = + StdTypeList.make(Type.LONG, Type.OBJECT); + + /** {@code non-null;} the list {@code [float, Object]} */ + public static final StdTypeList FLOAT_OBJECT = + StdTypeList.make(Type.FLOAT, Type.OBJECT); + + /** {@code non-null;} the list {@code [double, Object]} */ + public static final StdTypeList DOUBLE_OBJECT = + StdTypeList.make(Type.DOUBLE, Type.OBJECT); + + /** {@code non-null;} the list {@code [long, int]} */ + public static final StdTypeList LONG_INT = + StdTypeList.make(Type.LONG, Type.INT); + + /** {@code non-null;} the list {@code [int[], int]} */ + public static final StdTypeList INTARR_INT = + StdTypeList.make(Type.INT_ARRAY, Type.INT); + + /** {@code non-null;} the list {@code [long[], int]} */ + public static final StdTypeList LONGARR_INT = + StdTypeList.make(Type.LONG_ARRAY, Type.INT); + + /** {@code non-null;} the list {@code [float[], int]} */ + public static final StdTypeList FLOATARR_INT = + StdTypeList.make(Type.FLOAT_ARRAY, Type.INT); + + /** {@code non-null;} the list {@code [double[], int]} */ + public static final StdTypeList DOUBLEARR_INT = + StdTypeList.make(Type.DOUBLE_ARRAY, Type.INT); + + /** {@code non-null;} the list {@code [Object[], int]} */ + public static final StdTypeList OBJECTARR_INT = + StdTypeList.make(Type.OBJECT_ARRAY, Type.INT); + + /** {@code non-null;} the list {@code [boolean[], int]} */ + public static final StdTypeList BOOLEANARR_INT = + StdTypeList.make(Type.BOOLEAN_ARRAY, Type.INT); + + /** {@code non-null;} the list {@code [byte[], int]} */ + public static final StdTypeList BYTEARR_INT = + StdTypeList.make(Type.BYTE_ARRAY, Type.INT); + + /** {@code non-null;} the list {@code [char[], int]} */ + public static final StdTypeList CHARARR_INT = + StdTypeList.make(Type.CHAR_ARRAY, Type.INT); + + /** {@code non-null;} the list {@code [short[], int]} */ + public static final StdTypeList SHORTARR_INT = + StdTypeList.make(Type.SHORT_ARRAY, Type.INT); + + /** {@code non-null;} the list {@code [int, int[], int]} */ + public static final StdTypeList INT_INTARR_INT = + StdTypeList.make(Type.INT, Type.INT_ARRAY, Type.INT); + + /** {@code non-null;} the list {@code [long, long[], int]} */ + public static final StdTypeList LONG_LONGARR_INT = + StdTypeList.make(Type.LONG, Type.LONG_ARRAY, Type.INT); + + /** {@code non-null;} the list {@code [float, float[], int]} */ + public static final StdTypeList FLOAT_FLOATARR_INT = + StdTypeList.make(Type.FLOAT, Type.FLOAT_ARRAY, Type.INT); + + /** {@code non-null;} the list {@code [double, double[], int]} */ + public static final StdTypeList DOUBLE_DOUBLEARR_INT = + StdTypeList.make(Type.DOUBLE, Type.DOUBLE_ARRAY, Type.INT); + + /** {@code non-null;} the list {@code [Object, Object[], int]} */ + public static final StdTypeList OBJECT_OBJECTARR_INT = + StdTypeList.make(Type.OBJECT, Type.OBJECT_ARRAY, Type.INT); + + /** {@code non-null;} the list {@code [int, boolean[], int]} */ + public static final StdTypeList INT_BOOLEANARR_INT = + StdTypeList.make(Type.INT, Type.BOOLEAN_ARRAY, Type.INT); + + /** {@code non-null;} the list {@code [int, byte[], int]} */ + public static final StdTypeList INT_BYTEARR_INT = + StdTypeList.make(Type.INT, Type.BYTE_ARRAY, Type.INT); + + /** {@code non-null;} the list {@code [int, char[], int]} */ + public static final StdTypeList INT_CHARARR_INT = + StdTypeList.make(Type.INT, Type.CHAR_ARRAY, Type.INT); + + /** {@code non-null;} the list {@code [int, short[], int]} */ + public static final StdTypeList INT_SHORTARR_INT = + StdTypeList.make(Type.INT, Type.SHORT_ARRAY, Type.INT); + + /** + * Makes a single-element instance. + * + * @param type {@code non-null;} the element + * @return {@code non-null;} an appropriately-constructed instance + */ + public static StdTypeList make(Type type) { + StdTypeList result = new StdTypeList(1); + result.set(0, type); + return result; + } + + /** + * Makes a two-element instance. + * + * @param type0 {@code non-null;} the first element + * @param type1 {@code non-null;} the second element + * @return {@code non-null;} an appropriately-constructed instance + */ + public static StdTypeList make(Type type0, Type type1) { + StdTypeList result = new StdTypeList(2); + result.set(0, type0); + result.set(1, type1); + return result; + } + + /** + * Makes a three-element instance. + * + * @param type0 {@code non-null;} the first element + * @param type1 {@code non-null;} the second element + * @param type2 {@code non-null;} the third element + * @return {@code non-null;} an appropriately-constructed instance + */ + public static StdTypeList make(Type type0, Type type1, Type type2) { + StdTypeList result = new StdTypeList(3); + result.set(0, type0); + result.set(1, type1); + result.set(2, type2); + return result; + } + + /** + * Makes a four-element instance. + * + * @param type0 {@code non-null;} the first element + * @param type1 {@code non-null;} the second element + * @param type2 {@code non-null;} the third element + * @param type3 {@code non-null;} the fourth element + * @return {@code non-null;} an appropriately-constructed instance + */ + public static StdTypeList make(Type type0, Type type1, Type type2, + Type type3) { + StdTypeList result = new StdTypeList(4); + result.set(0, type0); + result.set(1, type1); + result.set(2, type2); + result.set(3, type3); + return result; + } + + /** + * Returns the given list as a comma-separated list of human forms. This + * is a static method so as to work on arbitrary {@link TypeList} + * instances. + * + * @param list {@code non-null;} the list to convert + * @return {@code non-null;} the human form + */ + public static String toHuman(TypeList list) { + int size = list.size(); + + if (size == 0) { + return ""; + } + + StringBuffer sb = new StringBuffer(100); + + for (int i = 0; i < size; i++) { + if (i != 0) { + sb.append(", "); + } + sb.append(list.getType(i).toHuman()); + } + + return sb.toString(); + } + + /** + * Returns a hashcode of the contents of the given list. This + * is a static method so as to work on arbitrary {@link TypeList} + * instances. + * + * @param list {@code non-null;} the list to inspect + * @return {@code non-null;} the hash code + */ + public static int hashContents(TypeList list) { + int size = list.size(); + int hash = 0; + + for (int i = 0; i < size; i++) { + hash = (hash * 31) + list.getType(i).hashCode(); + } + + return hash; + } + + /** + * Compares the contents of the given two instances for equality. This + * is a static method so as to work on arbitrary {@link TypeList} + * instances. + * + * @param list1 {@code non-null;} one list to compare + * @param list2 {@code non-null;} another list to compare + * @return whether the two lists contain corresponding equal elements + */ + public static boolean equalContents(TypeList list1, TypeList list2) { + int size = list1.size(); + + if (list2.size() != size) { + return false; + } + + for (int i = 0; i < size; i++) { + if (! list1.getType(i).equals(list2.getType(i))) { + return false; + } + } + + return true; + } + + /** + * Compares the contents of the given two instances for ordering. This + * is a static method so as to work on arbitrary {@link TypeList} + * instances. + * + * @param list1 {@code non-null;} one list to compare + * @param list2 {@code non-null;} another list to compare + * @return the order of the two lists + */ + public static int compareContents(TypeList list1, TypeList list2) { + int size1 = list1.size(); + int size2 = list2.size(); + int size = Math.min(size1, size2); + + for (int i = 0; i < size; i++) { + int comparison = list1.getType(i).compareTo(list2.getType(i)); + if (comparison != 0) { + return comparison; + } + } + + if (size1 == size2) { + return 0; + } else if (size1 < size2) { + return -1; + } else { + return 1; + } + } + + /** + * Constructs an instance. All indices initially contain {@code null}. + * + * @param size the size of the list + */ + public StdTypeList(int size) { + super(size); + } + + /** {@inheritDoc} */ + public Type getType(int n) { + return get(n); + } + + /** {@inheritDoc} */ + public int getWordCount() { + int sz = size(); + int result = 0; + + for (int i = 0; i < sz; i++) { + result += get(i).getCategory(); + } + + return result; + } + + /** {@inheritDoc} */ + public TypeList withAddedType(Type type) { + int sz = size(); + StdTypeList result = new StdTypeList(sz + 1); + + for (int i = 0; i < sz; i++) { + result.set0(i, get0(i)); + } + + result.set(sz, type); + result.setImmutable(); + return result; + } + + /** + * Gets the indicated element. It is an error to call this with the + * index for an element which was never set; if you do that, this + * will throw {@code NullPointerException}. + * + * @param n {@code >= 0, < size();} which element + * @return {@code non-null;} the indicated element + */ + public Type get(int n) { + return (Type) get0(n); + } + + /** + * Sets the type at the given index. + * + * @param n {@code >= 0, < size();} which element + * @param type {@code non-null;} the type to store + */ + public void set(int n, Type type) { + set0(n, type); + } + + /** + * Returns a new instance, which is the same as this instance, + * except that it has an additional type prepended to the + * original. + * + * @param type {@code non-null;} the new first element + * @return {@code non-null;} an appropriately-constructed instance + */ + public StdTypeList withFirst(Type type) { + int sz = size(); + StdTypeList result = new StdTypeList(sz + 1); + + result.set0(0, type); + for (int i = 0; i < sz; i++) { + result.set0(i + 1, getOrNull0(i)); + } + + return result; + } +} diff --git a/dx/src/com/android/jack/dx/rop/type/Type.java b/dx/src/com/android/jack/dx/rop/type/Type.java new file mode 100644 index 00000000..2b8d0a8e --- /dev/null +++ b/dx/src/com/android/jack/dx/rop/type/Type.java @@ -0,0 +1,862 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.rop.type; + +import com.android.jack.dx.util.Hex; + +import java.util.HashMap; + +/** + * Representation of a value type, such as may appear in a field, in a + * local, on a stack, or in a method descriptor. Instances of this + * class are generally interned and may be usefully compared with each + * other using {@code ==}. + */ +public final class Type implements TypeBearer, Comparable { + /** + * {@code non-null;} intern table mapping string descriptors to + * instances + */ + private static final HashMap internTable = + new HashMap(500); + + /** basic type constant for {@code void} */ + public static final int BT_VOID = 0; + + /** basic type constant for {@code boolean} */ + public static final int BT_BOOLEAN = 1; + + /** basic type constant for {@code byte} */ + public static final int BT_BYTE = 2; + + /** basic type constant for {@code char} */ + public static final int BT_CHAR = 3; + + /** basic type constant for {@code double} */ + public static final int BT_DOUBLE = 4; + + /** basic type constant for {@code float} */ + public static final int BT_FLOAT = 5; + + /** basic type constant for {@code int} */ + public static final int BT_INT = 6; + + /** basic type constant for {@code long} */ + public static final int BT_LONG = 7; + + /** basic type constant for {@code short} */ + public static final int BT_SHORT = 8; + + /** basic type constant for {@code Object} */ + public static final int BT_OBJECT = 9; + + /** basic type constant for a return address */ + public static final int BT_ADDR = 10; + + /** count of basic type constants */ + public static final int BT_COUNT = 11; + + /** {@code non-null;} instance representing {@code boolean} */ + public static final Type BOOLEAN = new Type("Z", BT_BOOLEAN); + + /** {@code non-null;} instance representing {@code byte} */ + public static final Type BYTE = new Type("B", BT_BYTE); + + /** {@code non-null;} instance representing {@code char} */ + public static final Type CHAR = new Type("C", BT_CHAR); + + /** {@code non-null;} instance representing {@code double} */ + public static final Type DOUBLE = new Type("D", BT_DOUBLE); + + /** {@code non-null;} instance representing {@code float} */ + public static final Type FLOAT = new Type("F", BT_FLOAT); + + /** {@code non-null;} instance representing {@code int} */ + public static final Type INT = new Type("I", BT_INT); + + /** {@code non-null;} instance representing {@code long} */ + public static final Type LONG = new Type("J", BT_LONG); + + /** {@code non-null;} instance representing {@code short} */ + public static final Type SHORT = new Type("S", BT_SHORT); + + /** {@code non-null;} instance representing {@code void} */ + public static final Type VOID = new Type("V", BT_VOID); + + /** {@code non-null;} instance representing a known-{@code null} */ + public static final Type KNOWN_NULL = new Type("", BT_OBJECT); + + /** {@code non-null;} instance representing a subroutine return address */ + public static final Type RETURN_ADDRESS = new Type("", BT_ADDR); + + static { + /* + * Put all the primitive types into the intern table. This needs + * to happen before the array types below get interned. + */ + putIntern(BOOLEAN); + putIntern(BYTE); + putIntern(CHAR); + putIntern(DOUBLE); + putIntern(FLOAT); + putIntern(INT); + putIntern(LONG); + putIntern(SHORT); + /* + * Note: VOID isn't put in the intern table, since it's special and + * shouldn't be found by a normal call to intern(). + */ + } + + /** + * {@code non-null;} instance representing + * {@code java.lang.annotation.Annotation} + */ + public static final Type ANNOTATION = + intern("Ljava/lang/annotation/Annotation;"); + + /** {@code non-null;} instance representing {@code java.lang.Class} */ + public static final Type CLASS = intern("Ljava/lang/Class;"); + + /** {@code non-null;} instance representing {@code java.lang.Cloneable} */ + public static final Type CLONEABLE = intern("Ljava/lang/Cloneable;"); + + /** {@code non-null;} instance representing {@code java.lang.Object} */ + public static final Type OBJECT = intern("Ljava/lang/Object;"); + + /** {@code non-null;} instance representing {@code java.io.Serializable} */ + public static final Type SERIALIZABLE = intern("Ljava/io/Serializable;"); + + /** {@code non-null;} instance representing {@code java.lang.String} */ + public static final Type STRING = intern("Ljava/lang/String;"); + + /** {@code non-null;} instance representing {@code java.lang.Throwable} */ + public static final Type THROWABLE = intern("Ljava/lang/Throwable;"); + + /** + * {@code non-null;} instance representing {@code java.lang.Boolean}; the + * suffix on the name helps disambiguate this from the instance + * representing a primitive type + */ + public static final Type BOOLEAN_CLASS = intern("Ljava/lang/Boolean;"); + + /** + * {@code non-null;} instance representing {@code java.lang.Byte}; the + * suffix on the name helps disambiguate this from the instance + * representing a primitive type + */ + public static final Type BYTE_CLASS = intern("Ljava/lang/Byte;"); + + /** + * {@code non-null;} instance representing {@code java.lang.Character}; the + * suffix on the name helps disambiguate this from the instance + * representing a primitive type + */ + public static final Type CHARACTER_CLASS = intern("Ljava/lang/Character;"); + + /** + * {@code non-null;} instance representing {@code java.lang.Double}; the + * suffix on the name helps disambiguate this from the instance + * representing a primitive type + */ + public static final Type DOUBLE_CLASS = intern("Ljava/lang/Double;"); + + /** + * {@code non-null;} instance representing {@code java.lang.Float}; the + * suffix on the name helps disambiguate this from the instance + * representing a primitive type + */ + public static final Type FLOAT_CLASS = intern("Ljava/lang/Float;"); + + /** + * {@code non-null;} instance representing {@code java.lang.Integer}; the + * suffix on the name helps disambiguate this from the instance + * representing a primitive type + */ + public static final Type INTEGER_CLASS = intern("Ljava/lang/Integer;"); + + /** + * {@code non-null;} instance representing {@code java.lang.Long}; the + * suffix on the name helps disambiguate this from the instance + * representing a primitive type + */ + public static final Type LONG_CLASS = intern("Ljava/lang/Long;"); + + /** + * {@code non-null;} instance representing {@code java.lang.Short}; the + * suffix on the name helps disambiguate this from the instance + * representing a primitive type + */ + public static final Type SHORT_CLASS = intern("Ljava/lang/Short;"); + + /** + * {@code non-null;} instance representing {@code java.lang.Void}; the + * suffix on the name helps disambiguate this from the instance + * representing a primitive type + */ + public static final Type VOID_CLASS = intern("Ljava/lang/Void;"); + + /** {@code non-null;} instance representing {@code boolean[]} */ + public static final Type BOOLEAN_ARRAY = BOOLEAN.getArrayType(); + + /** {@code non-null;} instance representing {@code byte[]} */ + public static final Type BYTE_ARRAY = BYTE.getArrayType(); + + /** {@code non-null;} instance representing {@code char[]} */ + public static final Type CHAR_ARRAY = CHAR.getArrayType(); + + /** {@code non-null;} instance representing {@code double[]} */ + public static final Type DOUBLE_ARRAY = DOUBLE.getArrayType(); + + /** {@code non-null;} instance representing {@code float[]} */ + public static final Type FLOAT_ARRAY = FLOAT.getArrayType(); + + /** {@code non-null;} instance representing {@code int[]} */ + public static final Type INT_ARRAY = INT.getArrayType(); + + /** {@code non-null;} instance representing {@code long[]} */ + public static final Type LONG_ARRAY = LONG.getArrayType(); + + /** {@code non-null;} instance representing {@code Object[]} */ + public static final Type OBJECT_ARRAY = OBJECT.getArrayType(); + + /** {@code non-null;} instance representing {@code short[]} */ + public static final Type SHORT_ARRAY = SHORT.getArrayType(); + + /** {@code non-null;} field descriptor for the type */ + private final String descriptor; + + /** + * basic type corresponding to this type; one of the + * {@code BT_*} constants + */ + private final int basicType; + + /** + * {@code >= -1;} for an uninitialized type, bytecode index that this + * instance was allocated at; {@code Integer.MAX_VALUE} if it + * was an incoming uninitialized instance; {@code -1} if this + * is an inititialized instance + */ + private final int newAt; + + /** + * {@code null-ok;} the internal-form class name corresponding to + * this type, if calculated; only valid if {@code this} is a + * reference type and additionally not a return address + */ + private String className; + + /** + * {@code null-ok;} the type corresponding to an array of this type, if + * calculated + */ + private Type arrayType; + + /** + * {@code null-ok;} the type corresponding to elements of this type, if + * calculated; only valid if {@code this} is an array type + */ + private Type componentType; + + /** + * {@code null-ok;} the type corresponding to the initialized version of + * this type, if this instance is in fact an uninitialized type + */ + private Type initializedType; + + /** + * Returns the unique instance corresponding to the type with the + * given descriptor. See vmspec-2 sec4.3.2 for details on the + * field descriptor syntax. This method does not allow + * {@code "V"} (that is, type {@code void}) as a valid + * descriptor. + * + * @param descriptor {@code non-null;} the descriptor + * @return {@code non-null;} the corresponding instance + * @throws IllegalArgumentException thrown if the descriptor has + * invalid syntax + */ + public static Type intern(String descriptor) { + Type result; + synchronized (internTable) { + result = internTable.get(descriptor); + } + if (result != null) { + return result; + } + + char firstChar; + try { + firstChar = descriptor.charAt(0); + } catch (IndexOutOfBoundsException ex) { + // Translate the exception. + throw new IllegalArgumentException("descriptor is empty"); + } catch (NullPointerException ex) { + // Elucidate the exception. + throw new NullPointerException("descriptor == null"); + } + + if (firstChar == '[') { + /* + * Recursively strip away array markers to get at the underlying + * type, and build back on to form the result. + */ + result = intern(descriptor.substring(1)); + return result.getArrayType(); + } + + /* + * If the first character isn't '[' and it wasn't found in the + * intern cache, then it had better be the descriptor for a class. + */ + + int length = descriptor.length(); + if ((firstChar != 'L') || + (descriptor.charAt(length - 1) != ';')) { + throw new IllegalArgumentException("bad descriptor: " + descriptor); + } + + /* + * Validate the characters of the class name itself. Note that + * vmspec-2 does not have a coherent definition for valid + * internal-form class names, and the definition here is fairly + * liberal: A name is considered valid as long as it doesn't + * contain any of '[' ';' '.' '(' ')', and it has no more than one + * '/' in a row, and no '/' at either end. + */ + + int limit = (length - 1); // Skip the final ';'. + for (int i = 1; i < limit; i++) { + char c = descriptor.charAt(i); + switch (c) { + case '[': + case ';': + case '.': + case '(': + case ')': { + throw new IllegalArgumentException("bad descriptor: " + descriptor); + } + case '/': { + if ((i == 1) || + (i == (length - 1)) || + (descriptor.charAt(i - 1) == '/')) { + throw new IllegalArgumentException("bad descriptor: " + descriptor); + } + break; + } + } + } + + result = new Type(descriptor, BT_OBJECT); + return putIntern(result); + } + + /** + * Returns the unique instance corresponding to the type with the + * given descriptor, allowing {@code "V"} to return the type + * for {@code void}. Other than that one caveat, this method + * is identical to {@link #intern}. + * + * @param descriptor {@code non-null;} the descriptor + * @return {@code non-null;} the corresponding instance + * @throws IllegalArgumentException thrown if the descriptor has + * invalid syntax + */ + public static Type internReturnType(String descriptor) { + try { + if (descriptor.equals("V")) { + // This is the one special case where void may be returned. + return VOID; + } + } catch (NullPointerException ex) { + // Elucidate the exception. + throw new NullPointerException("descriptor == null"); + } + + return intern(descriptor); + } + + /** + * Returns the unique instance corresponding to the type of the + * class with the given name. Calling this method is equivalent to + * calling {@code intern(name)} if {@code name} begins + * with {@code "["} and calling {@code intern("L" + name + ";")} + * in all other cases. + * + * @param name {@code non-null;} the name of the class whose type + * is desired + * @return {@code non-null;} the corresponding type + * @throws IllegalArgumentException thrown if the name has + * invalid syntax + */ + public static Type internClassName(String name) { + if (name == null) { + throw new NullPointerException("name == null"); + } + + if (name.startsWith("[")) { + return intern(name); + } + + return intern('L' + name + ';'); + } + + /** + * Constructs an instance corresponding to an "uninitialized type." + * This is a private constructor; use one of the public static + * methods to get instances. + * + * @param descriptor {@code non-null;} the field descriptor for the type + * @param basicType basic type corresponding to this type; one of the + * {@code BT_*} constants + * @param newAt {@code >= -1;} allocation bytecode index + */ + private Type(String descriptor, int basicType, int newAt) { + if (descriptor == null) { + throw new NullPointerException("descriptor == null"); + } + + if ((basicType < 0) || (basicType >= BT_COUNT)) { + throw new IllegalArgumentException("bad basicType"); + } + + if (newAt < -1) { + throw new IllegalArgumentException("newAt < -1"); + } + + this.descriptor = descriptor; + this.basicType = basicType; + this.newAt = newAt; + this.arrayType = null; + this.componentType = null; + this.initializedType = null; + } + + /** + * Constructs an instance corresponding to an "initialized type." + * This is a private constructor; use one of the public static + * methods to get instances. + * + * @param descriptor {@code non-null;} the field descriptor for the type + * @param basicType basic type corresponding to this type; one of the + * {@code BT_*} constants + */ + private Type(String descriptor, int basicType) { + this(descriptor, basicType, -1); + } + + /** {@inheritDoc} */ + @Override + public boolean equals(Object other) { + if (this == other) { + /* + * Since externally-visible types are interned, this check + * helps weed out some easy cases. + */ + return true; + } + + if (!(other instanceof Type)) { + return false; + } + + return descriptor.equals(((Type) other).descriptor); + } + + /** {@inheritDoc} */ + @Override + public int hashCode() { + return descriptor.hashCode(); + } + + /** {@inheritDoc} */ + public int compareTo(Type other) { + return descriptor.compareTo(other.descriptor); + } + + /** {@inheritDoc} */ + @Override + public String toString() { + return descriptor; + } + + /** {@inheritDoc} */ + public String toHuman() { + switch (basicType) { + case BT_VOID: return "void"; + case BT_BOOLEAN: return "boolean"; + case BT_BYTE: return "byte"; + case BT_CHAR: return "char"; + case BT_DOUBLE: return "double"; + case BT_FLOAT: return "float"; + case BT_INT: return "int"; + case BT_LONG: return "long"; + case BT_SHORT: return "short"; + case BT_OBJECT: break; + default: return descriptor; + } + + if (isArray()) { + return getComponentType().toHuman() + "[]"; + } + + // Remove the "L...;" around the type and convert "/" to ".". + return getClassName().replace("/", "."); + } + + /** {@inheritDoc} */ + public Type getType() { + return this; + } + + /** {@inheritDoc} */ + public Type getFrameType() { + switch (basicType) { + case BT_BOOLEAN: + case BT_BYTE: + case BT_CHAR: + case BT_INT: + case BT_SHORT: { + return INT; + } + } + + return this; + } + + /** {@inheritDoc} */ + public int getBasicType() { + return basicType; + } + + /** {@inheritDoc} */ + public int getBasicFrameType() { + switch (basicType) { + case BT_BOOLEAN: + case BT_BYTE: + case BT_CHAR: + case BT_INT: + case BT_SHORT: { + return BT_INT; + } + } + + return basicType; + } + + /** {@inheritDoc} */ + public boolean isConstant() { + return false; + } + + /** + * Gets the descriptor. + * + * @return {@code non-null;} the descriptor + */ + public String getDescriptor() { + return descriptor; + } + + /** + * Gets the name of the class this type corresponds to, in internal + * form. This method is only valid if this instance is for a + * normal reference type (that is, a reference type and + * additionally not a return address). + * + * @return {@code non-null;} the internal-form class name + */ + public String getClassName() { + if (className == null) { + if (!isReference()) { + throw new IllegalArgumentException("not an object type: " + + descriptor); + } + + if (descriptor.charAt(0) == '[') { + className = descriptor; + } else { + className = descriptor.substring(1, descriptor.length() - 1); + } + } + + return className; + } + + /** + * Gets the category. Most instances are category 1. {@code long} + * and {@code double} are the only category 2 types. + * + * @see #isCategory1 + * @see #isCategory2 + * @return the category + */ + public int getCategory() { + switch (basicType) { + case BT_LONG: + case BT_DOUBLE: { + return 2; + } + } + + return 1; + } + + /** + * Returns whether or not this is a category 1 type. + * + * @see #getCategory + * @see #isCategory2 + * @return whether or not this is a category 1 type + */ + public boolean isCategory1() { + switch (basicType) { + case BT_LONG: + case BT_DOUBLE: { + return false; + } + } + + return true; + } + + /** + * Returns whether or not this is a category 2 type. + * + * @see #getCategory + * @see #isCategory1 + * @return whether or not this is a category 2 type + */ + public boolean isCategory2() { + switch (basicType) { + case BT_LONG: + case BT_DOUBLE: { + return true; + } + } + + return false; + } + + /** + * Gets whether this type is "intlike." An intlike type is one which, when + * placed on a stack or in a local, is automatically converted to an + * {@code int}. + * + * @return whether this type is "intlike" + */ + public boolean isIntlike() { + switch (basicType) { + case BT_BOOLEAN: + case BT_BYTE: + case BT_CHAR: + case BT_INT: + case BT_SHORT: { + return true; + } + } + + return false; + } + + /** + * Gets whether this type is a primitive type. All types are either + * primitive or reference types. + * + * @return whether this type is primitive + */ + public boolean isPrimitive() { + switch (basicType) { + case BT_BOOLEAN: + case BT_BYTE: + case BT_CHAR: + case BT_DOUBLE: + case BT_FLOAT: + case BT_INT: + case BT_LONG: + case BT_SHORT: + case BT_VOID: { + return true; + } + } + + return false; + } + + /** + * Gets whether this type is a normal reference type. A normal + * reference type is a reference type that is not a return + * address. This method is just convenient shorthand for + * {@code getBasicType() == Type.BT_OBJECT}. + * + * @return whether this type is a normal reference type + */ + public boolean isReference() { + return (basicType == BT_OBJECT); + } + + /** + * Gets whether this type is an array type. If this method returns + * {@code true}, then it is safe to use {@link #getComponentType} + * to determine the component type. + * + * @return whether this type is an array type + */ + public boolean isArray() { + return (descriptor.charAt(0) == '['); + } + + /** + * Gets whether this type is an array type or is a known-null, and + * hence is compatible with array types. + * + * @return whether this type is an array type + */ + public boolean isArrayOrKnownNull() { + return isArray() || equals(KNOWN_NULL); + } + + /** + * Gets whether this type represents an uninitialized instance. An + * uninitialized instance is what one gets back from the {@code new} + * opcode, and remains uninitialized until a valid constructor is + * invoked on it. + * + * @return whether this type is "uninitialized" + */ + public boolean isUninitialized() { + return (newAt >= 0); + } + + /** + * Gets the bytecode index at which this uninitialized type was + * allocated. This returns {@code Integer.MAX_VALUE} if this + * type is an uninitialized incoming parameter (i.e., the + * {@code this} of an {@code } method) or + * {@code -1} if this type is in fact initialized. + * + * @return {@code >= -1;} the allocation bytecode index + */ + public int getNewAt() { + return newAt; + } + + /** + * Gets the initialized type corresponding to this instance, but only + * if this instance is in fact an uninitialized object type. + * + * @return {@code non-null;} the initialized type + */ + public Type getInitializedType() { + if (initializedType == null) { + throw new IllegalArgumentException("initialized type: " + + descriptor); + } + + return initializedType; + } + + /** + * Gets the type corresponding to an array of this type. + * + * @return {@code non-null;} the array type + */ + public Type getArrayType() { + if (arrayType == null) { + arrayType = putIntern(new Type('[' + descriptor, BT_OBJECT)); + } + + return arrayType; + } + + /** + * Gets the component type of this type. This method is only valid on + * array types. + * + * @return {@code non-null;} the component type + */ + public Type getComponentType() { + if (componentType == null) { + if (descriptor.charAt(0) != '[') { + throw new IllegalArgumentException("not an array type: " + + descriptor); + } + componentType = intern(descriptor.substring(1)); + } + + return componentType; + } + + /** + * Returns a new interned instance which is identical to this one, except + * it is indicated as uninitialized and allocated at the given bytecode + * index. This instance must be an initialized object type. + * + * @param newAt {@code >= 0;} the allocation bytecode index + * @return {@code non-null;} an appropriately-constructed instance + */ + public Type asUninitialized(int newAt) { + if (newAt < 0) { + throw new IllegalArgumentException("newAt < 0"); + } + + if (!isReference()) { + throw new IllegalArgumentException("not a reference type: " + + descriptor); + } + + if (isUninitialized()) { + /* + * Dealing with uninitialized types as a starting point is + * a pain, and it's not clear that it'd ever be used, so + * just disallow it. + */ + throw new IllegalArgumentException("already uninitialized: " + + descriptor); + } + + /* + * Create a new descriptor that is unique and shouldn't conflict + * with "normal" type descriptors + */ + String newDesc = 'N' + Hex.u2(newAt) + descriptor; + Type result = new Type(newDesc, BT_OBJECT, newAt); + result.initializedType = this; + return putIntern(result); + } + + /** + * Puts the given instance in the intern table if it's not already + * there. If a conflicting value is already in the table, then leave it. + * Return the interned value. + * + * @param type {@code non-null;} instance to make interned + * @return {@code non-null;} the actual interned object + */ + private static Type putIntern(Type type) { + synchronized (internTable) { + String descriptor = type.getDescriptor(); + Type already = internTable.get(descriptor); + if (already != null) { + return already; + } + internTable.put(descriptor, type); + return type; + } + } +} diff --git a/dx/src/com/android/jack/dx/rop/type/TypeBearer.java b/dx/src/com/android/jack/dx/rop/type/TypeBearer.java new file mode 100644 index 00000000..012814e9 --- /dev/null +++ b/dx/src/com/android/jack/dx/rop/type/TypeBearer.java @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.rop.type; + +import com.android.jack.dx.util.ToHuman; + +/** + * Object which has an associated type, possibly itself. + */ +public interface TypeBearer + extends ToHuman { + /** + * Gets the type associated with this instance. + * + * @return {@code non-null;} the type + */ + public Type getType(); + + /** + * Gets the frame type corresponding to this type. This method returns + * {@code this}, except if {@link Type#isIntlike} on the underlying + * type returns {@code true} but the underlying type is not in + * fact {@link Type#INT}, in which case this method returns an instance + * whose underlying type is {@code INT}. + * + * @return {@code non-null;} the frame type for this instance + */ + public TypeBearer getFrameType(); + + /** + * Gets the basic type corresponding to this instance. + * + * @return the basic type; one of the {@code BT_*} constants + * defined by {@link Type} + */ + public int getBasicType(); + + /** + * Gets the basic type corresponding to this instance's frame type. This + * is equivalent to {@code getFrameType().getBasicType()}, and + * is the same as calling {@code getFrameType()} unless this + * instance is an int-like type, in which case this method returns + * {@code BT_INT}. + * + * @see #getBasicType + * @see #getFrameType + * + * @return the basic frame type; one of the {@code BT_*} constants + * defined by {@link Type} + */ + public int getBasicFrameType(); + + /** + * Returns whether this instance represents a constant value. + * + * @return {@code true} if this instance represents a constant value + * and {@code false} if not + */ + public boolean isConstant(); +} diff --git a/dx/src/com/android/jack/dx/rop/type/TypeList.java b/dx/src/com/android/jack/dx/rop/type/TypeList.java new file mode 100644 index 00000000..825d8816 --- /dev/null +++ b/dx/src/com/android/jack/dx/rop/type/TypeList.java @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.rop.type; + +/** + * List of {@link Type} instances (or of things that contain types). + */ +public interface TypeList { + /** + * Returns whether this instance is mutable. Note that the + * {@code TypeList} interface itself doesn't provide any + * means of mutation, but that doesn't mean that there isn't an + * extra-interface way of mutating an instance. + * + * @return {@code true} if this instance is mutable or + * {@code false} if it is immutable + */ + public boolean isMutable(); + + /** + * Gets the size of this list. + * + * @return {@code >= 0;} the size + */ + public int size(); + + /** + * Gets the indicated element. It is an error to call this with the + * index for an element which was never set; if you do that, this + * will throw {@code NullPointerException}. + * + * @param n {@code >= 0, < size();} which element + * @return {@code non-null;} the indicated element + */ + public Type getType(int n); + + /** + * Gets the number of 32-bit words required to hold instances of + * all the elements of this list. This is a sum of the widths (categories) + * of all the elements. + * + * @return {@code >= 0;} the required number of words + */ + public int getWordCount(); + + /** + * Returns a new instance which is identical to this one, except that + * the given item is appended to the end and it is guaranteed to be + * immutable. + * + * @param type {@code non-null;} item to append + * @return {@code non-null;} an appropriately-constructed instance + */ + public TypeList withAddedType(Type type); +} diff --git a/dx/src/com/android/jack/dx/rop/type/package.html b/dx/src/com/android/jack/dx/rop/type/package.html new file mode 100644 index 00000000..fd7cf739 --- /dev/null +++ b/dx/src/com/android/jack/dx/rop/type/package.html @@ -0,0 +1,8 @@ + +

      Implementation of classes that represent types (classes or primitives).

      + +

      PACKAGES USED: +

        +
      • com.android.jack.dx.util
      • +
      + diff --git a/dx/src/com/android/jack/dx/ssa/BasicRegisterMapper.java b/dx/src/com/android/jack/dx/ssa/BasicRegisterMapper.java new file mode 100644 index 00000000..f9bdb2fa --- /dev/null +++ b/dx/src/com/android/jack/dx/ssa/BasicRegisterMapper.java @@ -0,0 +1,129 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.ssa; + +import com.android.jack.dx.rop.code.RegisterSpec; +import com.android.jack.dx.rop.code.RegisterSpecList; +import com.android.jack.dx.util.IntList; + +/** + * This class maps one register space into another, with + * each mapping built up individually and added via addMapping() + */ +public class BasicRegisterMapper extends RegisterMapper { + /** indexed by old register, containing new name */ + private IntList oldToNew; + + /** running count of used registers in new namespace */ + private int runningCountNewRegisters; + + /** + * Creates a new OneToOneRegisterMapper. + * + * @param countOldRegisters the number of registers in the old name space + */ + public BasicRegisterMapper(int countOldRegisters) { + oldToNew = new IntList(countOldRegisters); + } + + /** {@inheritDoc} */ + @Override + public int getNewRegisterCount() { + return runningCountNewRegisters; + } + + /** {@inheritDoc} */ + @Override + public RegisterSpec map(RegisterSpec registerSpec) { + if (registerSpec == null) { + return null; + } + + int newReg; + try { + newReg = oldToNew.get(registerSpec.getReg()); + } catch (IndexOutOfBoundsException ex) { + newReg = -1; + } + + if (newReg < 0) { + throw new RuntimeException("no mapping specified for register"); + } + + return registerSpec.withReg(newReg); + } + + /** + * Returns the new-namespace mapping for the specified + * old-namespace register, or -1 if one exists. + * + * @param oldReg {@code >= 0;} old-namespace register + * @return new-namespace register or -1 if none + */ + public int oldToNew(int oldReg) { + if (oldReg >= oldToNew.size()) { + return -1; + } + + return oldToNew.get(oldReg); + } + + /** {@inheritDoc} */ + public String toHuman() { + StringBuilder sb = new StringBuilder(); + + sb.append("Old\tNew\n"); + int sz = oldToNew.size(); + + for (int i = 0; i < sz; i++) { + sb.append(i); + sb.append('\t'); + sb.append(oldToNew.get(i)); + sb.append('\n'); + } + + sb.append("new reg count:"); + + sb.append(runningCountNewRegisters); + sb.append('\n'); + + return sb.toString(); + } + + /** + * Adds a mapping to the mapper. If oldReg has already been mapped, + * overwrites previous mapping with new mapping. + * + * @param oldReg {@code >= 0;} old register + * @param newReg {@code >= 0;} new register + * @param category {@code 1..2;} width of reg + */ + public void addMapping(int oldReg, int newReg, int category) { + if (oldReg >= oldToNew.size()) { + // expand the array as necessary + for (int i = oldReg - oldToNew.size(); i >= 0; i--) { + oldToNew.add(-1); + } + } + + oldToNew.set(oldReg, newReg); + + if (runningCountNewRegisters < (newReg + category)) { + runningCountNewRegisters = newReg + category; + } + } +} diff --git a/dx/src/com/android/jack/dx/ssa/ConstCollector.java b/dx/src/com/android/jack/dx/ssa/ConstCollector.java new file mode 100644 index 00000000..ee12cb44 --- /dev/null +++ b/dx/src/com/android/jack/dx/ssa/ConstCollector.java @@ -0,0 +1,400 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.ssa; + +import com.android.jack.dx.rop.code.LocalItem; +import com.android.jack.dx.rop.code.PlainCstInsn; +import com.android.jack.dx.rop.code.PlainInsn; +import com.android.jack.dx.rop.code.RegOps; +import com.android.jack.dx.rop.code.RegisterSpec; +import com.android.jack.dx.rop.code.RegisterSpecList; +import com.android.jack.dx.rop.code.Rop; +import com.android.jack.dx.rop.code.Rops; +import com.android.jack.dx.rop.code.SourcePosition; +import com.android.jack.dx.rop.code.ThrowingCstInsn; +import com.android.jack.dx.rop.cst.Constant; +import com.android.jack.dx.rop.cst.CstString; +import com.android.jack.dx.rop.cst.TypedConstant; +import com.android.jack.dx.rop.type.StdTypeList; +import com.android.jack.dx.rop.type.TypeBearer; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; + +/** + * Collects constants that are used more than once at the top of the + * method block. This increases register usage by about 5% but decreases + * insn size by about 3%. + */ +public class ConstCollector { + /** Maximum constants to collect per method. Puts cap on reg use */ + private static final int MAX_COLLECTED_CONSTANTS = 5; + + /** + * Also collect string consts, although they can throw exceptions. + * This is off now because it just doesn't seem to gain a whole lot. + * TODO if you turn this on, you must change SsaInsn.hasSideEffect() + * to return false for const-string insns whose exceptions are not + * caught in the current method. + */ + private static boolean COLLECT_STRINGS = false; + + /** + * If true, allow one local var to be involved with a collected const. + * Turned off because it mostly just inserts more moves. + */ + private static boolean COLLECT_ONE_LOCAL = false; + + /** method we're processing */ + private final SsaMethod ssaMeth; + + /** + * Processes a method. + * + * @param ssaMethod {@code non-null;} method to process + */ + public static void process(SsaMethod ssaMethod) { + ConstCollector cc = new ConstCollector(ssaMethod); + cc.run(); + } + + /** + * Constructs an instance. + * + * @param ssaMethod {@code non-null;} method to process + */ + private ConstCollector(SsaMethod ssaMethod) { + this.ssaMeth = ssaMethod; + } + + /** + * Applies the optimization. + */ + private void run() { + int regSz = ssaMeth.getRegCount(); + + ArrayList constantList + = getConstsSortedByCountUse(); + + int toCollect = Math.min(constantList.size(), MAX_COLLECTED_CONSTANTS); + + SsaBasicBlock start = ssaMeth.getEntryBlock(); + + // Constant to new register containing the constant + HashMap newRegs + = new HashMap (toCollect); + + for (int i = 0; i < toCollect; i++) { + TypedConstant cst = constantList.get(i); + RegisterSpec result + = RegisterSpec.make(ssaMeth.makeNewSsaReg(), cst); + + Rop constRop = Rops.opConst(cst); + + if (constRop.getBranchingness() == Rop.BRANCH_NONE) { + start.addInsnToHead( + new PlainCstInsn(Rops.opConst(cst), + SourcePosition.NO_INFO, result, + RegisterSpecList.EMPTY, cst)); + } else { + // We need two new basic blocks along with the new insn + SsaBasicBlock entryBlock = ssaMeth.getEntryBlock(); + SsaBasicBlock successorBlock + = entryBlock.getPrimarySuccessor(); + + // Insert a block containing the const insn. + SsaBasicBlock constBlock + = entryBlock.insertNewSuccessor(successorBlock); + + constBlock.replaceLastInsn( + new ThrowingCstInsn(constRop, SourcePosition.NO_INFO, + RegisterSpecList.EMPTY, + StdTypeList.EMPTY, cst)); + + // Insert a block containing the move-result-pseudo insn. + + SsaBasicBlock resultBlock + = constBlock.insertNewSuccessor(successorBlock); + PlainInsn insn + = new PlainInsn( + Rops.opMoveResultPseudo(result.getTypeBearer()), + SourcePosition.NO_INFO, + result, RegisterSpecList.EMPTY); + + resultBlock.addInsnToHead(insn); + } + + newRegs.put(cst, result); + } + + updateConstUses(newRegs, regSz); + } + + /** + * Gets all of the collectable constant values used in this method, + * sorted by most used first. Skips non-collectable consts, such as + * non-string object constants + * + * @return {@code non-null;} list of constants in most-to-least used order + */ + private ArrayList getConstsSortedByCountUse() { + int regSz = ssaMeth.getRegCount(); + + final HashMap countUses + = new HashMap(); + + /* + * Each collected constant can be used by just one local + * (used only if COLLECT_ONE_LOCAL is true). + */ + final HashSet usedByLocal + = new HashSet(); + + // Count how many times each const value is used. + for (int i = 0; i < regSz; i++) { + SsaInsn insn = ssaMeth.getDefinitionForRegister(i); + + if (insn == null || insn.getOpcode() == null) continue; + + RegisterSpec result = insn.getResult(); + TypeBearer typeBearer = result.getTypeBearer(); + + if (!typeBearer.isConstant()) continue; + + TypedConstant cst = (TypedConstant) typeBearer; + + // Find defining instruction for move-result-pseudo instructions + if (insn.getOpcode().getOpcode() == RegOps.MOVE_RESULT_PSEUDO) { + int pred = insn.getBlock().getPredecessors().nextSetBit(0); + ArrayList predInsns; + predInsns = ssaMeth.getBlocks().get(pred).getInsns(); + insn = predInsns.get(predInsns.size()-1); + } + + if (insn.canThrow()) { + /* + * Don't move anything other than strings -- the risk + * of changing where an exception is thrown is too high. + */ + if (!(cst instanceof CstString) || !COLLECT_STRINGS) { + continue; + } + /* + * We can't move any throwable const whose throw will be + * caught, so don't count them. + */ + if (insn.getBlock().getSuccessors().cardinality() > 1) { + continue; + } + } + + /* + * TODO: Might be nice to try and figure out which local + * wins most when collected. + */ + if (ssaMeth.isRegALocal(result)) { + if (!COLLECT_ONE_LOCAL) { + continue; + } else { + if (usedByLocal.contains(cst)) { + // Count one local usage only. + continue; + } else { + usedByLocal.add(cst); + } + } + } + + Integer has = countUses.get(cst); + if (has == null) { + countUses.put(cst, 1); + } else { + countUses.put(cst, has + 1); + } + } + + // Collect constants that have been reused. + ArrayList constantList = new ArrayList(); + for (Map.Entry entry : countUses.entrySet()) { + if (entry.getValue() > 1) { + constantList.add(entry.getKey()); + } + } + + // Sort by use, with most used at the beginning of the list. + Collections.sort(constantList, new Comparator() { + public int compare(Constant a, Constant b) { + int ret; + ret = countUses.get(b) - countUses.get(a); + + if (ret == 0) { + /* + * Provide sorting determinisim for constants with same + * usage count. + */ + ret = a.compareTo(b); + } + + return ret; + } + + @Override + public boolean equals (Object obj) { + return obj == this; + } + }); + + return constantList; + } + + /** + * Inserts mark-locals if necessary when changing a register. If + * the definition of {@code origReg} is associated with a local + * variable, then insert a mark-local for {@code newReg} just below + * it. We expect the definition of {@code origReg} to ultimately + * be removed by the dead code eliminator + * + * @param origReg {@code non-null;} original register + * @param newReg {@code non-null;} new register that will replace + * {@code origReg} + */ + private void fixLocalAssignment(RegisterSpec origReg, + RegisterSpec newReg) { + for (SsaInsn use : ssaMeth.getUseListForRegister(origReg.getReg())) { + RegisterSpec localAssignment = use.getLocalAssignment(); + if (localAssignment == null) { + continue; + } + + if (use.getResult() == null) { + /* + * This is a mark-local. it will be updated when all uses + * are updated. + */ + continue; + } + + LocalItem local = localAssignment.getLocalItem(); + + // Un-associate original use. + use.setResultLocal(null); + + // Now add a mark-local to the new reg immediately after. + newReg = newReg.withLocalItem(local); + + SsaInsn newInsn + = SsaInsn.makeFromRop( + new PlainInsn(Rops.opMarkLocal(newReg), + SourcePosition.NO_INFO, null, + RegisterSpecList.make(newReg)), + use.getBlock()); + + ArrayList insns = use.getBlock().getInsns(); + + insns.add(insns.indexOf(use) + 1, newInsn); + } + } + + /** + * Updates all uses of various consts to use the values in the newly + * assigned registers. + * + * @param newRegs {@code non-null;} mapping between constant and new reg + * @param origRegCount {@code >=0;} original SSA reg count, not including + * newly added constant regs + */ + private void updateConstUses(HashMap newRegs, + int origRegCount) { + + /* + * set of constants associated with a local variable; used + * only if COLLECT_ONE_LOCAL is true. + */ + final HashSet usedByLocal + = new HashSet(); + + final ArrayList[] useList = ssaMeth.getUseListCopy(); + + for (int i = 0; i < origRegCount; i++) { + SsaInsn insn = ssaMeth.getDefinitionForRegister(i); + + if (insn == null) { + continue; + } + + final RegisterSpec origReg = insn.getResult(); + TypeBearer typeBearer = insn.getResult().getTypeBearer(); + + if (!typeBearer.isConstant()) continue; + + TypedConstant cst = (TypedConstant) typeBearer; + final RegisterSpec newReg = newRegs.get(cst); + + if (newReg == null) { + continue; + } + + if (ssaMeth.isRegALocal(origReg)) { + if (!COLLECT_ONE_LOCAL) { + continue; + } else { + /* + * TODO: If the same local gets the same cst + * multiple times, it would be nice to reuse the + * register. + */ + if (usedByLocal.contains(cst)) { + continue; + } else { + usedByLocal.add(cst); + fixLocalAssignment(origReg, newRegs.get(cst)); + } + } + } + + // maps an original const register to the new collected register + RegisterMapper mapper = new RegisterMapper() { + @Override + public int getNewRegisterCount() { + return ssaMeth.getRegCount(); + } + + @Override + public RegisterSpec map(RegisterSpec registerSpec) { + if (registerSpec.getReg() == origReg.getReg()) { + return newReg.withLocalItem( + registerSpec.getLocalItem()); + } + + return registerSpec; + } + }; + + for (SsaInsn use : useList[origReg.getReg()]) { + if (use.canThrow() + && use.getBlock().getSuccessors().cardinality() > 1) { + continue; + } + use.mapSourceRegisters(mapper); + } + } + } +} diff --git a/dx/src/com/android/jack/dx/ssa/DeadCodeRemover.java b/dx/src/com/android/jack/dx/ssa/DeadCodeRemover.java new file mode 100644 index 00000000..70c00054 --- /dev/null +++ b/dx/src/com/android/jack/dx/ssa/DeadCodeRemover.java @@ -0,0 +1,272 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.ssa; + +import com.android.jack.dx.rop.code.Insn; +import com.android.jack.dx.rop.code.PlainInsn; +import com.android.jack.dx.rop.code.RegOps; +import com.android.jack.dx.rop.code.RegisterSpec; +import com.android.jack.dx.rop.code.RegisterSpecList; +import com.android.jack.dx.rop.code.Rop; +import com.android.jack.dx.rop.code.Rops; +import com.android.jack.dx.rop.code.SourcePosition; + +import java.util.ArrayList; +import java.util.BitSet; +import java.util.HashSet; + +/** + * A variation on Appel Algorithm 19.12 "Dead code elimination in SSA form". + * + * TODO this algorithm is more efficient if run in reverse from exit + * block to entry block. + */ +public class DeadCodeRemover { + /** method we're processing */ + private final SsaMethod ssaMeth; + + /** ssaMeth.getRegCount() */ + private final int regCount; + + /** + * indexed by register: whether reg should be examined + * (does it correspond to a no-side-effect insn?) + */ + private final BitSet worklist; + + /** use list indexed by register; modified during operation */ + private final ArrayList[] useList; + + /** + * Process a method with the dead-code remver + * + * @param ssaMethod method to process + */ + public static void process(SsaMethod ssaMethod) { + DeadCodeRemover dc = new DeadCodeRemover(ssaMethod); + dc.run(); + } + + /** + * Constructs an instance. + * + * @param ssaMethod method to process + */ + private DeadCodeRemover(SsaMethod ssaMethod) { + this.ssaMeth = ssaMethod; + + regCount = ssaMethod.getRegCount(); + worklist = new BitSet(regCount); + useList = ssaMeth.getUseListCopy(); + } + + /** + * Runs the dead code remover. + */ + private void run() { + pruneDeadInstructions(); + + HashSet deletedInsns = new HashSet(); + + ssaMeth.forEachInsn(new NoSideEffectVisitor(worklist)); + + int regV; + + while ( 0 <= (regV = worklist.nextSetBit(0)) ) { + worklist.clear(regV); + + if (useList[regV].size() == 0 + || isCircularNoSideEffect(regV, null)) { + + SsaInsn insnS = ssaMeth.getDefinitionForRegister(regV); + + // This insn has already been deleted. + if (deletedInsns.contains(insnS)) { + continue; + } + + RegisterSpecList sources = insnS.getSources(); + + int sz = sources.size(); + for (int i = 0; i < sz; i++) { + // Delete this insn from all usage lists. + RegisterSpec source = sources.get(i); + useList[source.getReg()].remove(insnS); + + if (!hasSideEffect( + ssaMeth.getDefinitionForRegister( + source.getReg()))) { + /* + * Only registers whose definition has no side effect + * should be added back to the worklist. + */ + worklist.set(source.getReg()); + } + } + + // Schedule this insn for later deletion. + deletedInsns.add(insnS); + } + } + + ssaMeth.deleteInsns(deletedInsns); + } + + /** + * Removes all instructions from every unreachable block. + */ + private void pruneDeadInstructions() { + HashSet deletedInsns = new HashSet(); + + ssaMeth.computeReachability(); + + for (SsaBasicBlock block : ssaMeth.getBlocks()) { + if (block.isReachable()) continue; + + // Prune instructions from unreachable blocks + for (int i = 0; i < block.getInsns().size(); i++) { + SsaInsn insn = block.getInsns().get(i); + RegisterSpecList sources = insn.getSources(); + int sourcesSize = sources.size(); + + // Delete this instruction completely if it has sources + if (sourcesSize != 0) { + deletedInsns.add(insn); + } + + // Delete this instruction from all usage lists. + for (int j = 0; j < sourcesSize; j++) { + RegisterSpec source = sources.get(j); + useList[source.getReg()].remove(insn); + } + + // Remove this instruction result from the sources of any phis + RegisterSpec result = insn.getResult(); + if (result == null) continue; + for (SsaInsn use : useList[result.getReg()]) { + if (use instanceof PhiInsn) { + PhiInsn phiUse = (PhiInsn) use; + phiUse.removePhiRegister(result); + } + } + } + } + + ssaMeth.deleteInsns(deletedInsns); + } + + /** + * Returns true if the only uses of this register form a circle of + * operations with no side effects. + * + * @param regV register to examine + * @param set a set of registers that we've already determined + * are only used as sources in operations with no side effect or null + * if this is the first recursion + * @return true if usage is circular without side effect + */ + private boolean isCircularNoSideEffect(int regV, BitSet set) { + if ((set != null) && set.get(regV)) { + return true; + } + + for (SsaInsn use : useList[regV]) { + if (hasSideEffect(use)) { + return false; + } + } + + if (set == null) { + set = new BitSet(regCount); + } + + // This register is only used in operations that have no side effect. + set.set(regV); + + for (SsaInsn use : useList[regV]) { + RegisterSpec result = use.getResult(); + + if (result == null + || !isCircularNoSideEffect(result.getReg(), set)) { + return false; + } + } + + return true; + } + + /** + * Returns true if this insn has a side-effect. Returns true + * if the insn is null for reasons stated in the code block. + * + * @param insn {@code null-ok;} instruction in question + * @return true if it has a side-effect + */ + private static boolean hasSideEffect(SsaInsn insn) { + if (insn == null) { + /* While false would seem to make more sense here, true + * prevents us from adding this back to a worklist unnecessarally. + */ + return true; + } + + return insn.hasSideEffect(); + } + + /** + * A callback class used to build up the initial worklist of + * registers defined by an instruction with no side effect. + */ + static private class NoSideEffectVisitor implements SsaInsn.Visitor { + BitSet noSideEffectRegs; + + /** + * Passes in data structures that will be filled out after + * ssaMeth.forEachInsn() is called with this instance. + * + * @param noSideEffectRegs to-build bitset of regs that are + * results of regs with no side effects + */ + public NoSideEffectVisitor(BitSet noSideEffectRegs) { + this.noSideEffectRegs = noSideEffectRegs; + } + + /** {@inheritDoc} */ + public void visitMoveInsn (NormalSsaInsn insn) { + // If we're tracking local vars, some moves have side effects. + if (!hasSideEffect(insn)) { + noSideEffectRegs.set(insn.getResult().getReg()); + } + } + + /** {@inheritDoc} */ + public void visitPhiInsn (PhiInsn phi) { + // If we're tracking local vars, then some phis have side effects. + if (!hasSideEffect(phi)) { + noSideEffectRegs.set(phi.getResult().getReg()); + } + } + + /** {@inheritDoc} */ + public void visitNonMoveInsn (NormalSsaInsn insn) { + RegisterSpec result = insn.getResult(); + if (!hasSideEffect(insn) && result != null) { + noSideEffectRegs.set(result.getReg()); + } + } + } +} diff --git a/dx/src/com/android/jack/dx/ssa/DomFront.java b/dx/src/com/android/jack/dx/ssa/DomFront.java new file mode 100644 index 00000000..9e67f464 --- /dev/null +++ b/dx/src/com/android/jack/dx/ssa/DomFront.java @@ -0,0 +1,204 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.ssa; + +import com.android.jack.dx.util.BitIntSet; +import com.android.jack.dx.util.IntSet; +import com.android.jack.dx.util.ListIntSet; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.BitSet; + +/** + * Calculates the dominance-frontiers of a methot's basic blocks. + * Algorithm from "A Simple, Fast Dominance Algorithm" by Cooper, + * Harvey, and Kennedy; transliterated to Java. + */ +public class DomFront { + /** local debug flag */ + private static boolean DEBUG = false; + + /** {@code non-null;} method being processed */ + private final SsaMethod meth; + + private final ArrayList nodes; + + private final DomInfo[] domInfos; + + /** + * Dominance-frontier information for a single basic block. + */ + public static class DomInfo { + /** + * {@code null-ok;} the dominance frontier set indexed by + * block index + */ + public IntSet dominanceFrontiers; + + /** {@code >= 0 after run();} the index of the immediate dominator */ + public int idom = -1; + } + + /** + * Constructs instance. Call {@link DomFront#run} to process. + * + * @param meth {@code non-null;} method to process + */ + public DomFront(SsaMethod meth) { + this.meth = meth; + nodes = meth.getBlocks(); + + int szNodes = nodes.size(); + domInfos = new DomInfo[szNodes]; + + for (int i = 0; i < szNodes; i++) { + domInfos[i] = new DomInfo(); + } + } + + /** + * Calculates the dominance frontier information for the method. + * + * @return {@code non-null;} an array of DomInfo structures + */ + public DomInfo[] run() { + int szNodes = nodes.size(); + + if (DEBUG) { + for (int i = 0; i < szNodes; i++) { + SsaBasicBlock node = nodes.get(i); + System.out.println("pred[" + i + "]: " + + node.getPredecessors()); + } + } + + Dominators methDom = Dominators.make(meth, domInfos, false); + + if (DEBUG) { + for (int i = 0; i < szNodes; i++) { + DomInfo info = domInfos[i]; + System.out.println("idom[" + i + "]: " + + info.idom); + } + } + + buildDomTree(); + + if (DEBUG) { + debugPrintDomChildren(); + } + + for (int i = 0; i < szNodes; i++) { + domInfos[i].dominanceFrontiers + = SetFactory.makeDomFrontSet(szNodes); + } + + calcDomFronts(); + + if (DEBUG) { + for (int i = 0; i < szNodes; i++) { + System.out.println("df[" + i + "]: " + + domInfos[i].dominanceFrontiers); + } + } + + return domInfos; + } + + private void debugPrintDomChildren() { + int szNodes = nodes.size(); + + for (int i = 0; i < szNodes; i++) { + SsaBasicBlock node = nodes.get(i); + StringBuffer sb = new StringBuffer(); + + sb.append('{'); + boolean comma = false; + for (SsaBasicBlock child : node.getDomChildren()) { + if (comma) { + sb.append(','); + } + sb.append(child); + comma = true; + } + sb.append('}'); + + System.out.println("domChildren[" + node + "]: " + + sb); + } + } + + /** + * The dominators algorithm leaves us knowing who the immediate dominator + * is for each node. This sweeps the node list and builds the proper + * dominance tree. + */ + private void buildDomTree() { + int szNodes = nodes.size(); + + for (int i = 0; i < szNodes; i++) { + DomInfo info = domInfos[i]; + + if (info.idom == -1) continue; + + SsaBasicBlock domParent = nodes.get(info.idom); + domParent.addDomChild(nodes.get(i)); + } + } + + /** + * Calculates the dominance-frontier set. + * from "A Simple, Fast Dominance Algorithm" by Cooper, + * Harvey, and Kennedy; transliterated to Java. + */ + private void calcDomFronts() { + int szNodes = nodes.size(); + + for (int b = 0; b < szNodes; b++) { + SsaBasicBlock nb = nodes.get(b); + DomInfo nbInfo = domInfos[b]; + BitSet pred = nb.getPredecessors(); + + if (pred.cardinality() > 1) { + for (int i = pred.nextSetBit(0); i >= 0; + i = pred.nextSetBit(i + 1)) { + + for (int runnerIndex = i; + runnerIndex != nbInfo.idom; /* empty */) { + /* + * We can stop if we hit a block we already + * added label to, since we must be at a part + * of the dom tree we have seen before. + */ + if (runnerIndex == -1) break; + + DomInfo runnerInfo = domInfos[runnerIndex]; + + if (runnerInfo.dominanceFrontiers.has(b)) { + break; + } + + // Add b to runner's dominance frontier set. + runnerInfo.dominanceFrontiers.add(b); + runnerIndex = runnerInfo.idom; + } + } + } + } + } +} diff --git a/dx/src/com/android/jack/dx/ssa/Dominators.java b/dx/src/com/android/jack/dx/ssa/Dominators.java new file mode 100644 index 00000000..b3b4c3fd --- /dev/null +++ b/dx/src/com/android/jack/dx/ssa/Dominators.java @@ -0,0 +1,285 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.ssa; + +import java.util.ArrayList; +import java.util.BitSet; +import java.util.HashSet; + +/** + * This class computes dominator and post-dominator information using the + * Lengauer-Tarjan method. + * + * See A Fast Algorithm for Finding Dominators in a Flowgraph + * T. Lengauer & R. Tarjan, ACM TOPLAS July 1979, pgs 121-141. + * + * This implementation runs in time O(n log n). The time bound + * could be changed to O(n * ack(n)) with a small change to the link and eval, + * and an addition of a child field to the DFS info. In reality, the constant + * overheads are high enough that the current method is faster in all but the + * strangest artificially constructed examples. + * + * The basic idea behind this algorithm is to perform a DFS walk, keeping track + * of various info about parents. We then use this info to calculate the + * dominators, using union-find structures to link together the DFS info, + * then finally evaluate the union-find results to get the dominators. + * This implementation is m log n because it does not perform union by + * rank to keep the union-find tree balanced. + */ +public final class Dominators { + /* postdom is true if we want post dominators */ + private final boolean postdom; + + /* {@code non-null;} method being processed */ + private final SsaMethod meth; + + /* Method's basic blocks. */ + private final ArrayList blocks; + + /** indexed by basic block index */ + private final DFSInfo[] info; + + private final ArrayList vertex; + + /** {@code non-null;} the raw dominator info */ + private final DomFront.DomInfo domInfos[]; + + /** + * Constructs an instance. + * + * @param meth {@code non-null;} method to process + * @param domInfos {@code non-null;} the raw dominator info + * @param postdom true for postdom information, false for normal dom info + */ + private Dominators(SsaMethod meth, DomFront.DomInfo[] domInfos, + boolean postdom) { + this.meth = meth; + this.domInfos = domInfos; + this.postdom = postdom; + this.blocks = meth.getBlocks(); + this.info = new DFSInfo[blocks.size() + 2]; + this.vertex = new ArrayList(); + } + + /** + * Constructs a fully-initialized instance. (This method exists so as + * to avoid calling a large amount of code in the constructor.) + * + * @param meth {@code non-null;} method to process + * @param domInfos {@code non-null;} the raw dominator info + * @param postdom true for postdom information, false for normal dom info + */ + public static Dominators make(SsaMethod meth, DomFront.DomInfo[] domInfos, + boolean postdom) { + Dominators result = new Dominators(meth, domInfos, postdom); + + result.run(); + return result; + } + + private BitSet getSuccs(SsaBasicBlock block) { + if (postdom) { + return block.getPredecessors(); + } else { + return block.getSuccessors(); + } + } + + private BitSet getPreds(SsaBasicBlock block) { + if (postdom) { + return block.getSuccessors(); + } else { + return block.getPredecessors(); + } + } + + /** + * Performs path compress on the DFS info. + * + * @param in Basic block whose DFS info we are path compressing. + */ + private void compress(SsaBasicBlock in) { + DFSInfo bbInfo = info[in.getIndex()]; + DFSInfo ancestorbbInfo = info[bbInfo.ancestor.getIndex()]; + + if (ancestorbbInfo.ancestor != null) { + ArrayList worklist = new ArrayList(); + HashSet visited = new HashSet(); + worklist.add(in); + + while (!worklist.isEmpty()) { + int wsize = worklist.size(); + SsaBasicBlock v = worklist.get(wsize - 1); + DFSInfo vbbInfo = info[v.getIndex()]; + SsaBasicBlock vAncestor = vbbInfo.ancestor; + DFSInfo vabbInfo = info[vAncestor.getIndex()]; + + // Make sure we process our ancestor before ourselves. + if (visited.add(vAncestor) && vabbInfo.ancestor != null) { + worklist.add(vAncestor); + continue; + } + worklist.remove(wsize - 1); + + // Update based on ancestor info. + if (vabbInfo.ancestor == null) { + continue; + } + SsaBasicBlock vAncestorRep = vabbInfo.rep; + SsaBasicBlock vRep = vbbInfo.rep; + if (info[vAncestorRep.getIndex()].semidom + < info[vRep.getIndex()].semidom) { + vbbInfo.rep = vAncestorRep; + } + vbbInfo.ancestor = vabbInfo.ancestor; + } + } + } + + private SsaBasicBlock eval(SsaBasicBlock v) { + DFSInfo bbInfo = info[v.getIndex()]; + + if (bbInfo.ancestor == null) { + return v; + } + + compress(v); + return bbInfo.rep; + } + + /** + * Performs dominator/post-dominator calculation for the control + * flow graph. + * + * @param meth {@code non-null;} method to analyze + */ + private void run() { + SsaBasicBlock root = postdom + ? meth.getExitBlock() : meth.getEntryBlock(); + + if (root != null) { + vertex.add(root); + domInfos[root.getIndex()].idom = root.getIndex(); + } + + /* + * First we perform a DFS numbering of the blocks, by + * numbering the dfs tree roots. + */ + + DfsWalker walker = new DfsWalker(); + meth.forEachBlockDepthFirst(postdom, walker); + + // the largest semidom number assigned + int dfsMax = vertex.size() - 1; + + // Now calculate semidominators. + for (int i = dfsMax; i >= 2; --i) { + SsaBasicBlock w = vertex.get(i); + DFSInfo wInfo = info[w.getIndex()]; + + BitSet preds = getPreds(w); + for (int j = preds.nextSetBit(0); + j >= 0; + j = preds.nextSetBit(j + 1)) { + SsaBasicBlock predBlock = blocks.get(j); + DFSInfo predInfo = info[predBlock.getIndex()]; + + /* + * PredInfo may not exist in case the predecessor is + * not reachable. + */ + if (predInfo != null) { + int predSemidom = info[eval(predBlock).getIndex()].semidom; + if (predSemidom < wInfo.semidom) { + wInfo.semidom = predSemidom; + } + } + } + info[vertex.get(wInfo.semidom).getIndex()].bucket.add(w); + + /* + * Normally we would call link here, but in our O(m log n) + * implementation this is equivalent to the following + * single line. + */ + wInfo.ancestor = wInfo.parent; + + // Implicity define idom for each vertex. + ArrayList wParentBucket; + wParentBucket = info[wInfo.parent.getIndex()].bucket; + + while (!wParentBucket.isEmpty()) { + int lastItem = wParentBucket.size() - 1; + SsaBasicBlock last = wParentBucket.remove(lastItem); + SsaBasicBlock U = eval(last); + if (info[U.getIndex()].semidom + < info[last.getIndex()].semidom) { + domInfos[last.getIndex()].idom = U.getIndex(); + } else { + domInfos[last.getIndex()].idom = wInfo.parent.getIndex(); + } + } + } + + // Now explicitly define the immediate dominator of each vertex + for (int i = 2; i <= dfsMax; ++i) { + SsaBasicBlock w = vertex.get(i); + if (domInfos[w.getIndex()].idom + != vertex.get(info[w.getIndex()].semidom).getIndex()) { + domInfos[w.getIndex()].idom + = domInfos[domInfos[w.getIndex()].idom].idom; + } + } + } + + /** + * Callback for depth-first walk through control flow graph (either + * from the entry block or the exit block). Records the traversal order + * in the {@code info}list. + */ + private class DfsWalker implements SsaBasicBlock.Visitor { + private int dfsNum = 0; + + public void visitBlock(SsaBasicBlock v, SsaBasicBlock parent) { + DFSInfo bbInfo = new DFSInfo(); + bbInfo.semidom = ++dfsNum; + bbInfo.rep = v; + bbInfo.parent = parent; + vertex.add(v); + info[v.getIndex()] = bbInfo; + } + } + + private static final class DFSInfo { + public int semidom; + public SsaBasicBlock parent; + + /** + * rep(resentative) is known as "label" in the paper. It is the node + * that our block's DFS info has been unioned to. + */ + public SsaBasicBlock rep; + + public SsaBasicBlock ancestor; + public ArrayList bucket; + + public DFSInfo() { + bucket = new ArrayList(); + } + } +} diff --git a/dx/src/com/android/jack/dx/ssa/EscapeAnalysis.java b/dx/src/com/android/jack/dx/ssa/EscapeAnalysis.java new file mode 100644 index 00000000..791a3b52 --- /dev/null +++ b/dx/src/com/android/jack/dx/ssa/EscapeAnalysis.java @@ -0,0 +1,843 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.ssa; + +import com.android.jack.dx.rop.code.Exceptions; +import com.android.jack.dx.rop.code.FillArrayDataInsn; +import com.android.jack.dx.rop.code.Insn; +import com.android.jack.dx.rop.code.PlainCstInsn; +import com.android.jack.dx.rop.code.PlainInsn; +import com.android.jack.dx.rop.code.RegOps; +import com.android.jack.dx.rop.code.RegisterSpec; +import com.android.jack.dx.rop.code.RegisterSpecList; +import com.android.jack.dx.rop.code.Rop; +import com.android.jack.dx.rop.code.Rops; +import com.android.jack.dx.rop.code.ThrowingCstInsn; +import com.android.jack.dx.rop.code.ThrowingInsn; +import com.android.jack.dx.rop.cst.Constant; +import com.android.jack.dx.rop.cst.CstLiteralBits; +import com.android.jack.dx.rop.cst.CstMethodRef; +import com.android.jack.dx.rop.cst.CstNat; +import com.android.jack.dx.rop.cst.CstString; +import com.android.jack.dx.rop.cst.CstType; +import com.android.jack.dx.rop.cst.TypedConstant; +import com.android.jack.dx.rop.cst.Zeroes; +import com.android.jack.dx.rop.type.StdTypeList; +import com.android.jack.dx.rop.type.Type; +import com.android.jack.dx.rop.type.TypeBearer; + +import java.util.ArrayList; +import java.util.BitSet; +import java.util.HashSet; +import java.util.List; + +/** + * Simple intraprocedural escape analysis. Finds new arrays that don't escape + * the method they are created in and replaces the array values with registers. + */ +public class EscapeAnalysis { + /** + * Struct used to generate and maintain escape analysis results. + */ + static class EscapeSet { + /** set containing all registers related to an object */ + BitSet regSet; + /** escape state of the object */ + EscapeState escape; + /** list of objects that are put into this object */ + ArrayList childSets; + /** list of objects that this object is put into */ + ArrayList parentSets; + /** flag to indicate this object is a scalar replaceable array */ + boolean replaceableArray; + + /** + * Constructs an instance of an EscapeSet + * + * @param reg the SSA register that defines the object + * @param size the number of registers in the method + * @param escState the lattice value to initially set this to + */ + EscapeSet(int reg, int size, EscapeState escState) { + regSet = new BitSet(size); + regSet.set(reg); + escape = escState; + childSets = new ArrayList(); + parentSets = new ArrayList(); + replaceableArray = false; + } + } + + /** + * Lattice values used to indicate escape state for an object. Analysis can + * only raise escape state values, not lower them. + * + * TOP - Used for objects that haven't been analyzed yet + * NONE - Object does not escape, and is eligible for scalar replacement. + * METHOD - Object remains local to method, but can't be scalar replaced. + * INTER - Object is passed between methods. (treated as globally escaping + * since this is an intraprocedural analysis) + * GLOBAL - Object escapes globally. + */ + public enum EscapeState { + TOP, NONE, METHOD, INTER, GLOBAL + } + + /** method we're processing */ + private SsaMethod ssaMeth; + /** ssaMeth.getRegCount() */ + private int regCount; + /** Lattice values for each object register group */ + private ArrayList latticeValues; + + /** + * Constructs an instance. + * + * @param ssaMeth method to process + */ + private EscapeAnalysis(SsaMethod ssaMeth) { + this.ssaMeth = ssaMeth; + this.regCount = ssaMeth.getRegCount(); + this.latticeValues = new ArrayList(); + } + + /** + * Finds the index in the lattice for a particular register. + * Returns the size of the lattice if the register wasn't found. + * + * @param reg {@code non-null;} register being looked up + * @return index of the register or size of the lattice if it wasn't found. + */ + private int findSetIndex(RegisterSpec reg) { + int i; + for (i = 0; i < latticeValues.size(); i++) { + EscapeSet e = latticeValues.get(i); + if (e.regSet.get(reg.getReg())) { + return i; + } + } + return i; + } + + /** + * Finds the corresponding instruction for a given move result + * + * @param moveInsn {@code non-null;} a move result instruction + * @return {@code non-null;} the instruction that produces the result for + * the move + */ + private SsaInsn getInsnForMove(SsaInsn moveInsn) { + int pred = moveInsn.getBlock().getPredecessors().nextSetBit(0); + ArrayList predInsns = ssaMeth.getBlocks().get(pred).getInsns(); + return predInsns.get(predInsns.size()-1); + } + + /** + * Finds the corresponding move result for a given instruction + * + * @param insn {@code non-null;} an instruction that must always be + * followed by a move result + * @return {@code non-null;} the move result for the given instruction + */ + private SsaInsn getMoveForInsn(SsaInsn insn) { + int succ = insn.getBlock().getSuccessors().nextSetBit(0); + ArrayList succInsns = ssaMeth.getBlocks().get(succ).getInsns(); + return succInsns.get(0); + } + + /** + * Creates a link in the lattice between two EscapeSets due to a put + * instruction. The object being put is the child and the object being put + * into is the parent. A child set must always have an escape state at + * least as high as its parent. + * + * @param parentSet {@code non-null;} the EscapeSet for the object being put + * into + * @param childSet {@code non-null;} the EscapeSet for the object being put + */ + private void addEdge(EscapeSet parentSet, EscapeSet childSet) { + if (!childSet.parentSets.contains(parentSet)) { + childSet.parentSets.add(parentSet); + } + if (!parentSet.childSets.contains(childSet)) { + parentSet.childSets.add(childSet); + } + } + + /** + * Merges all links in the lattice among two EscapeSets. On return, the + * newNode will have its old links as well as all links from the oldNode. + * The oldNode has all its links removed. + * + * @param newNode {@code non-null;} the EscapeSet to merge all links into + * @param oldNode {@code non-null;} the EscapeSet to remove all links from + */ + private void replaceNode(EscapeSet newNode, EscapeSet oldNode) { + for (EscapeSet e : oldNode.parentSets) { + e.childSets.remove(oldNode); + e.childSets.add(newNode); + newNode.parentSets.add(e); + } + for (EscapeSet e : oldNode.childSets) { + e.parentSets.remove(oldNode); + e.parentSets.add(newNode); + newNode.childSets.add(e); + } + } + + /** + * Performs escape analysis on a method. Finds scalar replaceable arrays and + * replaces them with equivalent registers. + * + * @param ssaMethod {@code non-null;} method to process + */ + public static void process(SsaMethod ssaMethod) { + new EscapeAnalysis(ssaMethod).run(); + } + + /** + * Process a single instruction, looking for new objects resulting from + * move result or move param. + * + * @param insn {@code non-null;} instruction to process + */ + private void processInsn(SsaInsn insn) { + int op = insn.getOpcode().getOpcode(); + RegisterSpec result = insn.getResult(); + EscapeSet escSet; + + // Identify new objects + if (op == RegOps.MOVE_RESULT_PSEUDO && + result.getTypeBearer().getBasicType() == Type.BT_OBJECT) { + // Handle objects generated through move_result_pseudo + escSet = processMoveResultPseudoInsn(insn); + processRegister(result, escSet); + } else if (op == RegOps.MOVE_PARAM && + result.getTypeBearer().getBasicType() == Type.BT_OBJECT) { + // Track method arguments that are objects + escSet = new EscapeSet(result.getReg(), regCount, EscapeState.NONE); + latticeValues.add(escSet); + processRegister(result, escSet); + } else if (op == RegOps.MOVE_RESULT && + result.getTypeBearer().getBasicType() == Type.BT_OBJECT) { + // Track method return values that are objects + escSet = new EscapeSet(result.getReg(), regCount, EscapeState.NONE); + latticeValues.add(escSet); + processRegister(result, escSet); + } + } + + /** + * Determine the origin of a move result pseudo instruction that generates + * an object. Creates a new EscapeSet for the new object accordingly. + * + * @param insn {@code non-null;} move result pseudo instruction to process + * @return {@code non-null;} an EscapeSet for the object referred to by the + * move result pseudo instruction + */ + private EscapeSet processMoveResultPseudoInsn(SsaInsn insn) { + RegisterSpec result = insn.getResult(); + SsaInsn prevSsaInsn = getInsnForMove(insn); + int prevOpcode = prevSsaInsn.getOpcode().getOpcode(); + EscapeSet escSet; + RegisterSpec prevSource; + + switch(prevOpcode) { + // New instance / Constant + case RegOps.NEW_INSTANCE: + case RegOps.CONST: + escSet = new EscapeSet(result.getReg(), regCount, + EscapeState.NONE); + break; + // New array + case RegOps.NEW_ARRAY: + case RegOps.FILLED_NEW_ARRAY: + prevSource = prevSsaInsn.getSources().get(0); + if (prevSource.getTypeBearer().isConstant()) { + // New fixed array + escSet = new EscapeSet(result.getReg(), regCount, + EscapeState.NONE); + escSet.replaceableArray = true; + } else { + // New variable array + escSet = new EscapeSet(result.getReg(), regCount, + EscapeState.GLOBAL); + } + break; + // Loading a static object + case RegOps.GET_STATIC: + escSet = new EscapeSet(result.getReg(), regCount, + EscapeState.GLOBAL); + break; + // Type cast / load an object from a field or array + case RegOps.CHECK_CAST: + case RegOps.GET_FIELD: + case RegOps.AGET: + prevSource = prevSsaInsn.getSources().get(0); + int setIndex = findSetIndex(prevSource); + + // Set should already exist, try to find it + if (setIndex != latticeValues.size()) { + escSet = latticeValues.get(setIndex); + escSet.regSet.set(result.getReg()); + return escSet; + } + + // Set not found, must be either null or unknown + if (prevSource.getType() == Type.KNOWN_NULL) { + escSet = new EscapeSet(result.getReg(), regCount, + EscapeState.NONE); + } else { + escSet = new EscapeSet(result.getReg(), regCount, + EscapeState.GLOBAL); + } + break; + default: + return null; + } + + // Add the newly created escSet to the lattice and return it + latticeValues.add(escSet); + return escSet; + } + + /** + * Iterate through all the uses of a new object. + * + * @param result {@code non-null;} register where new object is stored + * @param escSet {@code non-null;} EscapeSet for the new object + */ + private void processRegister(RegisterSpec result, EscapeSet escSet) { + ArrayList regWorklist = new ArrayList(); + regWorklist.add(result); + + // Go through the worklist + while (!regWorklist.isEmpty()) { + int listSize = regWorklist.size() - 1; + RegisterSpec def = regWorklist.remove(listSize); + List useList = ssaMeth.getUseListForRegister(def.getReg()); + + // Handle all the uses of this register + for (SsaInsn use : useList) { + Rop useOpcode = use.getOpcode(); + + if (useOpcode == null) { + // Handle phis + processPhiUse(use, escSet, regWorklist); + } else { + // Handle other opcodes + processUse(def, use, escSet, regWorklist); + } + } + } + } + + /** + * Handles phi uses of new objects. Will merge together the sources of a phi + * into a single EscapeSet. Adds the result of the phi to the worklist so + * its uses can be followed. + * + * @param use {@code non-null;} phi use being processed + * @param escSet {@code non-null;} EscapeSet for the object + * @param regWorklist {@code non-null;} worklist of instructions left to + * process for this object + */ + private void processPhiUse(SsaInsn use, EscapeSet escSet, + ArrayList regWorklist) { + int setIndex = findSetIndex(use.getResult()); + if (setIndex != latticeValues.size()) { + // Check if result is in a set already + EscapeSet mergeSet = latticeValues.get(setIndex); + if (mergeSet != escSet) { + // If it is, merge the sets and states, then delete the copy + escSet.replaceableArray = false; + escSet.regSet.or(mergeSet.regSet); + if (escSet.escape.compareTo(mergeSet.escape) < 0) { + escSet.escape = mergeSet.escape; + } + replaceNode(escSet, mergeSet); + latticeValues.remove(setIndex); + } + } else { + // If no set is found, add it to this escSet and the worklist + escSet.regSet.set(use.getResult().getReg()); + regWorklist.add(use.getResult()); + } + } + + /** + * Handles non-phi uses of new objects. Checks to see how instruction is + * used and updates the escape state accordingly. + * + * @param def {@code non-null;} register holding definition of new object + * @param use {@code non-null;} use of object being processed + * @param escSet {@code non-null;} EscapeSet for the object + * @param regWorklist {@code non-null;} worklist of instructions left to + * process for this object + */ + private void processUse(RegisterSpec def, SsaInsn use, EscapeSet escSet, + ArrayList regWorklist) { + int useOpcode = use.getOpcode().getOpcode(); + switch (useOpcode) { + case RegOps.MOVE: + // Follow uses of the move by adding it to the worklist + escSet.regSet.set(use.getResult().getReg()); + regWorklist.add(use.getResult()); + break; + case RegOps.IF_EQ: + case RegOps.IF_NE: + case RegOps.CHECK_CAST: + // Compared objects can't be replaced, so promote if necessary + if (escSet.escape.compareTo(EscapeState.METHOD) < 0) { + escSet.escape = EscapeState.METHOD; + } + break; + case RegOps.APUT: + // For array puts, check for a constant array index + RegisterSpec putIndex = use.getSources().get(2); + if (!putIndex.getTypeBearer().isConstant()) { + // If not constant, array can't be replaced + escSet.replaceableArray = false; + } + // Intentional fallthrough + case RegOps.PUT_FIELD: + // Skip non-object puts + RegisterSpec putValue = use.getSources().get(0); + if (putValue.getTypeBearer().getBasicType() != Type.BT_OBJECT) { + break; + } + escSet.replaceableArray = false; + + // Raise 1st object's escape state to 2nd if 2nd is higher + RegisterSpecList sources = use.getSources(); + if (sources.get(0).getReg() == def.getReg()) { + int setIndex = findSetIndex(sources.get(1)); + if (setIndex != latticeValues.size()) { + EscapeSet parentSet = latticeValues.get(setIndex); + addEdge(parentSet, escSet); + if (escSet.escape.compareTo(parentSet.escape) < 0) { + escSet.escape = parentSet.escape; + } + } + } else { + int setIndex = findSetIndex(sources.get(0)); + if (setIndex != latticeValues.size()) { + EscapeSet childSet = latticeValues.get(setIndex); + addEdge(escSet, childSet); + if (childSet.escape.compareTo(escSet.escape) < 0) { + childSet.escape = escSet.escape; + } + } + } + break; + case RegOps.AGET: + // For array gets, check for a constant array index + RegisterSpec getIndex = use.getSources().get(1); + if (!getIndex.getTypeBearer().isConstant()) { + // If not constant, array can't be replaced + escSet.replaceableArray = false; + } + break; + case RegOps.PUT_STATIC: + // Static puts cause an object to escape globally + escSet.escape = EscapeState.GLOBAL; + break; + case RegOps.INVOKE_STATIC: + case RegOps.INVOKE_VIRTUAL: + case RegOps.INVOKE_SUPER: + case RegOps.INVOKE_DIRECT: + case RegOps.INVOKE_INTERFACE: + case RegOps.RETURN: + case RegOps.THROW: + // These operations cause an object to escape interprocedurally + escSet.escape = EscapeState.INTER; + break; + default: + break; + } + } + + /** + * Performs scalar replacement on all eligible arrays. + */ + private void scalarReplacement() { + // Iterate through lattice, looking for non-escaping replaceable arrays + for (EscapeSet escSet : latticeValues) { + if (!escSet.replaceableArray || escSet.escape != EscapeState.NONE) { + continue; + } + + // Get the instructions for the definition and move of the array + int e = escSet.regSet.nextSetBit(0); + SsaInsn def = ssaMeth.getDefinitionForRegister(e); + SsaInsn prev = getInsnForMove(def); + + // Create a map for the new registers that will be created + TypeBearer lengthReg = prev.getSources().get(0).getTypeBearer(); + int length = ((CstLiteralBits) lengthReg).getIntBits(); + ArrayList newRegs = + new ArrayList(length); + HashSet deletedInsns = new HashSet(); + + // Replace the definition of the array with registers + replaceDef(def, prev, length, newRegs); + + // Mark definition instructions for deletion + deletedInsns.add(prev); + deletedInsns.add(def); + + // Go through all uses of the array + List useList = ssaMeth.getUseListForRegister(e); + for (SsaInsn use : useList) { + // Replace the use with scalars and then mark it for deletion + replaceUse(use, prev, newRegs, deletedInsns); + deletedInsns.add(use); + } + + // Delete all marked instructions + ssaMeth.deleteInsns(deletedInsns); + ssaMeth.onInsnsChanged(); + + // Convert the method back to SSA form + SsaConverter.updateSsaMethod(ssaMeth, regCount); + + // Propagate and remove extra moves added by scalar replacement + movePropagate(); + } + } + + /** + * Replaces the instructions that define an array with equivalent registers. + * For each entry in the array, a register is created, initialized to zero. + * A mapping between this register and the corresponding array index is + * added. + * + * @param def {@code non-null;} move result instruction for array + * @param prev {@code non-null;} instruction for instantiating new array + * @param length size of the new array + * @param newRegs {@code non-null;} mapping of array indices to new + * registers to be populated + */ + private void replaceDef(SsaInsn def, SsaInsn prev, int length, + ArrayList newRegs) { + Type resultType = def.getResult().getType(); + + // Create new zeroed out registers for each element in the array + for (int i = 0; i < length; i++) { + Constant newZero = Zeroes.zeroFor(resultType.getComponentType()); + TypedConstant typedZero = (TypedConstant) newZero; + RegisterSpec newReg = + RegisterSpec.make(ssaMeth.makeNewSsaReg(), typedZero); + newRegs.add(newReg); + insertPlainInsnBefore(def, RegisterSpecList.EMPTY, newReg, + RegOps.CONST, newZero); + } + } + + /** + * Replaces the use for a scalar replaceable array. Gets and puts become + * move instructions, and array lengths and fills are handled. Can also + * identify ArrayIndexOutOfBounds exceptions and throw them if detected. + * + * @param use {@code non-null;} move result instruction for array + * @param prev {@code non-null;} instruction for instantiating new array + * @param newRegs {@code non-null;} mapping of array indices to new + * registers + * @param deletedInsns {@code non-null;} set of instructions marked for + * deletion + */ + private void replaceUse(SsaInsn use, SsaInsn prev, + ArrayList newRegs, + HashSet deletedInsns) { + int index; + int length = newRegs.size(); + SsaInsn next; + RegisterSpecList sources; + RegisterSpec source, result; + CstLiteralBits indexReg; + + switch (use.getOpcode().getOpcode()) { + case RegOps.AGET: + // Replace array gets with moves + next = getMoveForInsn(use); + sources = use.getSources(); + indexReg = ((CstLiteralBits) sources.get(1).getTypeBearer()); + index = indexReg.getIntBits(); + if (index < length) { + source = newRegs.get(index); + result = source.withReg(next.getResult().getReg()); + insertPlainInsnBefore(next, RegisterSpecList.make(source), + result, RegOps.MOVE, null); + } else { + // Throw an exception if the index is out of bounds + insertExceptionThrow(next, sources.get(1), deletedInsns); + deletedInsns.add(next.getBlock().getInsns().get(2)); + } + deletedInsns.add(next); + break; + case RegOps.APUT: + // Replace array puts with moves + sources = use.getSources(); + indexReg = ((CstLiteralBits) sources.get(2).getTypeBearer()); + index = indexReg.getIntBits(); + if (index < length) { + source = sources.get(0); + result = source.withReg(newRegs.get(index).getReg()); + insertPlainInsnBefore(use, RegisterSpecList.make(source), + result, RegOps.MOVE, null); + // Update the newReg entry to mark value as unknown now + newRegs.set(index, result.withSimpleType()); + } else { + // Throw an exception if the index is out of bounds + insertExceptionThrow(use, sources.get(2), deletedInsns); + } + break; + case RegOps.ARRAY_LENGTH: + // Replace array lengths with const instructions + TypeBearer lengthReg = prev.getSources().get(0).getTypeBearer(); + //CstInteger lengthReg = CstInteger.make(length); + next = getMoveForInsn(use); + insertPlainInsnBefore(next, RegisterSpecList.EMPTY, + next.getResult(), RegOps.CONST, + (Constant) lengthReg); + deletedInsns.add(next); + break; + case RegOps.MARK_LOCAL: + // Remove mark local instructions + break; + case RegOps.FILL_ARRAY_DATA: + // Create const instructions for each fill value + Insn ropUse = use.getOriginalRopInsn(); + FillArrayDataInsn fill = (FillArrayDataInsn) ropUse; + ArrayList constList = fill.getInitValues(); + for (int i = 0; i < length; i++) { + RegisterSpec newFill = + RegisterSpec.make(newRegs.get(i).getReg(), + (TypeBearer) constList.get(i)); + insertPlainInsnBefore(use, RegisterSpecList.EMPTY, newFill, + RegOps.CONST, constList.get(i)); + // Update the newRegs to hold the new const value + newRegs.set(i, newFill); + } + break; + default: + } + } + + /** + * Identifies extra moves added by scalar replacement and propagates the + * source of the move to any users of the result. + */ + private void movePropagate() { + for (int i = 0; i < ssaMeth.getRegCount(); i++) { + SsaInsn insn = ssaMeth.getDefinitionForRegister(i); + + // Look for move instructions only + if (insn == null || insn.getOpcode() == null || + insn.getOpcode().getOpcode() != RegOps.MOVE) { + continue; + } + + final ArrayList[] useList = ssaMeth.getUseListCopy(); + final RegisterSpec source = insn.getSources().get(0); + final RegisterSpec result = insn.getResult(); + + // Ignore moves that weren't added due to scalar replacement + if (source.getReg() < regCount && result.getReg() < regCount) { + continue; + } + + // Create a mapping from source to result + RegisterMapper mapper = new RegisterMapper() { + @Override + public int getNewRegisterCount() { + return ssaMeth.getRegCount(); + } + + @Override + public RegisterSpec map(RegisterSpec registerSpec) { + if (registerSpec.getReg() == result.getReg()) { + return source; + } + + return registerSpec; + } + }; + + // Modify all uses of the move to use the source of the move instead + for (SsaInsn use : useList[result.getReg()]) { + use.mapSourceRegisters(mapper); + } + } + } + + /** + * Runs escape analysis and scalar replacement of arrays. + */ + private void run() { + ssaMeth.forEachBlockDepthFirstDom(new SsaBasicBlock.Visitor() { + public void visitBlock (SsaBasicBlock block, + SsaBasicBlock unused) { + block.forEachInsn(new SsaInsn.Visitor() { + public void visitMoveInsn(NormalSsaInsn insn) { + // do nothing + } + + public void visitPhiInsn(PhiInsn insn) { + // do nothing + } + + public void visitNonMoveInsn(NormalSsaInsn insn) { + processInsn(insn); + } + }); + } + }); + + // Go through lattice and promote fieldSets as necessary + for (EscapeSet e : latticeValues) { + if (e.escape != EscapeState.NONE) { + for (EscapeSet field : e.childSets) { + if (e.escape.compareTo(field.escape) > 0) { + field.escape = e.escape; + } + } + } + } + + // Perform scalar replacement for arrays + scalarReplacement(); + } + + /** + * Replaces instructions that trigger an ArrayIndexOutofBounds exception + * with an actual throw of the exception. + * + * @param insn {@code non-null;} instruction causing the exception + * @param index {@code non-null;} index value that is out of bounds + * @param deletedInsns {@code non-null;} set of instructions marked for + * deletion + */ + private void insertExceptionThrow(SsaInsn insn, RegisterSpec index, + HashSet deletedInsns) { + // Create a new ArrayIndexOutOfBoundsException + CstType exception = + new CstType(Exceptions.TYPE_ArrayIndexOutOfBoundsException); + insertThrowingInsnBefore(insn, RegisterSpecList.EMPTY, null, + RegOps.NEW_INSTANCE, exception); + + // Add a successor block with a move result pseudo for the exception + SsaBasicBlock currBlock = insn.getBlock(); + SsaBasicBlock newBlock = + currBlock.insertNewSuccessor(currBlock.getPrimarySuccessor()); + SsaInsn newInsn = newBlock.getInsns().get(0); + RegisterSpec newReg = + RegisterSpec.make(ssaMeth.makeNewSsaReg(), exception); + insertPlainInsnBefore(newInsn, RegisterSpecList.EMPTY, newReg, + RegOps.MOVE_RESULT_PSEUDO, null); + + // Add another successor block to initialize the exception + SsaBasicBlock newBlock2 = + newBlock.insertNewSuccessor(newBlock.getPrimarySuccessor()); + SsaInsn newInsn2 = newBlock2.getInsns().get(0); + CstNat newNat = new CstNat(new CstString(""), new CstString("(I)V")); + CstMethodRef newRef = new CstMethodRef(exception, newNat); + insertThrowingInsnBefore(newInsn2, RegisterSpecList.make(newReg, index), + null, RegOps.INVOKE_DIRECT, newRef); + deletedInsns.add(newInsn2); + + // Add another successor block to throw the new exception + SsaBasicBlock newBlock3 = + newBlock2.insertNewSuccessor(newBlock2.getPrimarySuccessor()); + SsaInsn newInsn3 = newBlock3.getInsns().get(0); + insertThrowingInsnBefore(newInsn3, RegisterSpecList.make(newReg), null, + RegOps.THROW, null); + newBlock3.replaceSuccessor(newBlock3.getPrimarySuccessorIndex(), + ssaMeth.getExitBlock().getIndex()); + deletedInsns.add(newInsn3); + } + + /** + * Inserts a new PlainInsn before the given instruction. + * TODO: move this somewhere more appropriate + * + * @param insn {@code non-null;} instruction to insert before + * @param newSources {@code non-null;} sources of new instruction + * @param newResult {@code non-null;} result of new instruction + * @param newOpcode opcode of new instruction + * @param cst {@code null-ok;} constant for new instruction, if any + */ + private void insertPlainInsnBefore(SsaInsn insn, + RegisterSpecList newSources, RegisterSpec newResult, int newOpcode, + Constant cst) { + + Insn originalRopInsn = insn.getOriginalRopInsn(); + Rop newRop; + if (newOpcode == RegOps.MOVE_RESULT_PSEUDO) { + newRop = Rops.opMoveResultPseudo(newResult.getType()); + } else { + newRop = Rops.ropFor(newOpcode, newResult, newSources, cst); + } + + Insn newRopInsn; + if (cst == null) { + newRopInsn = new PlainInsn(newRop, + originalRopInsn.getPosition(), newResult, newSources); + } else { + newRopInsn = new PlainCstInsn(newRop, + originalRopInsn.getPosition(), newResult, newSources, cst); + } + + NormalSsaInsn newInsn = new NormalSsaInsn(newRopInsn, insn.getBlock()); + List insns = insn.getBlock().getInsns(); + + insns.add(insns.lastIndexOf(insn), newInsn); + ssaMeth.onInsnAdded(newInsn); + } + + /** + * Inserts a new ThrowingInsn before the given instruction. + * TODO: move this somewhere more appropriate + * + * @param insn {@code non-null;} instruction to insert before + * @param newSources {@code non-null;} sources of new instruction + * @param newResult {@code non-null;} result of new instruction + * @param newOpcode opcode of new instruction + * @param cst {@code null-ok;} constant for new instruction, if any + */ + private void insertThrowingInsnBefore(SsaInsn insn, + RegisterSpecList newSources, RegisterSpec newResult, int newOpcode, + Constant cst) { + + Insn origRopInsn = insn.getOriginalRopInsn(); + Rop newRop = Rops.ropFor(newOpcode, newResult, newSources, cst); + Insn newRopInsn; + if (cst == null) { + newRopInsn = new ThrowingInsn(newRop, + origRopInsn.getPosition(), newSources, StdTypeList.EMPTY); + } else { + newRopInsn = new ThrowingCstInsn(newRop, + origRopInsn.getPosition(), newSources, StdTypeList.EMPTY, cst); + } + + NormalSsaInsn newInsn = new NormalSsaInsn(newRopInsn, insn.getBlock()); + List insns = insn.getBlock().getInsns(); + + insns.add(insns.lastIndexOf(insn), newInsn); + ssaMeth.onInsnAdded(newInsn); + } +} diff --git a/dx/src/com/android/jack/dx/ssa/InterferenceRegisterMapper.java b/dx/src/com/android/jack/dx/ssa/InterferenceRegisterMapper.java new file mode 100644 index 00000000..589e2f28 --- /dev/null +++ b/dx/src/com/android/jack/dx/ssa/InterferenceRegisterMapper.java @@ -0,0 +1,164 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.ssa; + +import com.android.jack.dx.rop.code.RegisterSpec; +import com.android.jack.dx.rop.code.RegisterSpecList; +import com.android.jack.dx.ssa.back.InterferenceGraph; +import com.android.jack.dx.util.BitIntSet; +import com.android.jack.dx.util.IntSet; + +import java.util.ArrayList; +import java.util.BitSet; + +/** + * A register mapper that keeps track of the accumulated interference + * information for the registers in the new namespace. + * + * Please note that this mapper requires that the old namespace does not + * have variable register widths/categories, and the new namespace does. + */ +public class InterferenceRegisterMapper extends BasicRegisterMapper { + /** + * Array of interference sets. ArrayList is indexed by new namespace + * and BitIntSet's are indexed by old namespace. The list expands + * as needed and missing items are assumed to interfere with nothing. + * + * Bit sets are always used here, unlike elsewhere, because the max + * size of this matrix will be (countSsaRegs * countRopRegs), which may + * grow to hundreds of K but not megabytes. + */ + private final ArrayList newRegInterference; + + /** the interference graph for the old namespace */ + private final InterferenceGraph oldRegInterference; + + /** + * Constructs an instance + * + * @param countOldRegisters number of registers in old namespace + */ + public InterferenceRegisterMapper(InterferenceGraph oldRegInterference, + int countOldRegisters) { + super(countOldRegisters); + + newRegInterference = new ArrayList(); + this.oldRegInterference = oldRegInterference; + } + + /** {@inheritDoc} */ + @Override + public void addMapping(int oldReg, int newReg, int category) { + super.addMapping(oldReg, newReg, category); + + addInterfence(newReg, oldReg); + + if (category == 2) { + addInterfence(newReg + 1, oldReg); + } + } + + /** + * Checks to see if old namespace reg {@code oldReg} interferes + * with what currently maps to {@code newReg}. + * + * @param oldReg old namespace register + * @param newReg new namespace register + * @param category category of old namespace register + * @return true if oldReg will interfere with newReg + */ + public boolean interferes(int oldReg, int newReg, int category) { + if (newReg >= newRegInterference.size()) { + return false; + } else { + IntSet existing = newRegInterference.get(newReg); + + if (existing == null) { + return false; + } else if (category == 1) { + return existing.has(oldReg); + } else { + return existing.has(oldReg) + || (interferes(oldReg, newReg+1, category-1)); + } + } + } + + /** + * Checks to see if old namespace reg {@code oldReg} interferes + * with what currently maps to {@code newReg}. + * + * @param oldSpec {@code non-null;} old namespace register + * @param newReg new namespace register + * @return true if oldReg will interfere with newReg + */ + public boolean interferes(RegisterSpec oldSpec, int newReg) { + return interferes(oldSpec.getReg(), newReg, oldSpec.getCategory()); + } + + /** + * Adds a register's interference set to the interference list, + * growing it if necessary. + * + * @param newReg register in new namespace + * @param oldReg register in old namespace + */ + private void addInterfence(int newReg, int oldReg) { + newRegInterference.ensureCapacity(newReg + 1); + + while (newReg >= newRegInterference.size()) { + newRegInterference.add(new BitIntSet(newReg +1)); + } + + oldRegInterference.mergeInterferenceSet( + oldReg, newRegInterference.get(newReg)); + } + + /** + * Checks to see if any of a set of old-namespace registers are + * pinned to the specified new-namespace reg + category. Takes into + * account the category of the old-namespace registers. + * + * @param oldSpecs {@code non-null;} set of old-namespace regs + * @param newReg {@code >= 0;} new-namespace register + * @param targetCategory {@code 1..2;} the number of adjacent new-namespace + * registers (starting at ropReg) to consider + * @return true if any of the old-namespace register have been mapped + * to the new-namespace register + category + */ + public boolean areAnyPinned(RegisterSpecList oldSpecs, + int newReg, int targetCategory) { + int sz = oldSpecs.size(); + + for (int i = 0; i < sz; i++) { + RegisterSpec oldSpec = oldSpecs.get(i); + int r = oldToNew(oldSpec.getReg()); + + /* + * If oldSpec is a category-2 register, then check both newReg + * and newReg - 1. + */ + if (r == newReg + || (oldSpec.getCategory() == 2 && (r + 1) == newReg) + || (targetCategory == 2 && (r == newReg + 1))) { + return true; + } + } + + return false; + } +} diff --git a/dx/src/com/android/jack/dx/ssa/LiteralOpUpgrader.java b/dx/src/com/android/jack/dx/ssa/LiteralOpUpgrader.java new file mode 100644 index 00000000..ebf94019 --- /dev/null +++ b/dx/src/com/android/jack/dx/ssa/LiteralOpUpgrader.java @@ -0,0 +1,207 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.ssa; + +import com.android.jack.dx.rop.code.Insn; +import com.android.jack.dx.rop.code.PlainCstInsn; +import com.android.jack.dx.rop.code.PlainInsn; +import com.android.jack.dx.rop.code.RegOps; +import com.android.jack.dx.rop.code.RegisterSpec; +import com.android.jack.dx.rop.code.RegisterSpecList; +import com.android.jack.dx.rop.code.Rop; +import com.android.jack.dx.rop.code.Rops; +import com.android.jack.dx.rop.code.TranslationAdvice; +import com.android.jack.dx.rop.cst.Constant; +import com.android.jack.dx.rop.cst.CstLiteralBits; +import com.android.jack.dx.rop.type.Type; +import com.android.jack.dx.rop.type.TypeBearer; + +import java.util.ArrayList; +import java.util.List; + +/** + * Upgrades insn to their literal (constant-immediate) equivalent if possible. + * Also switches IF instructions that compare with a constant zero or null + * to be their IF_*Z equivalents. + */ +public class LiteralOpUpgrader { + + /** method we're processing */ + private final SsaMethod ssaMeth; + + /** + * Process a method. + * + * @param ssaMethod {@code non-null;} method to process + */ + public static void process(SsaMethod ssaMethod) { + LiteralOpUpgrader dc; + + dc = new LiteralOpUpgrader(ssaMethod); + + dc.run(); + } + + private LiteralOpUpgrader(SsaMethod ssaMethod) { + this.ssaMeth = ssaMethod; + } + + /** + * Returns true if the register contains an integer 0 or a known-null + * object reference + * + * @param spec non-null spec + * @return true for 0 or null type bearers + */ + private static boolean isConstIntZeroOrKnownNull(RegisterSpec spec) { + TypeBearer tb = spec.getTypeBearer(); + if (tb instanceof CstLiteralBits) { + CstLiteralBits clb = (CstLiteralBits) tb; + return (clb.getLongBits() == 0); + } + return false; + } + + /** + * Run the literal op upgrader + */ + private void run() { + final TranslationAdvice advice = Optimizer.getAdvice(); + + ssaMeth.forEachInsn(new SsaInsn.Visitor() { + public void visitMoveInsn(NormalSsaInsn insn) { + // do nothing + } + + public void visitPhiInsn(PhiInsn insn) { + // do nothing + } + + public void visitNonMoveInsn(NormalSsaInsn insn) { + + Insn originalRopInsn = insn.getOriginalRopInsn(); + Rop opcode = originalRopInsn.getOpcode(); + RegisterSpecList sources = insn.getSources(); + + // Replace insns with constant results with const insns + if (tryReplacingWithConstant(insn)) return; + + if (sources.size() != 2 ) { + // We're only dealing with two-source insns here. + return; + } + + if (opcode.getBranchingness() == Rop.BRANCH_IF) { + /* + * An if instruction can become an if-*z instruction. + */ + if (isConstIntZeroOrKnownNull(sources.get(0))) { + replacePlainInsn(insn, sources.withoutFirst(), + RegOps.flippedIfOpcode(opcode.getOpcode()), null); + } else if (isConstIntZeroOrKnownNull(sources.get(1))) { + replacePlainInsn(insn, sources.withoutLast(), + opcode.getOpcode(), null); + } + } else if (advice.hasConstantOperation( + opcode, sources.get(0), sources.get(1))) { + insn.upgradeToLiteral(); + } else if (opcode.isCommutative() + && advice.hasConstantOperation( + opcode, sources.get(1), sources.get(0))) { + /* + * An instruction can be commuted to a literal operation + */ + + insn.setNewSources( + RegisterSpecList.make( + sources.get(1), sources.get(0))); + + insn.upgradeToLiteral(); + } + } + }); + } + + /** + * Tries to replace an instruction with a const instruction. The given + * instruction must have a constant result for it to be replaced. + * + * @param insn {@code non-null;} instruction to try to replace + * @return true if the instruction was replaced + */ + private boolean tryReplacingWithConstant(NormalSsaInsn insn) { + Insn originalRopInsn = insn.getOriginalRopInsn(); + Rop opcode = originalRopInsn.getOpcode(); + RegisterSpec result = insn.getResult(); + + if (result != null && !ssaMeth.isRegALocal(result) && + opcode.getOpcode() != RegOps.CONST) { + TypeBearer type = insn.getResult().getTypeBearer(); + if (type.isConstant() && type.getBasicType() == Type.BT_INT) { + // Replace the instruction with a constant + replacePlainInsn(insn, RegisterSpecList.EMPTY, + RegOps.CONST, (Constant) type); + + // Remove the source as well if this is a move-result-pseudo + if (opcode.getOpcode() == RegOps.MOVE_RESULT_PSEUDO) { + int pred = insn.getBlock().getPredecessors().nextSetBit(0); + ArrayList predInsns = + ssaMeth.getBlocks().get(pred).getInsns(); + NormalSsaInsn sourceInsn = + (NormalSsaInsn) predInsns.get(predInsns.size()-1); + replacePlainInsn(sourceInsn, RegisterSpecList.EMPTY, + RegOps.GOTO, null); + } + return true; + } + } + return false; + } + + /** + * Replaces an SsaInsn containing a PlainInsn with a new PlainInsn. The + * new PlainInsn is constructed with a new RegOp and new sources. + * + * TODO move this somewhere else. + * + * @param insn {@code non-null;} an SsaInsn containing a PlainInsn + * @param newSources {@code non-null;} new sources list for new insn + * @param newOpcode A RegOp from {@link RegOps} + * @param cst {@code null-ok;} constant for new instruction, if any + */ + private void replacePlainInsn(NormalSsaInsn insn, + RegisterSpecList newSources, int newOpcode, Constant cst) { + + Insn originalRopInsn = insn.getOriginalRopInsn(); + Rop newRop = Rops.ropFor(newOpcode, insn.getResult(), newSources, cst); + Insn newRopInsn; + if (cst == null) { + newRopInsn = new PlainInsn(newRop, originalRopInsn.getPosition(), + insn.getResult(), newSources); + } else { + newRopInsn = new PlainCstInsn(newRop, originalRopInsn.getPosition(), + insn.getResult(), newSources, cst); + } + NormalSsaInsn newInsn = new NormalSsaInsn(newRopInsn, insn.getBlock()); + + List insns = insn.getBlock().getInsns(); + + ssaMeth.onInsnRemoved(insn); + insns.set(insns.lastIndexOf(insn), newInsn); + ssaMeth.onInsnAdded(newInsn); + } +} diff --git a/dx/src/com/android/jack/dx/ssa/LocalVariableExtractor.java b/dx/src/com/android/jack/dx/ssa/LocalVariableExtractor.java new file mode 100644 index 00000000..a16eeb19 --- /dev/null +++ b/dx/src/com/android/jack/dx/ssa/LocalVariableExtractor.java @@ -0,0 +1,208 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.ssa; + +import com.android.jack.dx.rop.code.RegisterSpec; +import com.android.jack.dx.rop.code.RegisterSpecSet; +import com.android.jack.dx.util.IntList; + +import java.util.ArrayList; +import java.util.BitSet; +import java.util.List; + +/** + * Code to figure out which local variables are active at which points in + * a method. Stolen and retrofitted from + * com.android.jack.dx.rop.code.LocalVariableExtractor + * + * TODO remove this. Allow Rop-form LocalVariableInfo to be passed in, + * converted, and adapted through edge-splitting. + */ +public class LocalVariableExtractor { + /** {@code non-null;} method being extracted from */ + private final SsaMethod method; + + /** {@code non-null;} block list for the method */ + private final ArrayList blocks; + + /** {@code non-null;} result in-progress */ + private final LocalVariableInfo resultInfo; + + /** {@code non-null;} work set indicating blocks needing to be processed */ + private final BitSet workSet; + + /** + * Extracts out all the local variable information from the given method. + * + * @param method {@code non-null;} the method to extract from + * @return {@code non-null;} the extracted information + */ + public static LocalVariableInfo extract(SsaMethod method) { + LocalVariableExtractor lve = new LocalVariableExtractor(method); + return lve.doit(); + } + + /** + * Constructs an instance. This method is private. Use {@link #extract}. + * + * @param method {@code non-null;} the method to extract from + */ + private LocalVariableExtractor(SsaMethod method) { + if (method == null) { + throw new NullPointerException("method == null"); + } + + ArrayList blocks = method.getBlocks(); + + this.method = method; + this.blocks = blocks; + this.resultInfo = new LocalVariableInfo(method); + this.workSet = new BitSet(blocks.size()); + } + + /** + * Does the extraction. + * + * @return {@code non-null;} the extracted information + */ + private LocalVariableInfo doit() { + + //FIXME why is this needed here? + if (method.getRegCount() > 0 ) { + for (int bi = method.getEntryBlockIndex(); + bi >= 0; + bi = workSet.nextSetBit(0)) { + workSet.clear(bi); + processBlock(bi); + } + } + + resultInfo.setImmutable(); + return resultInfo; + } + + /** + * Processes a single block. + * + * @param blockIndex {@code >= 0;} block index of the block to process + */ + private void processBlock(int blockIndex) { + RegisterSpecSet primaryState + = resultInfo.mutableCopyOfStarts(blockIndex); + SsaBasicBlock block = blocks.get(blockIndex); + List insns = block.getInsns(); + int insnSz = insns.size(); + + // The exit block has no insns and no successors + if (blockIndex == method.getExitBlockIndex()) { + return; + } + + /* + * We may have to treat the last instruction specially: If it + * can (but doesn't always) throw, and the exception can be + * caught within the same method, then we need to use the + * state *before* executing it to be what is merged into + * exception targets. + */ + SsaInsn lastInsn = insns.get(insnSz - 1); + boolean hasExceptionHandlers + = lastInsn.getOriginalRopInsn().getCatches().size() !=0 ; + boolean canThrowDuringLastInsn = hasExceptionHandlers + && (lastInsn.getResult() != null); + int freezeSecondaryStateAt = insnSz - 1; + RegisterSpecSet secondaryState = primaryState; + + /* + * Iterate over the instructions, adding information for each place + * that the active variable set changes. + */ + + for (int i = 0; i < insnSz; i++) { + if (canThrowDuringLastInsn && (i == freezeSecondaryStateAt)) { + // Until this point, primaryState == secondaryState. + primaryState.setImmutable(); + primaryState = primaryState.mutableCopy(); + } + + SsaInsn insn = insns.get(i); + RegisterSpec result; + + result = insn.getLocalAssignment(); + + if (result == null) { + // We may be nuking an existing local + + result = insn.getResult(); + + if (result != null && primaryState.get(result.getReg()) != null) { + primaryState.remove(primaryState.get(result.getReg())); + } + continue; + } + + result = result.withSimpleType(); + + RegisterSpec already = primaryState.get(result); + /* + * The equals() check ensures we only add new info if + * the instruction causes a change to the set of + * active variables. + */ + if (!result.equals(already)) { + /* + * If this insn represents a local moving from one register + * to another, remove the association between the old register + * and the local. + */ + RegisterSpec previous + = primaryState.localItemToSpec(result.getLocalItem()); + + if (previous != null + && (previous.getReg() != result.getReg())) { + + primaryState.remove(previous); + } + + resultInfo.addAssignment(insn, result); + primaryState.put(result); + } + } + + primaryState.setImmutable(); + + /* + * Merge this state into the start state for each successor, + * and update the work set where required (that is, in cases + * where the start state for a block changes). + */ + + IntList successors = block.getSuccessorList(); + int succSz = successors.size(); + int primarySuccessor = block.getPrimarySuccessorIndex(); + + for (int i = 0; i < succSz; i++) { + int succ = successors.get(i); + RegisterSpecSet state = (succ == primarySuccessor) ? + primaryState : secondaryState; + + if (resultInfo.mergeStarts(succ, state)) { + workSet.set(succ); + } + } + } +} diff --git a/dx/src/com/android/jack/dx/ssa/LocalVariableInfo.java b/dx/src/com/android/jack/dx/ssa/LocalVariableInfo.java new file mode 100644 index 00000000..0ff957c0 --- /dev/null +++ b/dx/src/com/android/jack/dx/ssa/LocalVariableInfo.java @@ -0,0 +1,251 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.ssa; + +import com.android.jack.dx.rop.code.RegisterSpec; +import com.android.jack.dx.rop.code.RegisterSpecSet; +import com.android.jack.dx.util.MutabilityControl; + +import java.util.HashMap; +import java.util.List; + +/** + * Container for local variable information for a particular {@link + * com.android.jack.dx.ssa.SsaMethod}. + * Stolen from {@link com.android.jack.dx.rop.code.LocalVariableInfo}. + */ +public class LocalVariableInfo extends MutabilityControl { + /** {@code >= 0;} the register count for the method */ + private final int regCount; + + /** + * {@code non-null;} {@link com.android.jack.dx.rop.code.RegisterSpecSet} to use when indicating a block + * that has no locals; it is empty and immutable but has an appropriate + * max size for the method + */ + private final RegisterSpecSet emptySet; + + /** + * {@code non-null;} array consisting of register sets representing the + * sets of variables already assigned upon entry to each block, + * where array indices correspond to block indices + */ + private final RegisterSpecSet[] blockStarts; + + /** {@code non-null;} map from instructions to the variable each assigns */ + private final HashMap insnAssignments; + + /** + * Constructs an instance. + * + * @param method {@code non-null;} the method being represented by this instance + */ + public LocalVariableInfo(SsaMethod method) { + if (method == null) { + throw new NullPointerException("method == null"); + } + + List blocks = method.getBlocks(); + + this.regCount = method.getRegCount(); + this.emptySet = new RegisterSpecSet(regCount); + this.blockStarts = new RegisterSpecSet[blocks.size()]; + this.insnAssignments = + new HashMap(/*hint here*/); + + emptySet.setImmutable(); + } + + /** + * Sets the register set associated with the start of the block with + * the given index. + * + * @param index {@code >= 0;} the block index + * @param specs {@code non-null;} the register set to associate with the block + */ + public void setStarts(int index, RegisterSpecSet specs) { + throwIfImmutable(); + + if (specs == null) { + throw new NullPointerException("specs == null"); + } + + try { + blockStarts[index] = specs; + } catch (ArrayIndexOutOfBoundsException ex) { + // Translate the exception. + throw new IllegalArgumentException("bogus index"); + } + } + + /** + * Merges the given register set into the set for the block with the + * given index. If there was not already an associated set, then this + * is the same as calling {@link #setStarts}. Otherwise, this will + * merge the two sets and call {@link #setStarts} on the result of the + * merge. + * + * @param index {@code >= 0;} the block index + * @param specs {@code non-null;} the register set to merge into the start set + * for the block + * @return {@code true} if the merge resulted in an actual change + * to the associated set (including storing one for the first time) or + * {@code false} if there was no change + */ + public boolean mergeStarts(int index, RegisterSpecSet specs) { + RegisterSpecSet start = getStarts0(index); + boolean changed = false; + + if (start == null) { + setStarts(index, specs); + return true; + } + + RegisterSpecSet newStart = start.mutableCopy(); + newStart.intersect(specs, true); + + if (start.equals(newStart)) { + return false; + } + + newStart.setImmutable(); + setStarts(index, newStart); + + return true; + } + + /** + * Gets the register set associated with the start of the block + * with the given index. This returns an empty set with the appropriate + * max size if no set was associated with the block in question. + * + * @param index {@code >= 0;} the block index + * @return {@code non-null;} the associated register set + */ + public RegisterSpecSet getStarts(int index) { + RegisterSpecSet result = getStarts0(index); + + return (result != null) ? result : emptySet; + } + + /** + * Gets the register set associated with the start of the given + * block. This is just convenient shorthand for + * {@code getStarts(block.getLabel())}. + * + * @param block {@code non-null;} the block in question + * @return {@code non-null;} the associated register set + */ + public RegisterSpecSet getStarts(SsaBasicBlock block) { + return getStarts(block.getIndex()); + } + + /** + * Gets a mutable copy of the register set associated with the + * start of the block with the given index. This returns a + * newly-allocated empty {@link RegisterSpecSet} of appropriate + * max size if there is not yet any set associated with the block. + * + * @param index {@code >= 0;} the block index + * @return {@code non-null;} the associated register set + */ + public RegisterSpecSet mutableCopyOfStarts(int index) { + RegisterSpecSet result = getStarts0(index); + + return (result != null) ? + result.mutableCopy() : new RegisterSpecSet(regCount); + } + + /** + * Adds an assignment association for the given instruction and + * register spec. This throws an exception if the instruction + * doesn't actually perform a named variable assignment. + * + * Note: Although the instruction contains its own spec for + * the result, it still needs to be passed in explicitly to this + * method, since the spec that is stored here should always have a + * simple type and the one in the instruction can be an arbitrary + * {@link com.android.jack.dx.rop.type.TypeBearer} (such as a constant value). + * + * @param insn {@code non-null;} the instruction in question + * @param spec {@code non-null;} the associated register spec + */ + public void addAssignment(SsaInsn insn, RegisterSpec spec) { + throwIfImmutable(); + + if (insn == null) { + throw new NullPointerException("insn == null"); + } + + if (spec == null) { + throw new NullPointerException("spec == null"); + } + + insnAssignments.put(insn, spec); + } + + /** + * Gets the named register being assigned by the given instruction, if + * previously stored in this instance. + * + * @param insn {@code non-null;} instruction in question + * @return {@code null-ok;} the named register being assigned, if any + */ + public RegisterSpec getAssignment(SsaInsn insn) { + return insnAssignments.get(insn); + } + + /** + * Gets the number of assignments recorded by this instance. + * + * @return {@code >= 0;} the number of assignments + */ + public int getAssignmentCount() { + return insnAssignments.size(); + } + + public void debugDump() { + for (int index = 0 ; index < blockStarts.length; index++) { + if (blockStarts[index] == null) { + continue; + } + + if (blockStarts[index] == emptySet) { + System.out.printf("%04x: empty set\n", index); + } else { + System.out.printf("%04x: %s\n", index, blockStarts[index]); + } + } + } + + /** + * Helper method, to get the starts for a index, throwing the + * right exception for range problems. + * + * @param index {@code >= 0;} the block index + * @return {@code null-ok;} associated register set or {@code null} if there + * is none + */ + private RegisterSpecSet getStarts0(int index) { + try { + return blockStarts[index]; + } catch (ArrayIndexOutOfBoundsException ex) { + // Translate the exception. + throw new IllegalArgumentException("bogus index"); + } + } +} diff --git a/dx/src/com/android/jack/dx/ssa/MoveParamCombiner.java b/dx/src/com/android/jack/dx/ssa/MoveParamCombiner.java new file mode 100644 index 00000000..dc05a6e4 --- /dev/null +++ b/dx/src/com/android/jack/dx/ssa/MoveParamCombiner.java @@ -0,0 +1,156 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.ssa; + +import com.android.jack.dx.rop.code.CstInsn; +import com.android.jack.dx.rop.code.LocalItem; +import com.android.jack.dx.rop.code.RegOps; +import com.android.jack.dx.rop.code.RegisterSpec; +import com.android.jack.dx.rop.cst.CstInteger; + +import java.util.HashSet; +import java.util.ArrayList; +import java.util.List; + +/** + * Combine identical move-param insns, which may result from Ropper's + * handling of synchronized methods. + */ +public class MoveParamCombiner { + + /** method to process */ + private final SsaMethod ssaMeth; + + /** + * Processes a method with this optimization step. + * + * @param ssaMethod method to process + */ + public static void process(SsaMethod ssaMethod) { + new MoveParamCombiner(ssaMethod).run(); + } + + private MoveParamCombiner(SsaMethod ssaMeth) { + this.ssaMeth = ssaMeth; + } + + /** + * Runs this optimization step. + */ + private void run() { + // This will contain the definition specs for each parameter + final RegisterSpec[] paramSpecs + = new RegisterSpec[ssaMeth.getParamWidth()]; + + // Insns to delete when all done + final HashSet deletedInsns = new HashSet(); + + ssaMeth.forEachInsn(new SsaInsn.Visitor() { + public void visitMoveInsn (NormalSsaInsn insn) { + } + public void visitPhiInsn (PhiInsn phi) { + } + public void visitNonMoveInsn (NormalSsaInsn insn) { + if (insn.getOpcode().getOpcode() != RegOps.MOVE_PARAM) { + return; + } + + int param = getParamIndex(insn); + + if (paramSpecs[param] == null) { + paramSpecs[param] = insn.getResult(); + } else { + final RegisterSpec specA = paramSpecs[param]; + final RegisterSpec specB = insn.getResult(); + LocalItem localA = specA.getLocalItem(); + LocalItem localB = specB.getLocalItem(); + LocalItem newLocal; + + /* + * Is there local information to preserve? + */ + + if (localA == null) { + newLocal = localB; + } else if (localB == null) { + newLocal = localA; + } else if (localA.equals(localB)) { + newLocal = localA; + } else { + /* + * Oddly, these two identical move-params have distinct + * debug info. We'll just keep them distinct. + */ + return; + } + + ssaMeth.getDefinitionForRegister(specA.getReg()) + .setResultLocal(newLocal); + + /* + * Map all uses of specB to specA + */ + + RegisterMapper mapper = new RegisterMapper() { + /** @inheritDoc */ + public int getNewRegisterCount() { + return ssaMeth.getRegCount(); + } + + /** @inheritDoc */ + public RegisterSpec map(RegisterSpec registerSpec) { + if (registerSpec.getReg() == specB.getReg()) { + return specA; + } + + return registerSpec; + } + }; + + List uses + = ssaMeth.getUseListForRegister(specB.getReg()); + + // Use list is modified by mapSourceRegisters + for (int i = uses.size() - 1; i >= 0; i--) { + SsaInsn use = uses.get(i); + use.mapSourceRegisters(mapper); + } + + deletedInsns.add(insn); + } + + } + }); + + ssaMeth.deleteInsns(deletedInsns); + } + + /** + * Returns the parameter index associated with a move-param insn. Does + * not verify that the insn is a move-param insn. + * + * @param insn {@code non-null;} a move-param insn + * @return {@code >=0;} parameter index + */ + private int getParamIndex(NormalSsaInsn insn) { + CstInsn cstInsn = (CstInsn)(insn.getOriginalRopInsn()); + + int param = ((CstInteger)cstInsn.getConstant()).getValue(); + return param; + } + +} diff --git a/dx/src/com/android/jack/dx/ssa/NormalSsaInsn.java b/dx/src/com/android/jack/dx/ssa/NormalSsaInsn.java new file mode 100644 index 00000000..c6695664 --- /dev/null +++ b/dx/src/com/android/jack/dx/ssa/NormalSsaInsn.java @@ -0,0 +1,236 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.ssa; + +import com.android.jack.dx.rop.code.*; + +/** + * A "normal" (non-phi) instruction in SSA form. Always wraps a rop insn. + */ +public final class NormalSsaInsn extends SsaInsn implements Cloneable { + /** {@code non-null;} rop insn that we're wrapping */ + private Insn insn; + + /** + * Creates an instance. + * + * @param insn Rop insn to wrap + * @param block block that contains this insn + */ + NormalSsaInsn(final Insn insn, final SsaBasicBlock block) { + super(insn.getResult(), block); + this.insn = insn; + } + + /** {@inheritDoc} */ + @Override + public final void mapSourceRegisters(RegisterMapper mapper) { + RegisterSpecList oldSources = insn.getSources(); + RegisterSpecList newSources = mapper.map(oldSources); + + if (newSources != oldSources) { + insn = insn.withNewRegisters(getResult(), newSources); + getBlock().getParent().onSourcesChanged(this, oldSources); + } + } + + /** + * Changes one of the insn's sources. New source should be of same type + * and category. + * + * @param index {@code >=0;} index of source to change + * @param newSpec spec for new source + */ + public final void changeOneSource(int index, RegisterSpec newSpec) { + RegisterSpecList origSources = insn.getSources(); + int sz = origSources.size(); + RegisterSpecList newSources = new RegisterSpecList(sz); + + for (int i = 0; i < sz; i++) { + newSources.set(i, i == index ? newSpec : origSources.get(i)); + } + + newSources.setImmutable(); + + RegisterSpec origSpec = origSources.get(index); + if (origSpec.getReg() != newSpec.getReg()) { + /* + * If the register remains unchanged, we're only changing + * the type or local var name so don't update use list + */ + getBlock().getParent().onSourceChanged(this, origSpec, newSpec); + } + + insn = insn.withNewRegisters(getResult(), newSources); + } + + /** + * Changes the source list of the insn. New source list should be the + * same size and consist of sources of identical types. + * + * @param newSources non-null new sources list. + */ + public final void setNewSources (RegisterSpecList newSources) { + RegisterSpecList origSources = insn.getSources(); + + if (origSources.size() != newSources.size()) { + throw new RuntimeException("Sources counts don't match"); + } + + insn = insn.withNewRegisters(getResult(), newSources); + } + + /** {@inheritDoc} */ + @Override + public NormalSsaInsn clone() { + return (NormalSsaInsn) super.clone(); + } + + /** + * Like rop.Insn.getSources(). + * + * @return {@code null-ok;} sources list + */ + @Override + public RegisterSpecList getSources() { + return insn.getSources(); + } + + /** {@inheritDoc} */ + public String toHuman() { + return toRopInsn().toHuman(); + } + + /** {@inheritDoc} */ + @Override + public Insn toRopInsn() { + return insn.withNewRegisters(getResult(), insn.getSources()); + } + + /** + * @return the Rop opcode for this insn + */ + @Override + public Rop getOpcode() { + return insn.getOpcode(); + } + + /** {@inheritDoc} */ + @Override + public Insn getOriginalRopInsn() { + return insn; + } + + /** {@inheritDoc} */ + @Override + public RegisterSpec getLocalAssignment() { + RegisterSpec assignment; + + if (insn.getOpcode().getOpcode() == RegOps.MARK_LOCAL) { + assignment = insn.getSources().get(0); + } else { + assignment = getResult(); + } + + if (assignment == null) { + return null; + } + + LocalItem local = assignment.getLocalItem(); + + if (local == null) { + return null; + } + + return assignment; + } + + /** + * Upgrades this insn to a version that represents the constant source + * literally. If the upgrade is not possible, this does nothing. + * + * @see Insn#withSourceLiteral + */ + public void upgradeToLiteral() { + RegisterSpecList oldSources = insn.getSources(); + + insn = insn.withSourceLiteral(); + getBlock().getParent().onSourcesChanged(this, oldSources); + } + + /** + * @return true if this is a move (but not a move-operand) instruction + */ + @Override + public boolean isNormalMoveInsn() { + return insn.getOpcode().getOpcode() == RegOps.MOVE; + } + + /** {@inheritDoc} */ + @Override + public boolean isMoveException() { + return insn.getOpcode().getOpcode() == RegOps.MOVE_EXCEPTION; + } + + /** {@inheritDoc} */ + @Override + public boolean canThrow() { + return insn.canThrow(); + } + + /** {@inheritDoc} */ + @Override + public void accept(Visitor v) { + if (isNormalMoveInsn()) { + v.visitMoveInsn(this); + } else { + v.visitNonMoveInsn(this); + } + } + + /** {@inheritDoc} */ + @Override + public boolean isPhiOrMove() { + return isNormalMoveInsn(); + } + + /** + * {@inheritDoc} + * + * TODO: Increase the scope of this. + */ + @Override + public boolean hasSideEffect() { + Rop opcode = getOpcode(); + + if (opcode.getBranchingness() != Rop.BRANCH_NONE) { + return true; + } + + boolean hasLocalSideEffect + = Optimizer.getPreserveLocals() && getLocalAssignment() != null; + + switch (opcode.getOpcode()) { + case RegOps.MOVE_RESULT: + case RegOps.MOVE: + case RegOps.CONST: + return hasLocalSideEffect; + default: + return true; + } + } +} diff --git a/dx/src/com/android/jack/dx/ssa/Optimizer.java b/dx/src/com/android/jack/dx/ssa/Optimizer.java new file mode 100644 index 00000000..799b7042 --- /dev/null +++ b/dx/src/com/android/jack/dx/ssa/Optimizer.java @@ -0,0 +1,257 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.ssa; + +import com.android.jack.dx.rop.code.RopMethod; +import com.android.jack.dx.rop.code.TranslationAdvice; +import com.android.jack.dx.ssa.back.LivenessAnalyzer; +import com.android.jack.dx.ssa.back.SsaToRop; + +import java.util.EnumSet; + +/** + * Runs a method through the SSA form conversion, any optimization algorithms, + * and returns it to rop form. + */ +public class Optimizer { + private static boolean preserveLocals = true; + + private static TranslationAdvice advice; + + /** optional optimizer steps */ + public enum OptionalStep { + MOVE_PARAM_COMBINER, SCCP, LITERAL_UPGRADE, CONST_COLLECTOR, + ESCAPE_ANALYSIS + } + + /** + * @return true if local variable information should be preserved, even + * at code size/register size cost + */ + public static boolean getPreserveLocals() { + return preserveLocals; + } + + /** + * @return {@code non-null;} translation advice + */ + public static TranslationAdvice getAdvice() { + return advice; + } + + /** + * Runs optimization algorthims over this method, and returns a new + * instance of RopMethod with the changes. + * + * @param rmeth method to process + * @param paramWidth the total width, in register-units, of this method's + * parameters + * @param isStatic true if this method has no 'this' pointer argument. + * @param inPreserveLocals true if local variable info should be preserved, + * at the cost of some registers and insns + * @param inAdvice {@code non-null;} translation advice + * @return optimized method + */ + public static RopMethod optimize(RopMethod rmeth, int paramWidth, + boolean isStatic, boolean inPreserveLocals, + TranslationAdvice inAdvice) { + + return optimize(rmeth, paramWidth, isStatic, inPreserveLocals, inAdvice, + EnumSet.allOf(OptionalStep.class)); + } + + /** + * Runs optimization algorthims over this method, and returns a new + * instance of RopMethod with the changes. + * + * @param rmeth method to process + * @param paramWidth the total width, in register-units, of this method's + * parameters + * @param isStatic true if this method has no 'this' pointer argument. + * @param inPreserveLocals true if local variable info should be preserved, + * at the cost of some registers and insns + * @param inAdvice {@code non-null;} translation advice + * @param steps set of optional optimization steps to run + * @return optimized method + */ + public static RopMethod optimize(RopMethod rmeth, int paramWidth, + boolean isStatic, boolean inPreserveLocals, + TranslationAdvice inAdvice, EnumSet steps) { + SsaMethod ssaMeth = null; + + preserveLocals = inPreserveLocals; + advice = inAdvice; + + ssaMeth = SsaConverter.convertToSsaMethod(rmeth, paramWidth, isStatic); + runSsaFormSteps(ssaMeth, steps); + + RopMethod resultMeth = SsaToRop.convertToRopMethod(ssaMeth, false); + + if (resultMeth.getBlocks().getRegCount() + > advice.getMaxOptimalRegisterCount()) { + // Try to see if we can squeeze it under the register count bar + resultMeth = optimizeMinimizeRegisters(rmeth, paramWidth, isStatic, + steps); + } + return resultMeth; + } + + /** + * Runs the optimizer with a strategy to minimize the number of rop-form + * registers used by the end result. Dex bytecode does not have instruction + * forms that take register numbers larger than 15 for all instructions. + * If we've produced a method that uses more than 16 registers, try again + * with a different strategy to see if we can get under the bar. The end + * result will be much more efficient. + * + * @param rmeth method to process + * @param paramWidth the total width, in register-units, of this method's + * parameters + * @param isStatic true if this method has no 'this' pointer argument. + * @param steps set of optional optimization steps to run + * @return optimized method + */ + private static RopMethod optimizeMinimizeRegisters(RopMethod rmeth, + int paramWidth, boolean isStatic, + EnumSet steps) { + SsaMethod ssaMeth; + RopMethod resultMeth; + + ssaMeth = SsaConverter.convertToSsaMethod( + rmeth, paramWidth, isStatic); + + EnumSet newSteps = steps.clone(); + + /* + * CONST_COLLECTOR trades insns for registers, which is not an + * appropriate strategy here. + */ + newSteps.remove(OptionalStep.CONST_COLLECTOR); + + runSsaFormSteps(ssaMeth, newSteps); + + resultMeth = SsaToRop.convertToRopMethod(ssaMeth, true); + return resultMeth; + } + + private static void runSsaFormSteps(SsaMethod ssaMeth, + EnumSet steps) { + boolean needsDeadCodeRemover = true; + + if (steps.contains(OptionalStep.MOVE_PARAM_COMBINER)) { + MoveParamCombiner.process(ssaMeth); + } + + if (steps.contains(OptionalStep.SCCP)) { + SCCP.process(ssaMeth); + DeadCodeRemover.process(ssaMeth); + needsDeadCodeRemover = false; + } + + if (steps.contains(OptionalStep.LITERAL_UPGRADE)) { + LiteralOpUpgrader.process(ssaMeth); + DeadCodeRemover.process(ssaMeth); + needsDeadCodeRemover = false; + } + + /* + * ESCAPE_ANALYSIS impacts debuggability, so left off by default + */ + steps.remove(OptionalStep.ESCAPE_ANALYSIS); + if (steps.contains(OptionalStep.ESCAPE_ANALYSIS)) { + EscapeAnalysis.process(ssaMeth); + DeadCodeRemover.process(ssaMeth); + needsDeadCodeRemover = false; + } + + if (steps.contains(OptionalStep.CONST_COLLECTOR)) { + ConstCollector.process(ssaMeth); + DeadCodeRemover.process(ssaMeth); + needsDeadCodeRemover = false; + } + + // dead code remover must be run before phi type resolver + if (needsDeadCodeRemover) { + DeadCodeRemover.process(ssaMeth); + } + + PhiTypeResolver.process(ssaMeth); + } + + public static SsaMethod debugEdgeSplit(RopMethod rmeth, int paramWidth, + boolean isStatic, boolean inPreserveLocals, + TranslationAdvice inAdvice) { + + preserveLocals = inPreserveLocals; + advice = inAdvice; + + return SsaConverter.testEdgeSplit(rmeth, paramWidth, isStatic); + } + + public static SsaMethod debugPhiPlacement(RopMethod rmeth, int paramWidth, + boolean isStatic, boolean inPreserveLocals, + TranslationAdvice inAdvice) { + + preserveLocals = inPreserveLocals; + advice = inAdvice; + + return SsaConverter.testPhiPlacement(rmeth, paramWidth, isStatic); + } + + public static SsaMethod debugRenaming(RopMethod rmeth, int paramWidth, + boolean isStatic, boolean inPreserveLocals, + TranslationAdvice inAdvice) { + + preserveLocals = inPreserveLocals; + advice = inAdvice; + + return SsaConverter.convertToSsaMethod(rmeth, paramWidth, isStatic); + } + + public static SsaMethod debugDeadCodeRemover(RopMethod rmeth, + int paramWidth, boolean isStatic, boolean inPreserveLocals, + TranslationAdvice inAdvice) { + + SsaMethod ssaMeth; + + preserveLocals = inPreserveLocals; + advice = inAdvice; + + ssaMeth = SsaConverter.convertToSsaMethod(rmeth, paramWidth, isStatic); + DeadCodeRemover.process(ssaMeth); + + return ssaMeth; + } + + public static SsaMethod debugNoRegisterAllocation(RopMethod rmeth, + int paramWidth, boolean isStatic, boolean inPreserveLocals, + TranslationAdvice inAdvice, EnumSet steps) { + + SsaMethod ssaMeth; + + preserveLocals = inPreserveLocals; + advice = inAdvice; + + ssaMeth = SsaConverter.convertToSsaMethod(rmeth, paramWidth, isStatic); + + runSsaFormSteps(ssaMeth, steps); + + LivenessAnalyzer.constructInterferenceGraph(ssaMeth); + + return ssaMeth; + } +} diff --git a/dx/src/com/android/jack/dx/ssa/PhiInsn.java b/dx/src/com/android/jack/dx/ssa/PhiInsn.java new file mode 100644 index 00000000..9372305d --- /dev/null +++ b/dx/src/com/android/jack/dx/ssa/PhiInsn.java @@ -0,0 +1,398 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.ssa; + +import com.android.jack.dx.rop.code.*; +import com.android.jack.dx.rop.type.Type; +import com.android.jack.dx.rop.type.TypeBearer; +import com.android.jack.dx.util.Hex; + +import java.util.ArrayList; +import java.util.List; + +/** + * A Phi instruction (magical post-control-flow-merge) instruction + * in SSA form. Will be converted to moves in predecessor blocks before + * conversion back to ROP form. + */ +public final class PhiInsn extends SsaInsn { + /** + * result register. The original result register of the phi insn + * is needed during the renaming process after the new result + * register has already been chosen. + */ + private final int ropResultReg; + + /** + * {@code non-null;} operands of the instruction; built up by + * {@link #addPhiOperand} + */ + private final ArrayList operands = new ArrayList(); + + /** {@code null-ok;} source registers; constructed lazily */ + private RegisterSpecList sources; + + /** + * Constructs a new phi insn with no operands. + * + * @param resultReg the result reg for this phi insn + * @param block block containing this insn. + */ + public PhiInsn(RegisterSpec resultReg, SsaBasicBlock block) { + super(resultReg, block); + ropResultReg = resultReg.getReg(); + } + + /** + * Makes a phi insn with a void result type. + * + * @param resultReg the result register for this phi insn. + * @param block block containing this insn. + */ + public PhiInsn(final int resultReg, final SsaBasicBlock block) { + /* + * The result type here is bogus: The type depends on the + * operand and will be derived later. + */ + super(RegisterSpec.make(resultReg, Type.VOID), block); + ropResultReg = resultReg; + } + + /** {@inheritDoc} */ + @Override + public PhiInsn clone() { + throw new UnsupportedOperationException("can't clone phi"); + } + + /** + * Updates the TypeBearers of all the sources (phi operands) to be + * the current TypeBearer of the register-defining instruction's result. + * This is used during phi-type resolution.

      + * + * Note that local association of operands are preserved in this step. + * + * @param ssaMeth method that contains this insn + */ + public void updateSourcesToDefinitions(SsaMethod ssaMeth) { + for (Operand o : operands) { + RegisterSpec def + = ssaMeth.getDefinitionForRegister( + o.regSpec.getReg()).getResult(); + + o.regSpec = o.regSpec.withType(def.getType()); + } + + sources = null; + } + + /** + * Changes the result type. Used during phi type resolution + * + * @param type {@code non-null;} new TypeBearer + * @param local {@code null-ok;} new local info, if available + */ + public void changeResultType(TypeBearer type, LocalItem local) { + setResult(RegisterSpec.makeLocalOptional( + getResult().getReg(), type, local)); + } + + /** + * Gets the original rop-form result reg. This is useful during renaming. + * + * @return the original rop-form result reg + */ + public int getRopResultReg() { + return ropResultReg; + } + + /** + * Adds an operand to this phi instruction. + * + * @param registerSpec register spec, including type and reg of operand + * @param predBlock predecessor block to be associated with this operand + */ + public void addPhiOperand(RegisterSpec registerSpec, + SsaBasicBlock predBlock) { + operands.add(new Operand(registerSpec, predBlock.getIndex(), + predBlock.getRopLabel())); + + // Un-cache sources, in case someone has already called getSources(). + sources = null; + } + + /** + * Removes all operand uses of a register from this phi instruction. + * + * @param registerSpec register spec, including type and reg of operand + */ + public void removePhiRegister(RegisterSpec registerSpec) { + ArrayList operandsToRemove = new ArrayList(); + for (Operand o : operands) { + if (o.regSpec.getReg() == registerSpec.getReg()) { + operandsToRemove.add(o); + } + } + + operands.removeAll(operandsToRemove); + + // Un-cache sources, in case someone has already called getSources(). + sources = null; + } + + /** + * Gets the index of the pred block associated with the RegisterSpec + * at the particular getSources() index. + * + * @param sourcesIndex index of source in getSources() + * @return block index + */ + public int predBlockIndexForSourcesIndex(int sourcesIndex) { + return operands.get(sourcesIndex).blockIndex; + } + + /** + * {@inheritDoc} + * + * Always returns null for {@code PhiInsn}s. + */ + @Override + public Rop getOpcode() { + return null; + } + + /** + * {@inheritDoc} + * + * Always returns null for {@code PhiInsn}s. + */ + @Override + public Insn getOriginalRopInsn() { + return null; + } + + /** + * {@inheritDoc} + * + * Always returns false for {@code PhiInsn}s. + */ + @Override + public boolean canThrow() { + return false; + } + + /** + * Gets sources. Constructed lazily from phi operand data structures and + * then cached. + * + * @return {@code non-null;} sources list + */ + @Override + public RegisterSpecList getSources() { + if (sources != null) { + return sources; + } + + if (operands.size() == 0) { + // How'd this happen? A phi insn with no operand? + return RegisterSpecList.EMPTY; + } + + int szSources = operands.size(); + sources = new RegisterSpecList(szSources); + + for (int i = 0; i < szSources; i++) { + Operand o = operands.get(i); + + sources.set(i, o.regSpec); + } + + sources.setImmutable(); + return sources; + } + + /** {@inheritDoc} */ + @Override + public boolean isRegASource(int reg) { + /* + * Avoid creating a sources list in case it has not already been + * created. + */ + + for (Operand o : operands) { + if (o.regSpec.getReg() == reg) { + return true; + } + } + + return false; + } + + /** + * @return true if all operands use the same register + */ + public boolean areAllOperandsEqual() { + if (operands.size() == 0 ) { + // This should never happen. + return true; + } + + int firstReg = operands.get(0).regSpec.getReg(); + for (Operand o : operands) { + if (firstReg != o.regSpec.getReg()) { + return false; + } + } + + return true; + } + + /** {@inheritDoc} */ + @Override + public final void mapSourceRegisters(RegisterMapper mapper) { + for (Operand o : operands) { + RegisterSpec old = o.regSpec; + o.regSpec = mapper.map(old); + if (old != o.regSpec) { + getBlock().getParent().onSourceChanged(this, old, o.regSpec); + } + } + sources = null; + } + + /** + * Always throws an exeption, since a phi insn may not be + * converted back to rop form. + * + * @return always throws exception + */ + @Override + public Insn toRopInsn() { + throw new IllegalArgumentException( + "Cannot convert phi insns to rop form"); + } + + /** + * Returns the list of predecessor blocks associated with all operands + * that have {@code reg} as an operand register. + * + * @param reg register to look up + * @param ssaMeth method we're operating on + * @return list of predecessor blocks, empty if none + */ + public List predBlocksForReg(int reg, SsaMethod ssaMeth) { + ArrayList ret = new ArrayList(); + + for (Operand o : operands) { + if (o.regSpec.getReg() == reg) { + ret.add(ssaMeth.getBlocks().get(o.blockIndex)); + } + } + + return ret; + } + + /** {@inheritDoc} */ + @Override + public boolean isPhiOrMove() { + return true; + } + + /** {@inheritDoc} */ + @Override + public boolean hasSideEffect() { + return Optimizer.getPreserveLocals() && getLocalAssignment() != null; + } + + /** {@inheritDoc} */ + @Override + public void accept(SsaInsn.Visitor v) { + v.visitPhiInsn(this); + } + + /** {@inheritDoc} */ + public String toHuman() { + return toHumanWithInline(null); + } + + /** + * Returns human-readable string for listing dumps. This method + * allows sub-classes to specify extra text. + * + * @param extra {@code null-ok;} the argument to print after the opcode + * @return human-readable string for listing dumps + */ + protected final String toHumanWithInline(String extra) { + StringBuffer sb = new StringBuffer(80); + + sb.append(SourcePosition.NO_INFO); + sb.append(": phi"); + + if (extra != null) { + sb.append("("); + sb.append(extra); + sb.append(")"); + } + + RegisterSpec result = getResult(); + + if (result == null) { + sb.append(" ."); + } else { + sb.append(" "); + sb.append(result.toHuman()); + } + + sb.append(" <-"); + + int sz = getSources().size(); + if (sz == 0) { + sb.append(" ."); + } else { + for (int i = 0; i < sz; i++) { + sb.append(" "); + sb.append(sources.get(i).toHuman() + + "[b=" + + Hex.u2(operands.get(i).ropLabel) + "]"); + } + } + + return sb.toString(); + } + + /** + * A single phi operand, consiting of source register and block index + * for move. + */ + private static class Operand { + public RegisterSpec regSpec; + public final int blockIndex; + public final int ropLabel; // only used for debugging + + public Operand(RegisterSpec regSpec, int blockIndex, int ropLabel) { + this.regSpec = regSpec; + this.blockIndex = blockIndex; + this.ropLabel = ropLabel; + } + } + + /** + * Visitor interface for instances of this (outer) class. + */ + public static interface Visitor { + public void visitPhiInsn(PhiInsn insn); + } +} diff --git a/dx/src/com/android/jack/dx/ssa/PhiTypeResolver.java b/dx/src/com/android/jack/dx/ssa/PhiTypeResolver.java new file mode 100644 index 00000000..abc12534 --- /dev/null +++ b/dx/src/com/android/jack/dx/ssa/PhiTypeResolver.java @@ -0,0 +1,263 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.ssa; + +import com.android.jack.dx.rop.code.LocalItem; +import com.android.jack.dx.rop.code.RegisterSpec; +import com.android.jack.dx.rop.code.RegisterSpecList; +import com.android.jack.dx.rop.type.Type; +import com.android.jack.dx.rop.type.TypeBearer; + +import java.util.BitSet; +import java.util.List; + +/** + * Resolves the result types of phi instructions. When phi instructions + * are inserted, their result types are set to BT_VOID (which is a nonsensical + * type for a register) but must be resolve to a real type before converting + * out of SSA form.

      + * + * The resolve is done as an iterative merge of each phi's operand types. + * Phi operands may be themselves be the result of unresolved phis, + * and the algorithm tries to find the most-fit type (for example, if every + * operand is the same constant value or the same local variable info, we want + * that to be reflected).

      + * + * This algorithm assumes a dead-code remover has already removed all + * circular-only phis that may have been inserted. + */ +public class PhiTypeResolver { + + SsaMethod ssaMeth; + /** indexed by register; all registers still defined by unresolved phis */ + private final BitSet worklist; + + /** + * Resolves all phi types in the method + * @param ssaMeth method to process + */ + public static void process (SsaMethod ssaMeth) { + new PhiTypeResolver(ssaMeth).run(); + } + + private PhiTypeResolver(SsaMethod ssaMeth) { + this.ssaMeth = ssaMeth; + worklist = new BitSet(ssaMeth.getRegCount()); + } + + /** + * Runs the phi-type resolver. + */ + private void run() { + + int regCount = ssaMeth.getRegCount(); + + for (int reg = 0; reg < regCount; reg++) { + SsaInsn definsn = ssaMeth.getDefinitionForRegister(reg); + + if (definsn != null + && (definsn.getResult().getBasicType() == Type.BT_VOID)) { + worklist.set(reg); + } + } + + int reg; + while ( 0 <= (reg = worklist.nextSetBit(0))) { + worklist.clear(reg); + + /* + * definitions on the worklist have a type of BT_VOID, which + * must have originated from a PhiInsn. + */ + PhiInsn definsn = (PhiInsn)ssaMeth.getDefinitionForRegister(reg); + + if (resolveResultType(definsn)) { + /* + * If the result type has changed, re-resolve all phis + * that use this. + */ + + List useList = ssaMeth.getUseListForRegister(reg); + + int sz = useList.size(); + for (int i = 0; i < sz; i++ ) { + SsaInsn useInsn = useList.get(i); + RegisterSpec resultReg = useInsn.getResult(); + if (resultReg != null && useInsn instanceof PhiInsn) { + worklist.set(resultReg.getReg()); + } + } + } + } + } + + /** + * Returns true if a and b are equal, whether + * or not either of them are null. + * @param a + * @param b + * @return true if equal + */ + private static boolean equalsHandlesNulls(LocalItem a, LocalItem b) { + return (a == b) || ((a != null) && a.equals(b)); + } + + /** + * Resolves the result of a phi insn based on its operands. The "void" + * type, which is a nonsensical type for a register, is used for + * registers defined by as-of-yet-unresolved phi operations. + * + * @return true if the result type changed, false if no change + */ + boolean resolveResultType(PhiInsn insn) { + insn.updateSourcesToDefinitions(ssaMeth); + + RegisterSpecList sources = insn.getSources(); + + // Start by finding the first non-void operand + RegisterSpec first = null; + int firstIndex = -1; + + int szSources = sources.size(); + for (int i = 0 ; i = 0 width of new namespace. + */ + public abstract int getNewRegisterCount(); + + /** + * @param registerSpec old register + * @return register in new space + */ + public abstract RegisterSpec map(RegisterSpec registerSpec); + + /** + * + * @param sources old register list + * @return new mapped register list, or old if nothing has changed. + */ + public final RegisterSpecList map(RegisterSpecList sources) { + int sz = sources.size(); + RegisterSpecList newSources = new RegisterSpecList(sz); + + for (int i = 0; i < sz; i++) { + newSources.set(i, map(sources.get(i))); + } + + newSources.setImmutable(); + + // Return the old sources if nothing has changed. + return newSources.equals(sources) ? sources : newSources; + } +} diff --git a/dx/src/com/android/jack/dx/ssa/SCCP.java b/dx/src/com/android/jack/dx/ssa/SCCP.java new file mode 100644 index 00000000..95753535 --- /dev/null +++ b/dx/src/com/android/jack/dx/ssa/SCCP.java @@ -0,0 +1,697 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.ssa; + +import com.android.jack.dx.rop.code.CstInsn; +import com.android.jack.dx.rop.code.Insn; +import com.android.jack.dx.rop.code.PlainInsn; +import com.android.jack.dx.rop.code.RegOps; +import com.android.jack.dx.rop.code.RegisterSpec; +import com.android.jack.dx.rop.code.RegisterSpecList; +import com.android.jack.dx.rop.code.Rop; +import com.android.jack.dx.rop.code.Rops; +import com.android.jack.dx.rop.cst.Constant; +import com.android.jack.dx.rop.cst.CstBoolean; +import com.android.jack.dx.rop.cst.CstInteger; +import com.android.jack.dx.rop.cst.TypedConstant; +import com.android.jack.dx.rop.type.Type; +import com.android.jack.dx.rop.type.TypeBearer; + +import java.util.ArrayList; +import java.util.BitSet; + +/** + * A small variant of Wegman and Zadeck's Sparse Conditional Constant + * Propagation algorithm. + */ +public class SCCP { + /** Lattice values */ + private static final int TOP = 0; + private static final int CONSTANT = 1; + private static final int VARYING = 2; + /** method we're processing */ + private SsaMethod ssaMeth; + /** ssaMeth.getRegCount() */ + private int regCount; + /** Lattice values for each SSA register */ + private int[] latticeValues; + /** For those registers that are constant, this is the constant value */ + private Constant[] latticeConstants; + /** Worklist of basic blocks to be processed */ + private ArrayList cfgWorklist; + /** Worklist of executed basic blocks with phis to be processed */ + private ArrayList cfgPhiWorklist; + /** Bitset containing bits for each block that has been found executable */ + private BitSet executableBlocks; + /** Worklist for SSA edges. This is a list of registers to process */ + private ArrayList ssaWorklist; + /** + * Worklist for SSA edges that represent varying values. It makes the + * algorithm much faster if you move all values to VARYING as fast as + * possible. + */ + private ArrayList varyingWorklist; + /** Worklist of potential branches to convert to gotos */ + private ArrayList branchWorklist; + + private SCCP(SsaMethod ssaMeth) { + this.ssaMeth = ssaMeth; + this.regCount = ssaMeth.getRegCount(); + this.latticeValues = new int[this.regCount]; + this.latticeConstants = new Constant[this.regCount]; + this.cfgWorklist = new ArrayList(); + this.cfgPhiWorklist = new ArrayList(); + this.executableBlocks = new BitSet(ssaMeth.getBlocks().size()); + this.ssaWorklist = new ArrayList(); + this.varyingWorklist = new ArrayList(); + this.branchWorklist = new ArrayList(); + for (int i = 0; i < this.regCount; i++) { + latticeValues[i] = TOP; + latticeConstants[i] = null; + } + } + + /** + * Performs sparse conditional constant propagation on a method. + * @param ssaMethod Method to process + */ + public static void process (SsaMethod ssaMethod) { + new SCCP(ssaMethod).run(); + } + + /** + * Adds a SSA basic block to the CFG worklist if it's unexecuted, or + * to the CFG phi worklist if it's already executed. + * @param ssaBlock Block to add + */ + private void addBlockToWorklist(SsaBasicBlock ssaBlock) { + if (!executableBlocks.get(ssaBlock.getIndex())) { + cfgWorklist.add(ssaBlock); + executableBlocks.set(ssaBlock.getIndex()); + } else { + cfgPhiWorklist.add(ssaBlock); + } + } + + /** + * Adds an SSA register's uses to the SSA worklist. + * @param reg SSA register + * @param latticeValue new lattice value for @param reg. + */ + private void addUsersToWorklist(int reg, int latticeValue) { + if (latticeValue == VARYING) { + for (SsaInsn insn : ssaMeth.getUseListForRegister(reg)) { + varyingWorklist.add(insn); + } + } else { + for (SsaInsn insn : ssaMeth.getUseListForRegister(reg)) { + ssaWorklist.add(insn); + } + } + } + + /** + * Sets a lattice value for a register to value. + * @param reg SSA register + * @param value Lattice value + * @param cst Constant value (may be null) + * @return true if the lattice value changed. + */ + private boolean setLatticeValueTo(int reg, int value, Constant cst) { + if (value != CONSTANT) { + if (latticeValues[reg] != value) { + latticeValues[reg] = value; + return true; + } + return false; + } else { + if (latticeValues[reg] != value + || !latticeConstants[reg].equals(cst)) { + latticeValues[reg] = value; + latticeConstants[reg] = cst; + return true; + } + return false; + } + } + + /** + * Simulates a PHI node and set the lattice for the result + * to the appropriate value. + * Meet values: + * TOP x anything = TOP + * VARYING x anything = VARYING + * CONSTANT x CONSTANT = CONSTANT if equal constants, VARYING otherwise + * @param insn PHI to simulate. + */ + private void simulatePhi(PhiInsn insn) { + int phiResultReg = insn.getResult().getReg(); + + if (latticeValues[phiResultReg] == VARYING) { + return; + } + + RegisterSpecList sources = insn.getSources(); + int phiResultValue = TOP; + Constant phiConstant = null; + int sourceSize = sources.size(); + + for (int i = 0; i < sourceSize; i++) { + int predBlockIndex = insn.predBlockIndexForSourcesIndex(i); + int sourceReg = sources.get(i).getReg(); + int sourceRegValue = latticeValues[sourceReg]; + + if (!executableBlocks.get(predBlockIndex)) { + continue; + } + + if (sourceRegValue == CONSTANT) { + if (phiConstant == null) { + phiConstant = latticeConstants[sourceReg]; + phiResultValue = CONSTANT; + } else if (!latticeConstants[sourceReg].equals(phiConstant)){ + phiResultValue = VARYING; + break; + } + } else { + phiResultValue = sourceRegValue; + break; + } + } + if (setLatticeValueTo(phiResultReg, phiResultValue, phiConstant)) { + addUsersToWorklist(phiResultReg, phiResultValue); + } + } + + /** + * Simulate a block and note the results in the lattice. + * @param block Block to visit + */ + private void simulateBlock(SsaBasicBlock block) { + for (SsaInsn insn : block.getInsns()) { + if (insn instanceof PhiInsn) { + simulatePhi((PhiInsn) insn); + } else { + simulateStmt(insn); + } + } + } + + /** + * Simulate the phis in a block and note the results in the lattice. + * @param block Block to visit + */ + private void simulatePhiBlock(SsaBasicBlock block) { + for (SsaInsn insn : block.getInsns()) { + if (insn instanceof PhiInsn) { + simulatePhi((PhiInsn) insn); + } else { + return; + } + } + } + + private static String latticeValName(int latticeVal) { + switch (latticeVal) { + case TOP: return "TOP"; + case CONSTANT: return "CONSTANT"; + case VARYING: return "VARYING"; + default: return "UNKNOWN"; + } + } + + /** + * Simulates branch insns, if possible. Adds reachable successor blocks + * to the CFG worklists. + * @param insn branch to simulate + */ + private void simulateBranch(SsaInsn insn) { + Rop opcode = insn.getOpcode(); + RegisterSpecList sources = insn.getSources(); + + boolean constantBranch = false; + boolean constantSuccessor = false; + + // Check if the insn is a branch with a constant condition + if (opcode.getBranchingness() == Rop.BRANCH_IF) { + Constant cA = null; + Constant cB = null; + + RegisterSpec specA = sources.get(0); + int regA = specA.getReg(); + if (!ssaMeth.isRegALocal(specA) && + latticeValues[regA] == CONSTANT) { + cA = latticeConstants[regA]; + } + + if (sources.size() == 2) { + RegisterSpec specB = sources.get(1); + int regB = specB.getReg(); + if (!ssaMeth.isRegALocal(specB) && + latticeValues[regB] == CONSTANT) { + cB = latticeConstants[regB]; + } + } + + // Calculate the result of the condition + if (cA != null && sources.size() == 1) { + switch (((TypedConstant) cA).getBasicType()) { + case Type.BT_BOOLEAN: { + constantBranch = true; + boolean vA = ((CstBoolean) cA).getValue(); + switch (opcode.getOpcode()) { + case RegOps.IF_EQ: + constantSuccessor = !vA; + break; + case RegOps.IF_NE: + constantSuccessor = vA; + break; + default: + throw new RuntimeException("Unexpected op"); + } + break; + } + case Type.BT_INT: + constantBranch = true; + int vA = ((CstInteger) cA).getValue(); + switch (opcode.getOpcode()) { + case RegOps.IF_EQ: + constantSuccessor = (vA == 0); + break; + case RegOps.IF_NE: + constantSuccessor = (vA != 0); + break; + case RegOps.IF_LT: + constantSuccessor = (vA < 0); + break; + case RegOps.IF_GE: + constantSuccessor = (vA >= 0); + break; + case RegOps.IF_LE: + constantSuccessor = (vA <= 0); + break; + case RegOps.IF_GT: + constantSuccessor = (vA > 0); + break; + default: + throw new RuntimeException("Unexpected op"); + } + break; + default: + // not yet supported + } + } else if (cA != null && cB != null) { + switch (((TypedConstant) cA).getBasicType()) { + case Type.BT_INT: + constantBranch = true; + int vA = ((CstInteger) cA).getValue(); + int vB = ((CstInteger) cB).getValue(); + switch (opcode.getOpcode()) { + case RegOps.IF_EQ: + constantSuccessor = (vA == vB); + break; + case RegOps.IF_NE: + constantSuccessor = (vA != vB); + break; + case RegOps.IF_LT: + constantSuccessor = (vA < vB); + break; + case RegOps.IF_GE: + constantSuccessor = (vA >= vB); + break; + case RegOps.IF_LE: + constantSuccessor = (vA <= vB); + break; + case RegOps.IF_GT: + constantSuccessor = (vA > vB); + break; + default: + throw new RuntimeException("Unexpected op"); + } + break; + default: + // not yet supported + } + } + } + + /* + * If condition is constant, add only the target block to the + * worklist. Otherwise, add all successors to the worklist. + */ + SsaBasicBlock block = insn.getBlock(); + + if (constantBranch) { + int successorBlock; + if (constantSuccessor) { + successorBlock = block.getSuccessorList().get(1); + } else { + successorBlock = block.getSuccessorList().get(0); + } + addBlockToWorklist(ssaMeth.getBlocks().get(successorBlock)); + branchWorklist.add(insn); + } else { + for (int i = 0; i < block.getSuccessorList().size(); i++) { + int successorBlock = block.getSuccessorList().get(i); + addBlockToWorklist(ssaMeth.getBlocks().get(successorBlock)); + } + } + } + + /** + * Simulates math insns, if possible. + * + * @param insn non-null insn to simulate + * @param resultType basic type of the result + * @return constant result or null if not simulatable. + */ + private Constant simulateMath(SsaInsn insn, int resultType) { + Insn ropInsn = insn.getOriginalRopInsn(); + int opcode = insn.getOpcode().getOpcode(); + RegisterSpecList sources = insn.getSources(); + int regA = sources.get(0).getReg(); + Constant cA; + Constant cB; + + if (latticeValues[regA] != CONSTANT) { + cA = null; + } else { + cA = latticeConstants[regA]; + } + + if (sources.size() == 1) { + CstInsn cstInsn = (CstInsn) ropInsn; + cB = cstInsn.getConstant(); + } else { /* sources.size() == 2 */ + int regB = sources.get(1).getReg(); + if (latticeValues[regB] != CONSTANT) { + cB = null; + } else { + cB = latticeConstants[regB]; + } + } + + if (cA == null || cB == null) { + //TODO handle a constant of 0 with MUL or AND + return null; + } + + switch (resultType) { + case Type.BT_INT: + int vR; + boolean skip=false; + + int vA = ((CstInteger) cA).getValue(); + int vB = ((CstInteger) cB).getValue(); + + switch (opcode) { + case RegOps.ADD: + vR = vA + vB; + break; + case RegOps.SUB: + // 1 source for reverse sub, 2 sources for regular sub + if (sources.size() == 1) { + vR = vB - vA; + } else { + vR = vA - vB; + } + break; + case RegOps.MUL: + vR = vA * vB; + break; + case RegOps.DIV: + if (vB == 0) { + skip = true; + vR = 0; // just to hide a warning + } else { + vR = vA / vB; + } + break; + case RegOps.AND: + vR = vA & vB; + break; + case RegOps.OR: + vR = vA | vB; + break; + case RegOps.XOR: + vR = vA ^ vB; + break; + case RegOps.SHL: + vR = vA << vB; + break; + case RegOps.SHR: + vR = vA >> vB; + break; + case RegOps.USHR: + vR = vA >>> vB; + break; + case RegOps.REM: + if (vB == 0) { + skip = true; + vR = 0; // just to hide a warning + } else { + vR = vA % vB; + } + break; + default: + throw new RuntimeException("Unexpected op"); + } + + return skip ? null : CstInteger.make(vR); + + default: + // not yet supported + return null; + } + } + + /** + * Simulates a statement and set the result lattice value. + * @param insn instruction to simulate + */ + private void simulateStmt(SsaInsn insn) { + Insn ropInsn = insn.getOriginalRopInsn(); + if (ropInsn.getOpcode().getBranchingness() != Rop.BRANCH_NONE + || ropInsn.getOpcode().isCallLike()) { + simulateBranch(insn); + } + + int opcode = insn.getOpcode().getOpcode(); + RegisterSpec result = insn.getResult(); + + if (result == null) { + // Find move-result-pseudo result for int div and int rem + if (opcode == RegOps.DIV || opcode == RegOps.REM) { + SsaBasicBlock succ = insn.getBlock().getPrimarySuccessor(); + result = succ.getInsns().get(0).getResult(); + } else { + return; + } + } + + int resultReg = result.getReg(); + int resultValue = VARYING; + Constant resultConstant = null; + + switch (opcode) { + case RegOps.CONST: { + CstInsn cstInsn = (CstInsn)ropInsn; + resultValue = CONSTANT; + resultConstant = cstInsn.getConstant(); + break; + } + case RegOps.MOVE: { + if (insn.getSources().size() == 1) { + int sourceReg = insn.getSources().get(0).getReg(); + resultValue = latticeValues[sourceReg]; + resultConstant = latticeConstants[sourceReg]; + } + break; + } + case RegOps.ADD: + case RegOps.SUB: + case RegOps.MUL: + case RegOps.DIV: + case RegOps.AND: + case RegOps.OR: + case RegOps.XOR: + case RegOps.SHL: + case RegOps.SHR: + case RegOps.USHR: + case RegOps.REM: { + resultConstant = simulateMath(insn, result.getBasicType()); + if (resultConstant != null) { + resultValue = CONSTANT; + } + break; + } + case RegOps.MOVE_RESULT_PSEUDO: { + if (latticeValues[resultReg] == CONSTANT) { + resultValue = latticeValues[resultReg]; + resultConstant = latticeConstants[resultReg]; + } + break; + } + // TODO: Handle non-int arithmetic. + // TODO: Eliminate check casts that we can prove the type of. + default: {} + } + if (setLatticeValueTo(resultReg, resultValue, resultConstant)) { + addUsersToWorklist(resultReg, resultValue); + } + } + + private void run() { + SsaBasicBlock firstBlock = ssaMeth.getEntryBlock(); + addBlockToWorklist(firstBlock); + + /* Empty all the worklists by propagating our values */ + while (!cfgWorklist.isEmpty() + || !cfgPhiWorklist.isEmpty() + || !ssaWorklist.isEmpty() + || !varyingWorklist.isEmpty()) { + while (!cfgWorklist.isEmpty()) { + int listSize = cfgWorklist.size() - 1; + SsaBasicBlock block = cfgWorklist.remove(listSize); + simulateBlock(block); + } + + while (!cfgPhiWorklist.isEmpty()) { + int listSize = cfgPhiWorklist.size() - 1; + SsaBasicBlock block = cfgPhiWorklist.remove(listSize); + simulatePhiBlock(block); + } + + while (!varyingWorklist.isEmpty()) { + int listSize = varyingWorklist.size() - 1; + SsaInsn insn = varyingWorklist.remove(listSize); + + if (!executableBlocks.get(insn.getBlock().getIndex())) { + continue; + } + + if (insn instanceof PhiInsn) { + simulatePhi((PhiInsn)insn); + } else { + simulateStmt(insn); + } + } + while (!ssaWorklist.isEmpty()) { + int listSize = ssaWorklist.size() - 1; + SsaInsn insn = ssaWorklist.remove(listSize); + + if (!executableBlocks.get(insn.getBlock().getIndex())) { + continue; + } + + if (insn instanceof PhiInsn) { + simulatePhi((PhiInsn)insn); + } else { + simulateStmt(insn); + } + } + } + + replaceConstants(); + replaceBranches(); + } + + /** + * Replaces TypeBearers in source register specs with constant type + * bearers if possible. These are then referenced in later optimization + * steps. + */ + private void replaceConstants() { + for (int reg = 0; reg < regCount; reg++) { + if (latticeValues[reg] != CONSTANT) { + continue; + } + if (!(latticeConstants[reg] instanceof TypedConstant)) { + // We can't do much with these + continue; + } + + SsaInsn defn = ssaMeth.getDefinitionForRegister(reg); + TypeBearer typeBearer = defn.getResult().getTypeBearer(); + + if (typeBearer.isConstant()) { + /* + * The definition was a constant already. + * The uses should be as well. + */ + continue; + } + + // Update the destination RegisterSpec with the constant value + RegisterSpec dest = defn.getResult(); + RegisterSpec newDest + = dest.withType((TypedConstant)latticeConstants[reg]); + defn.setResult(newDest); + + /* + * Update the sources RegisterSpec's of all non-move uses. + * These will be used in later steps. + */ + for (SsaInsn insn : ssaMeth.getUseListForRegister(reg)) { + if (insn.isPhiOrMove()) { + continue; + } + + NormalSsaInsn nInsn = (NormalSsaInsn) insn; + RegisterSpecList sources = insn.getSources(); + + int index = sources.indexOfRegister(reg); + + RegisterSpec spec = sources.get(index); + RegisterSpec newSpec + = spec.withType((TypedConstant)latticeConstants[reg]); + + nInsn.changeOneSource(index, newSpec); + } + } + } + + /** + * Replaces branches that have constant conditions with gotos + */ + private void replaceBranches() { + for (SsaInsn insn : branchWorklist) { + // Find if a successor block is never executed + int oldSuccessor = -1; + SsaBasicBlock block = insn.getBlock(); + int successorSize = block.getSuccessorList().size(); + for (int i = 0; i < successorSize; i++) { + int successorBlock = block.getSuccessorList().get(i); + if (!executableBlocks.get(successorBlock)) { + oldSuccessor = successorBlock; + } + } + + /* + * Prune branches that have already been handled and ones that no + * longer have constant conditions (no nonexecutable successors) + */ + if (successorSize != 2 || oldSuccessor == -1) continue; + + // Replace branch with goto + Insn originalRopInsn = insn.getOriginalRopInsn(); + block.replaceLastInsn(new PlainInsn(Rops.GOTO, + originalRopInsn.getPosition(), null, RegisterSpecList.EMPTY)); + block.removeSuccessor(oldSuccessor); + } + } +} diff --git a/dx/src/com/android/jack/dx/ssa/SetFactory.java b/dx/src/com/android/jack/dx/ssa/SetFactory.java new file mode 100644 index 00000000..9c24b8ba --- /dev/null +++ b/dx/src/com/android/jack/dx/ssa/SetFactory.java @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.ssa; + +import com.android.jack.dx.util.BitIntSet; +import com.android.jack.dx.util.IntSet; +import com.android.jack.dx.util.ListIntSet; + + +/** + * Makes int sets for various parts of the optimizer. + */ +public final class SetFactory { + + /** + * BitIntSet/ListIntSet threshold for dominance frontier sets. These + * sets are kept per basic block until phi placement and tend to be, + * like the CFG itself, very sparse at large sizes. + * + * A value of 3072 here is somewhere around 1.125mb of total bitset size. + */ + private static final int DOMFRONT_SET_THRESHOLD_SIZE = 3072; + + /** + * BitIntSet/ListIntSet threshold for interference graph sets. These + * sets are kept per register until register allocation is done. + * + * A value of 3072 here is somewhere around 1.125mb of total bitset size. + */ + private static final int INTERFERENCE_SET_THRESHOLD_SIZE = 3072; + + /** + * BitIntSet/ListIntSet threshold for the live in/out sets kept by + * {@link SsaBasicBlock}. These are sets of SSA registers kept per basic + * block during register allocation. + * + * The total size of a bitset for this would be the count of blocks + * times the size of registers. The threshold value here is merely + * the register count, which is typically on the order of the block + * count as well. + */ + private static final int LIVENESS_SET_THRESHOLD_SIZE = 3072; + + + /** + * Make IntSet for the dominance-frontier sets. + * + * @param szBlocks {@code >=0;} count of basic blocks in method + * @return {@code non-null;} appropriate set + */ + /*package*/ static IntSet makeDomFrontSet(int szBlocks) { + return szBlocks <= DOMFRONT_SET_THRESHOLD_SIZE + ? new BitIntSet(szBlocks) + : new ListIntSet(); + } + + /** + * Make IntSet for the interference graph sets. Public because + * InterferenceGraph is in another package. + * + * @param countRegs {@code >=0;} count of SSA registers used in method + * @return {@code non-null;} appropriate set + */ + public static IntSet makeInterferenceSet(int countRegs) { + return countRegs <= INTERFERENCE_SET_THRESHOLD_SIZE + ? new BitIntSet(countRegs) + : new ListIntSet(); + } + + /** + * Make IntSet for register live in/out sets. + * + * @param countRegs {@code >=0;} count of SSA registers used in method + * @return {@code non-null;} appropriate set + */ + /*package*/ static IntSet makeLivenessSet(int countRegs) { + return countRegs <= LIVENESS_SET_THRESHOLD_SIZE + ? new BitIntSet(countRegs) + : new ListIntSet(); + } +} diff --git a/dx/src/com/android/jack/dx/ssa/SsaBasicBlock.java b/dx/src/com/android/jack/dx/ssa/SsaBasicBlock.java new file mode 100644 index 00000000..4217184d --- /dev/null +++ b/dx/src/com/android/jack/dx/ssa/SsaBasicBlock.java @@ -0,0 +1,1032 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.ssa; + +import com.android.jack.dx.rop.code.BasicBlock; +import com.android.jack.dx.rop.code.BasicBlockList; +import com.android.jack.dx.rop.code.Insn; +import com.android.jack.dx.rop.code.InsnList; +import com.android.jack.dx.rop.code.PlainInsn; +import com.android.jack.dx.rop.code.RegisterSpec; +import com.android.jack.dx.rop.code.RegisterSpecList; +import com.android.jack.dx.rop.code.Rop; +import com.android.jack.dx.rop.code.RopMethod; +import com.android.jack.dx.rop.code.Rops; +import com.android.jack.dx.rop.code.SourcePosition; +import com.android.jack.dx.util.Hex; +import com.android.jack.dx.util.IntList; +import com.android.jack.dx.util.IntSet; + +import java.util.ArrayList; +import java.util.BitSet; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +/** + * An SSA representation of a basic block. + */ +public final class SsaBasicBlock { + /** + * {@code non-null;} comparator for instances of this class that + * just compares block labels + */ + public static final Comparator LABEL_COMPARATOR = + new LabelComparator(); + + /** {@code non-null;} insn list associated with this instance */ + private ArrayList insns; + + /** {@code non-null;} predecessor set (by block list index) */ + private BitSet predecessors; + + /** {@code non-null;} successor set (by block list index) */ + private BitSet successors; + + /** + * {@code non-null;} ordered successor list + * (same block may be listed more than once) + */ + private IntList successorList; + + /** + * block list index of primary successor, or {@code -1} for no primary + * successor + */ + private int primarySuccessor = -1; + + /** label of block in rop form */ + private int ropLabel; + + /** {@code non-null;} method we belong to */ + private SsaMethod parent; + + /** our index into parent.getBlock() */ + private int index; + + /** list of dom children */ + private final ArrayList domChildren; + + /** + * the number of moves added to the end of the block during the + * phi-removal process. Retained for subsequent move scheduling. + */ + private int movesFromPhisAtEnd = 0; + + /** + * the number of moves added to the beginning of the block during the + * phi-removal process. Retained for subsequent move scheduling. + */ + private int movesFromPhisAtBeginning = 0; + + /** + * contains last computed value of reachability of this block, or -1 + * if reachability hasn't been calculated yet + */ + private int reachable = -1; + + /** + * {@code null-ok;} indexed by reg: the regs that are live-in at + * this block + */ + private IntSet liveIn; + + /** + * {@code null-ok;} indexed by reg: the regs that are live-out at + * this block + */ + private IntSet liveOut; + + /** + * Creates a new empty basic block. + * + * @param basicBlockIndex index this block will have + * @param ropLabel original rop-form label + * @param parent method of this block + */ + public SsaBasicBlock(final int basicBlockIndex, final int ropLabel, + final SsaMethod parent) { + this.parent = parent; + this.index = basicBlockIndex; + this.insns = new ArrayList(); + this.ropLabel = ropLabel; + + this.predecessors = new BitSet(parent.getBlocks().size()); + this.successors = new BitSet(parent.getBlocks().size()); + this.successorList = new IntList(); + + domChildren = new ArrayList(); + } + + /** + * Creates a new SSA basic block from a ROP form basic block. + * + * @param rmeth original method + * @param basicBlockIndex index this block will have + * @param parent method of this block predecessor set will be + * updated + * @return new instance + */ + public static SsaBasicBlock newFromRop(RopMethod rmeth, + int basicBlockIndex, final SsaMethod parent) { + BasicBlockList ropBlocks = rmeth.getBlocks(); + BasicBlock bb = ropBlocks.get(basicBlockIndex); + SsaBasicBlock result = + new SsaBasicBlock(basicBlockIndex, bb.getLabel(), parent); + InsnList ropInsns = bb.getInsns(); + + result.insns.ensureCapacity(ropInsns.size()); + + for (int i = 0, sz = ropInsns.size() ; i < sz ; i++) { + result.insns.add(new NormalSsaInsn (ropInsns.get(i), result)); + } + + result.predecessors = SsaMethod.bitSetFromLabelList( + ropBlocks, + rmeth.labelToPredecessors(bb.getLabel())); + + result.successors + = SsaMethod.bitSetFromLabelList(ropBlocks, bb.getSuccessors()); + + result.successorList + = SsaMethod.indexListFromLabelList(ropBlocks, + bb.getSuccessors()); + + if (result.successorList.size() != 0) { + int primarySuccessor = bb.getPrimarySuccessor(); + + result.primarySuccessor = (primarySuccessor < 0) + ? -1 : ropBlocks.indexOfLabel(primarySuccessor); + } + + return result; + } + + /** + * Adds a basic block as a dom child for this block. Used when constructing + * the dom tree. + * + * @param child {@code non-null;} new dom child + */ + public void addDomChild(SsaBasicBlock child) { + domChildren.add(child); + } + + /** + * Gets the dom children for this node. Don't modify this list. + * + * @return {@code non-null;} list of dom children + */ + public ArrayList getDomChildren() { + return domChildren; + } + + /** + * Adds a phi insn to the beginning of this block. The result type of + * the phi will be set to void, to indicate that it's currently unknown. + * + * @param reg {@code >=0;} result reg + */ + public void addPhiInsnForReg(int reg) { + insns.add(0, new PhiInsn(reg, this)); + } + + /** + * Adds a phi insn to the beginning of this block. This is to be used + * when the result type or local-association can be determined at phi + * insert time. + * + * @param resultSpec {@code non-null;} reg + */ + public void addPhiInsnForReg(RegisterSpec resultSpec) { + insns.add(0, new PhiInsn(resultSpec, this)); + } + + /** + * Adds an insn to the head of this basic block, just after any phi + * insns. + * + * @param insn {@code non-null;} rop-form insn to add + */ + public void addInsnToHead(Insn insn) { + SsaInsn newInsn = SsaInsn.makeFromRop(insn, this); + insns.add(getCountPhiInsns(), newInsn); + parent.onInsnAdded(newInsn); + } + + /** + * Replaces the last insn in this block. The provided insn must have + * some branchingness. + * + * @param insn {@code non-null;} rop-form insn to add, which must branch. + */ + public void replaceLastInsn(Insn insn) { + if (insn.getOpcode().getBranchingness() == Rop.BRANCH_NONE) { + throw new IllegalArgumentException("last insn must branch"); + } + + SsaInsn oldInsn = insns.get(insns.size() - 1); + SsaInsn newInsn = SsaInsn.makeFromRop(insn, this); + + insns.set(insns.size() - 1, newInsn); + + parent.onInsnRemoved(oldInsn); + parent.onInsnAdded(newInsn); + } + + /** + * Visits each phi insn. + * + * @param v {@code non-null;} the callback + */ + public void forEachPhiInsn(PhiInsn.Visitor v) { + int sz = insns.size(); + + for (int i = 0; i < sz; i++) { + SsaInsn insn = insns.get(i); + if (insn instanceof PhiInsn) { + v.visitPhiInsn((PhiInsn) insn); + } else { + /* + * Presently we assume PhiInsn's are in a continuous + * block at the top of the list + */ + break; + } + } + } + + /** + * Deletes all phi insns. Do this after adding appropriate move insns. + */ + public void removeAllPhiInsns() { + /* + * Presently we assume PhiInsn's are in a continuous + * block at the top of the list. + */ + + insns.subList(0, getCountPhiInsns()).clear(); + } + + /** + * Gets the number of phi insns at the top of this basic block. + * + * @return count of phi insns + */ + private int getCountPhiInsns() { + int countPhiInsns; + + int sz = insns.size(); + for (countPhiInsns = 0; countPhiInsns < sz; countPhiInsns++) { + SsaInsn insn = insns.get(countPhiInsns); + if (!(insn instanceof PhiInsn)) { + break; + } + } + + return countPhiInsns; + } + + /** + * @return {@code non-null;} the (mutable) instruction list for this block, + * with phi insns at the beginning + */ + public ArrayList getInsns() { + return insns; + } + + /** + * @return {@code non-null;} the (mutable) list of phi insns for this block + */ + public List getPhiInsns() { + return insns.subList(0, getCountPhiInsns()); + } + + /** + * @return the block index of this block + */ + public int getIndex() { + return index; + } + + /** + * @return the label of this block in rop form + */ + public int getRopLabel() { + return ropLabel; + } + + /** + * @return the label of this block in rop form as a hex string + */ + public String getRopLabelString() { + return Hex.u2(ropLabel); + } + + /** + * @return {@code non-null;} predecessors set, indexed by block index + */ + public BitSet getPredecessors() { + return predecessors; + } + + /** + * @return {@code non-null;} successors set, indexed by block index + */ + public BitSet getSuccessors() { + return successors; + } + + /** + * @return {@code non-null;} ordered successor list, containing block + * indicies + */ + public IntList getSuccessorList() { + return successorList; + } + + /** + * @return {@code >= -1;} block index of primary successor or + * {@code -1} if no primary successor + */ + public int getPrimarySuccessorIndex() { + return primarySuccessor; + } + + /** + * @return rop label of primary successor + */ + public int getPrimarySuccessorRopLabel() { + return parent.blockIndexToRopLabel(primarySuccessor); + } + + /** + * @return {@code null-ok;} the primary successor block or {@code null} + * if there is none + */ + public SsaBasicBlock getPrimarySuccessor() { + if (primarySuccessor < 0) { + return null; + } else { + return parent.getBlocks().get(primarySuccessor); + } + } + + /** + * @return successor list of rop labels + */ + public IntList getRopLabelSuccessorList() { + IntList result = new IntList(successorList.size()); + + int sz = successorList.size(); + + for (int i = 0; i < sz; i++) { + result.add(parent.blockIndexToRopLabel(successorList.get(i))); + } + return result; + } + + /** + * @return {@code non-null;} method that contains this block + */ + public SsaMethod getParent() { + return parent; + } + + /** + * Inserts a new empty GOTO block as a predecessor to this block. + * All previous predecessors will be predecessors to the new block. + * + * @return {@code non-null;} an appropriately-constructed instance + */ + public SsaBasicBlock insertNewPredecessor() { + SsaBasicBlock newPred = parent.makeNewGotoBlock(); + + // Update the new block. + newPred.predecessors = predecessors; + newPred.successors.set(index) ; + newPred.successorList.add(index); + newPred.primarySuccessor = index; + + + // Update us. + predecessors = new BitSet(parent.getBlocks().size()); + predecessors.set(newPred.index); + + // Update our (soon-to-be) old predecessors. + for (int i = newPred.predecessors.nextSetBit(0); i >= 0; + i = newPred.predecessors.nextSetBit(i + 1)) { + + SsaBasicBlock predBlock = parent.getBlocks().get(i); + + predBlock.replaceSuccessor(index, newPred.index); + } + + return newPred; + } + + /** + * Constructs and inserts a new empty GOTO block {@code Z} between + * this block ({@code A}) and a current successor block + * ({@code B}). The new block will replace B as A's successor and + * A as B's predecessor. A and B will no longer be directly connected. + * If B is listed as a successor multiple times, all references + * are replaced. + * + * @param other current successor (B) + * @return {@code non-null;} an appropriately-constructed instance + */ + public SsaBasicBlock insertNewSuccessor(SsaBasicBlock other) { + SsaBasicBlock newSucc = parent.makeNewGotoBlock(); + + if (!successors.get(other.index)) { + throw new RuntimeException("Block " + other.getRopLabelString() + + " not successor of " + getRopLabelString()); + } + + // Update the new block. + newSucc.predecessors.set(this.index); + newSucc.successors.set(other.index) ; + newSucc.successorList.add(other.index); + newSucc.primarySuccessor = other.index; + + // Update us. + for (int i = successorList.size() - 1 ; i >= 0; i--) { + if (successorList.get(i) == other.index) { + successorList.set(i, newSucc.index); + } + } + + if (primarySuccessor == other.index) { + primarySuccessor = newSucc.index; + } + successors.clear(other.index); + successors.set(newSucc.index); + + // Update "other". + other.predecessors.set(newSucc.index); + other.predecessors.set(index, successors.get(other.index)); + + return newSucc; + } + + /** + * Replaces an old successor with a new successor. This will throw + * RuntimeException if {@code oldIndex} was not a successor. + * + * @param oldIndex index of old successor block + * @param newIndex index of new successor block + */ + public void replaceSuccessor(int oldIndex, int newIndex) { + if (oldIndex == newIndex) { + return; + } + + // Update us. + successors.set(newIndex); + + if (primarySuccessor == oldIndex) { + primarySuccessor = newIndex; + } + + for (int i = successorList.size() - 1 ; i >= 0; i--) { + if (successorList.get(i) == oldIndex) { + successorList.set(i, newIndex); + } + } + + successors.clear(oldIndex); + + // Update new successor. + parent.getBlocks().get(newIndex).predecessors.set(index); + + // Update old successor. + parent.getBlocks().get(oldIndex).predecessors.clear(index); + } + + /** + * Removes a successor from this block's successor list. + * + * @param oldIndex index of successor block to remove + */ + public void removeSuccessor(int oldIndex) { + int removeIndex = 0; + + for (int i = successorList.size() - 1; i >= 0; i--) { + if (successorList.get(i) == oldIndex) { + removeIndex = i; + } else { + primarySuccessor = successorList.get(i); + } + } + + successorList.removeIndex(removeIndex); + successors.clear(oldIndex); + parent.getBlocks().get(oldIndex).predecessors.clear(index); + } + + /** + * Attaches block to an exit block if necessary. If this block + * is not an exit predecessor or is the exit block, this block does + * nothing. For use by {@link com.android.jack.dx.ssa.SsaMethod#makeExitBlock} + * + * @param exitBlock {@code non-null;} exit block + */ + public void exitBlockFixup(SsaBasicBlock exitBlock) { + if (this == exitBlock) { + return; + } + + if (successorList.size() == 0) { + /* + * This is an exit predecessor. + * Set the successor to the exit block + */ + successors.set(exitBlock.index); + successorList.add(exitBlock.index); + primarySuccessor = exitBlock.index; + exitBlock.predecessors.set(this.index); + } + } + + /** + * Adds a move instruction to the end of this basic block, just + * before the last instruction. If the result of the final instruction + * is the source in question, then the move is placed at the beginning of + * the primary successor block. This is for unversioned registers. + * + * @param result move destination + * @param source move source + */ + public void addMoveToEnd(RegisterSpec result, RegisterSpec source) { + + if (result.getReg() == source.getReg()) { + // Sometimes we end up with no-op moves. Ignore them here. + return; + } + + /* + * The last Insn has to be a normal SSA insn: a phi can't branch + * or return or cause an exception, etc. + */ + NormalSsaInsn lastInsn; + lastInsn = (NormalSsaInsn)insns.get(insns.size()-1); + + if (lastInsn.getResult() != null || lastInsn.getSources().size() > 0) { + /* + * The final insn in this block has a source or result + * register, and the moves we may need to place and + * schedule may interfere. We need to insert this + * instruction at the beginning of the primary successor + * block instead. We know this is safe, because when we + * edge-split earlier, we ensured that each successor has + * only us as a predecessor. + */ + + for (int i = successors.nextSetBit(0) + ; i >= 0 + ; i = successors.nextSetBit(i + 1)) { + + SsaBasicBlock succ; + + succ = parent.getBlocks().get(i); + succ.addMoveToBeginning(result, source); + } + } else { + /* + * We can safely add a move to the end of the block just + * before the last instruction, because the final insn does + * not assign to anything. + */ + RegisterSpecList sources = RegisterSpecList.make(source); + NormalSsaInsn toAdd = new NormalSsaInsn( + new PlainInsn(Rops.opMove(result.getType()), + SourcePosition.NO_INFO, result, sources), this); + + insns.add(insns.size() - 1, toAdd); + + movesFromPhisAtEnd++; + } + } + + /** + * Adds a move instruction after the phi insn block. + * + * @param result move destination + * @param source move source + */ + public void addMoveToBeginning (RegisterSpec result, RegisterSpec source) { + if (result.getReg() == source.getReg()) { + // Sometimes we end up with no-op moves. Ignore them here. + return; + } + + RegisterSpecList sources = RegisterSpecList.make(source); + NormalSsaInsn toAdd = new NormalSsaInsn( + new PlainInsn(Rops.opMove(result.getType()), + SourcePosition.NO_INFO, result, sources), this); + + insns.add(getCountPhiInsns(), toAdd); + movesFromPhisAtBeginning++; + } + + /** + * Sets the register as used in a bitset, taking into account its + * category/width. + * + * @param regsUsed set, indexed by register number + * @param rs register to mark as used + */ + private static void setRegsUsed (BitSet regsUsed, RegisterSpec rs) { + regsUsed.set(rs.getReg()); + if (rs.getCategory() > 1) { + regsUsed.set(rs.getReg() + 1); + } + } + + /** + * Checks to see if the register is used in a bitset, taking + * into account its category/width. + * + * @param regsUsed set, indexed by register number + * @param rs register to mark as used + * @return true if register is fully or partially (for the case of wide + * registers) used. + */ + private static boolean checkRegUsed (BitSet regsUsed, RegisterSpec rs) { + int reg = rs.getReg(); + int category = rs.getCategory(); + + return regsUsed.get(reg) + || (category == 2 ? regsUsed.get(reg + 1) : false); + } + + /** + * Ensures that all move operations in this block occur such that + * reads of any register happen before writes to that register. + * NOTE: caller is expected to returnSpareRegisters()! + * + * TODO: See Briggs, et al "Practical Improvements to the Construction and + * Destruction of Static Single Assignment Form" section 5. a) This can + * be done in three passes. + * + * @param toSchedule List of instructions. Must consist only of moves. + */ + private void scheduleUseBeforeAssigned(List toSchedule) { + BitSet regsUsedAsSources = new BitSet(parent.getRegCount()); + + // TODO: Get rid of this. + BitSet regsUsedAsResults = new BitSet(parent.getRegCount()); + + int sz = toSchedule.size(); + + int insertPlace = 0; + + while (insertPlace < sz) { + int oldInsertPlace = insertPlace; + + // Record all registers used as sources in this block. + for (int i = insertPlace; i < sz; i++) { + setRegsUsed(regsUsedAsSources, + toSchedule.get(i).getSources().get(0)); + + setRegsUsed(regsUsedAsResults, + toSchedule.get(i).getResult()); + } + + /* + * If there are no circular dependencies, then there exists + * n instructions where n > 1 whose result is not used as a source. + */ + for (int i = insertPlace; i + * + * This is necessary because copy-propogation may have left us in a state + * where the same basic block has the same register as a phi operand + * and a result. In this case, the register in the phi operand always + * refers value before any other phis have executed. + */ + public void scheduleMovesFromPhis() { + if (movesFromPhisAtBeginning > 1) { + List toSchedule; + + toSchedule = insns.subList(0, movesFromPhisAtBeginning); + + scheduleUseBeforeAssigned(toSchedule); + + SsaInsn firstNonPhiMoveInsn = insns.get(movesFromPhisAtBeginning); + + /* + * TODO: It's actually possible that this case never happens, + * because a move-exception block, having only one predecessor + * in SSA form, perhaps is never on a dominance frontier. + */ + if (firstNonPhiMoveInsn.isMoveException()) { + if (true) { + /* + * We've yet to observe this case, and if it can + * occur the code written to handle it probably + * does not work. + */ + throw new RuntimeException( + "Unexpected: moves from " + +"phis before move-exception"); + } else { + /* + * A move-exception insn must be placed first in this block + * We need to move it there, and deal with possible + * interference. + */ + boolean moveExceptionInterferes = false; + + int moveExceptionResult + = firstNonPhiMoveInsn.getResult().getReg(); + + /* + * Does the move-exception result reg interfere with the + * phi moves? + */ + for (SsaInsn insn : toSchedule) { + if (insn.isResultReg(moveExceptionResult) + || insn.isRegASource(moveExceptionResult)) { + moveExceptionInterferes = true; + break; + } + } + + if (!moveExceptionInterferes) { + // This is the easy case. + insns.remove(movesFromPhisAtBeginning); + insns.add(0, firstNonPhiMoveInsn); + } else { + /* + * We need to move the result to a spare reg + * and move it back. + */ + RegisterSpec originalResultSpec + = firstNonPhiMoveInsn.getResult(); + int spareRegister = parent.borrowSpareRegister( + originalResultSpec.getCategory()); + + // We now move it to a spare register. + firstNonPhiMoveInsn.changeResultReg(spareRegister); + RegisterSpec tempSpec = + firstNonPhiMoveInsn.getResult(); + + insns.add(0, firstNonPhiMoveInsn); + + // And here we move it back. + + NormalSsaInsn toAdd = new NormalSsaInsn( + new PlainInsn( + Rops.opMove(tempSpec.getType()), + SourcePosition.NO_INFO, + originalResultSpec, + RegisterSpecList.make(tempSpec)), + this); + + + /* + * Place it immediately after the phi-moves, + * overwriting the move-exception that was there. + */ + insns.set(movesFromPhisAtBeginning + 1, toAdd); + } + } + } + } + + if (movesFromPhisAtEnd > 1) { + scheduleUseBeforeAssigned( + insns.subList(insns.size() - movesFromPhisAtEnd - 1, + insns.size() - 1)); + } + + // Return registers borrowed here and in scheduleUseBeforeAssigned(). + parent.returnSpareRegisters(); + + } + + /** + * Visits all insns in this block. + * + * @param visitor {@code non-null;} callback interface + */ + public void forEachInsn(SsaInsn.Visitor visitor) { + // This gets called a LOT, and not using an iterator + // saves a lot of allocations and reduces memory usage + int len = insns.size(); + for (int i = 0; i < len; i++) { + insns.get(i).accept(visitor); + } + } + + /** {@inheritDoc} */ + @Override + public String toString() { + return "{" + index + ":" + Hex.u2(ropLabel) + '}'; + } + + /** + * Visitor interface for basic blocks. + */ + public interface Visitor { + /** + * Indicates a block has been visited by an iterator method. + * + * @param v {@code non-null;} block visited + * @param parent {@code null-ok;} parent node if applicable + */ + void visitBlock (SsaBasicBlock v, SsaBasicBlock parent); + } + + /** + * Label comparator. + */ + public static final class LabelComparator + implements Comparator { + /** {@inheritDoc} */ + public int compare(SsaBasicBlock b1, SsaBasicBlock b2) { + int label1 = b1.ropLabel; + int label2 = b2.ropLabel; + + if (label1 < label2) { + return -1; + } else if (label1 > label2) { + return 1; + } else { + return 0; + } + } + } +} diff --git a/dx/src/com/android/jack/dx/ssa/SsaConverter.java b/dx/src/com/android/jack/dx/ssa/SsaConverter.java new file mode 100644 index 00000000..89dd19dd --- /dev/null +++ b/dx/src/com/android/jack/dx/ssa/SsaConverter.java @@ -0,0 +1,391 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.ssa; + +import com.android.jack.dx.rop.code.RegisterSpec; +import com.android.jack.dx.rop.code.RopMethod; +import com.android.jack.dx.util.IntIterator; + +import java.util.ArrayList; +import java.util.BitSet; + +/** + * Converts ROP methods to SSA Methods + */ +public class SsaConverter { + public static final boolean DEBUG = false; + + /** + * Returns an SSA representation, edge-split and with phi + * functions placed. + * + * @param rmeth input + * @param paramWidth the total width, in register-units, of the method's + * parameters + * @param isStatic {@code true} if this method has no {@code this} + * pointer argument + * @return output in SSA form + */ + public static SsaMethod convertToSsaMethod(RopMethod rmeth, + int paramWidth, boolean isStatic) { + SsaMethod result + = SsaMethod.newFromRopMethod(rmeth, paramWidth, isStatic); + + edgeSplit(result); + + LocalVariableInfo localInfo = LocalVariableExtractor.extract(result); + + placePhiFunctions(result, localInfo, 0); + new SsaRenamer(result).run(); + + /* + * The exit block, added here, is not considered for edge splitting + * or phi placement since no actual control flows to it. + */ + result.makeExitBlock(); + + return result; + } + + /** + * Updates an SSA representation, placing phi functions and renaming all + * registers above a certain threshold number. + * + * @param ssaMeth input + * @param threshold registers below this number are unchanged + */ + public static void updateSsaMethod(SsaMethod ssaMeth, int threshold) { + LocalVariableInfo localInfo = LocalVariableExtractor.extract(ssaMeth); + placePhiFunctions(ssaMeth, localInfo, threshold); + new SsaRenamer(ssaMeth, threshold).run(); + } + + /** + * Returns an SSA represention with only the edge-splitter run. + * + * @param rmeth method to process + * @param paramWidth width of all arguments in the method + * @param isStatic {@code true} if this method has no {@code this} + * pointer argument + * @return an SSA represention with only the edge-splitter run + */ + public static SsaMethod testEdgeSplit (RopMethod rmeth, int paramWidth, + boolean isStatic) { + SsaMethod result; + + result = SsaMethod.newFromRopMethod(rmeth, paramWidth, isStatic); + + edgeSplit(result); + return result; + } + + /** + * Returns an SSA represention with only the steps through the + * phi placement run. + * + * @param rmeth method to process + * @param paramWidth width of all arguments in the method + * @param isStatic {@code true} if this method has no {@code this} + * pointer argument + * @return an SSA represention with only the edge-splitter run + */ + public static SsaMethod testPhiPlacement (RopMethod rmeth, int paramWidth, + boolean isStatic) { + SsaMethod result; + + result = SsaMethod.newFromRopMethod(rmeth, paramWidth, isStatic); + + edgeSplit(result); + + LocalVariableInfo localInfo = LocalVariableExtractor.extract(result); + + placePhiFunctions(result, localInfo, 0); + return result; + } + + /** + * See Appel section 19.1: + * + * Converts CFG into "edge-split" form, such that each node either a + * unique successor or unique predecessor.

      + * + * In addition, the SSA form we use enforces a further constraint, + * requiring each block with a final instruction that returns a + * value to have a primary successor that has no other + * predecessor. This ensures move statements can always be + * inserted correctly when phi statements are removed. + * + * @param result method to process + */ + private static void edgeSplit(SsaMethod result) { + edgeSplitPredecessors(result); + edgeSplitMoveExceptionsAndResults(result); + edgeSplitSuccessors(result); + } + + /** + * Inserts Z nodes as new predecessors for every node that has multiple + * successors and multiple predecessors. + * + * @param result {@code non-null;} method to process + */ + private static void edgeSplitPredecessors(SsaMethod result) { + ArrayList blocks = result.getBlocks(); + + /* + * New blocks are added to the end of the block list during + * this iteration. + */ + for (int i = blocks.size() - 1; i >= 0; i-- ) { + SsaBasicBlock block = blocks.get(i); + if (nodeNeedsUniquePredecessor(block)) { + block.insertNewPredecessor(); + } + } + } + + /** + * @param block {@code non-null;} block in question + * @return {@code true} if this node needs to have a unique + * predecessor created for it + */ + private static boolean nodeNeedsUniquePredecessor(SsaBasicBlock block) { + /* + * Any block with that has both multiple successors and multiple + * predecessors needs a new predecessor node. + */ + + int countPredecessors = block.getPredecessors().cardinality(); + int countSuccessors = block.getSuccessors().cardinality(); + + return (countPredecessors > 1 && countSuccessors > 1); + } + + /** + * In ROP form, move-exception must occur as the first insn in a block + * immediately succeeding the insn that could thrown an exception. + * We may need room to insert move insns later, so make sure to split + * any block that starts with a move-exception such that there is a + * unique move-exception block for each predecessor. + * + * @param ssaMeth method to process + */ + private static void edgeSplitMoveExceptionsAndResults(SsaMethod ssaMeth) { + ArrayList blocks = ssaMeth.getBlocks(); + + /* + * New blocks are added to the end of the block list during + * this iteration. + */ + for (int i = blocks.size() - 1; i >= 0; i-- ) { + SsaBasicBlock block = blocks.get(i); + + /* + * Any block that starts with a move-exception and has more than + * one predecessor... + */ + if (!block.isExitBlock() + && block.getPredecessors().cardinality() > 1 + && block.getInsns().get(0).isMoveException()) { + + // block.getPredecessors() is changed in the loop below. + BitSet preds = (BitSet)block.getPredecessors().clone(); + for (int j = preds.nextSetBit(0); j >= 0; + j = preds.nextSetBit(j + 1)) { + SsaBasicBlock predecessor = blocks.get(j); + SsaBasicBlock zNode + = predecessor.insertNewSuccessor(block); + + /* + * Make sure to place the move-exception as the + * first insn. + */ + zNode.getInsns().add(0, block.getInsns().get(0).clone()); + } + + // Remove the move-exception from the original block. + block.getInsns().remove(0); + } + } + } + + /** + * Inserts Z nodes for every node that needs a new + * successor. + * + * @param result {@code non-null;} method to process + */ + private static void edgeSplitSuccessors(SsaMethod result) { + ArrayList blocks = result.getBlocks(); + + /* + * New blocks are added to the end of the block list during + * this iteration. + */ + for (int i = blocks.size() - 1; i >= 0; i-- ) { + SsaBasicBlock block = blocks.get(i); + + // Successors list is modified in loop below. + BitSet successors = (BitSet)block.getSuccessors().clone(); + for (int j = successors.nextSetBit(0); + j >= 0; j = successors.nextSetBit(j+1)) { + + SsaBasicBlock succ = blocks.get(j); + + if (needsNewSuccessor(block, succ)) { + block.insertNewSuccessor(succ); + } + } + } + } + + /** + * Returns {@code true} if block and successor need a Z-node + * between them. Presently, this is {@code true} if the final + * instruction has any sources or results and the current + * successor block has more than one predecessor. + * + * @param block predecessor node + * @param succ successor node + * @return {@code true} if a Z node is needed + */ + private static boolean needsNewSuccessor(SsaBasicBlock block, + SsaBasicBlock succ) { + ArrayList insns = block.getInsns(); + SsaInsn lastInsn = insns.get(insns.size() - 1); + + return ((lastInsn.getResult() != null) + || (lastInsn.getSources().size() > 0)) + && succ.getPredecessors().cardinality() > 1; + } + + /** + * See Appel algorithm 19.6: + * + * Place Phi functions in appropriate locations. + * + * @param ssaMeth {@code non-null;} method to process. + * Modifications are made in-place. + * @param localInfo {@code non-null;} local variable info, used + * when placing phis + * @param threshold registers below this number are ignored + */ + private static void placePhiFunctions (SsaMethod ssaMeth, + LocalVariableInfo localInfo, int threshold) { + ArrayList ssaBlocks; + int regCount; + int blockCount; + + ssaBlocks = ssaMeth.getBlocks(); + blockCount = ssaBlocks.size(); + regCount = ssaMeth.getRegCount() - threshold; + + DomFront df = new DomFront(ssaMeth); + DomFront.DomInfo[] domInfos = df.run(); + + // Bit set of registers vs block index "definition sites" + BitSet[] defsites = new BitSet[regCount]; + + // Bit set of registers vs block index "phi placement sites" + BitSet[] phisites = new BitSet[regCount]; + + for (int i = 0; i < regCount; i++) { + defsites[i] = new BitSet(blockCount); + phisites[i] = new BitSet(blockCount); + } + + /* + * For each register, build a set of all basic blocks where + * containing an assignment to that register. + */ + for (int bi = 0, s = ssaBlocks.size(); bi < s; bi++) { + SsaBasicBlock b = ssaBlocks.get(bi); + + for (SsaInsn insn : b.getInsns()) { + RegisterSpec rs = insn.getResult(); + + if (rs != null && rs.getReg() - threshold >= 0) { + defsites[rs.getReg() - threshold].set(bi); + } + } + } + + if (DEBUG) { + System.out.println("defsites"); + + for (int i = 0; i < regCount; i++) { + StringBuilder sb = new StringBuilder(); + sb.append('v').append(i).append(": "); + sb.append(defsites[i].toString()); + System.out.println(sb); + } + } + + BitSet worklist; + + /* + * For each register, compute all locations for phi placement + * based on dominance-frontier algorithm. + */ + for (int reg = 0, s = regCount; reg < s; reg++) { + int workBlockIndex; + + /* Worklist set starts out with each node where reg is assigned. */ + + worklist = (BitSet) (defsites[reg].clone()); + + while (0 <= (workBlockIndex = worklist.nextSetBit(0))) { + worklist.clear(workBlockIndex); + IntIterator dfIterator + = domInfos[workBlockIndex].dominanceFrontiers.iterator(); + + while (dfIterator.hasNext()) { + int dfBlockIndex = dfIterator.next(); + + if (!phisites[reg].get(dfBlockIndex)) { + phisites[reg].set(dfBlockIndex); + + int tReg = reg + threshold; + RegisterSpec rs + = localInfo.getStarts(dfBlockIndex).get(tReg); + + if (rs == null) { + ssaBlocks.get(dfBlockIndex).addPhiInsnForReg(tReg); + } else { + ssaBlocks.get(dfBlockIndex).addPhiInsnForReg(rs); + } + + if (!defsites[reg].get(dfBlockIndex)) { + worklist.set(dfBlockIndex); + } + } + } + } + } + + if (DEBUG) { + System.out.println("phisites"); + + for (int i = 0; i < regCount; i++) { + StringBuilder sb = new StringBuilder(); + sb.append('v').append(i).append(": "); + sb.append(phisites[i].toString()); + System.out.println(sb); + } + } + } +} diff --git a/dx/src/com/android/jack/dx/ssa/SsaInsn.java b/dx/src/com/android/jack/dx/ssa/SsaInsn.java new file mode 100644 index 00000000..bf76db26 --- /dev/null +++ b/dx/src/com/android/jack/dx/ssa/SsaInsn.java @@ -0,0 +1,289 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.ssa; + +import com.android.jack.dx.rop.code.*; +import com.android.jack.dx.util.ToHuman; + +/** + * An instruction in SSA form + */ +public abstract class SsaInsn implements ToHuman, Cloneable { + /** {@code non-null;} the block that contains this instance */ + private final SsaBasicBlock block; + + /** {@code null-ok;} result register */ + private RegisterSpec result; + + /** + * Constructs an instance. + * + * @param result {@code null-ok;} initial result register. May be changed. + * @param block {@code non-null;} block containing this insn. Can + * never change. + */ + protected SsaInsn(RegisterSpec result, SsaBasicBlock block) { + if (block == null) { + throw new NullPointerException("block == null"); + } + + this.block = block; + this.result = result; + } + + /** + * Makes a new SSA insn form a rop insn. + * + * @param insn {@code non-null;} rop insn + * @param block {@code non-null;} owning block + * @return {@code non-null;} an appropriately constructed instance + */ + public static SsaInsn makeFromRop(Insn insn, SsaBasicBlock block) { + return new NormalSsaInsn(insn, block); + } + + /** {@inheritDoc} */ + @Override + public SsaInsn clone() { + try { + return (SsaInsn)super.clone(); + } catch (CloneNotSupportedException ex) { + throw new RuntimeException ("unexpected", ex); + } + } + + /** + * Like {@link com.android.jack.dx.rop.code.Insn getResult()}. + * + * @return result register + */ + public RegisterSpec getResult() { + return result; + } + + /** + * Set the result register. + * + * @param result {@code non-null;} the new result register + */ + protected void setResult(RegisterSpec result) { + if (result == null) { + throw new NullPointerException("result == null"); + } + + this.result = result; + } + + /** + * Like {@link com.android.jack.dx.rop.code.Insn getSources()}. + * + * @return {@code non-null;} sources list + */ + abstract public RegisterSpecList getSources(); + + /** + * Gets the block to which this insn instance belongs. + * + * @return owning block + */ + public SsaBasicBlock getBlock() { + return block; + } + + /** + * Returns whether or not the specified reg is the result reg. + * + * @param reg register to test + * @return true if there is a result and it is stored in the specified + * register + */ + public boolean isResultReg(int reg) { + return result != null && result.getReg() == reg; + } + + + /** + * Changes the result register if this insn has a result. This is used + * during renaming. + * + * @param reg new result register + */ + public void changeResultReg(int reg) { + if (result != null) { + result = result.withReg(reg); + } + } + + /** + * Sets the local association for the result of this insn. This is + * sometimes updated during the SsaRenamer process. + * + * @param local {@code null-ok;} new debug/local variable info + */ + public final void setResultLocal(LocalItem local) { + LocalItem oldItem = result.getLocalItem(); + + if (local != oldItem && (local == null + || !local.equals(result.getLocalItem()))) { + result = RegisterSpec.makeLocalOptional( + result.getReg(), result.getType(), local); + } + } + + /** + * Map registers after register allocation. + * + * @param mapper {@code non-null;} mapping from old to new registers + */ + public final void mapRegisters(RegisterMapper mapper) { + RegisterSpec oldResult = result; + + result = mapper.map(result); + block.getParent().updateOneDefinition(this, oldResult); + mapSourceRegisters(mapper); + } + + /** + * Maps only source registers. + * + * @param mapper new mapping + */ + abstract public void mapSourceRegisters(RegisterMapper mapper); + + /** + * Returns the Rop opcode for this insn, or null if this is a phi insn. + * + * TODO: Move this up into NormalSsaInsn. + * + * @return {@code null-ok;} Rop opcode if there is one. + */ + abstract public Rop getOpcode(); + + /** + * Returns the original Rop insn for this insn, or null if this is + * a phi insn. + * + * TODO: Move this up into NormalSsaInsn. + * + * @return {@code null-ok;} Rop insn if there is one. + */ + abstract public Insn getOriginalRopInsn(); + + /** + * Gets the spec of a local variable assignment that occurs at this + * instruction, or null if no local variable assignment occurs. This + * may be the result register, or for {@code mark-local} insns + * it may be the source. + * + * @see com.android.jack.dx.rop.code.Insn#getLocalAssignment() + * + * @return {@code null-ok;} a local-associated register spec or null + */ + public RegisterSpec getLocalAssignment() { + if (result != null && result.getLocalItem() != null) { + return result; + } + + return null; + } + + /** + * Indicates whether the specified register is amongst the registers + * used as sources for this instruction. + * + * @param reg the register in question + * @return true if the reg is a source + */ + public boolean isRegASource(int reg) { + return null != getSources().specForRegister(reg); + } + + /** + * Transform back to ROP form. + * + * TODO: Move this up into NormalSsaInsn. + * + * @return {@code non-null;} a ROP representation of this instruction, with + * updated registers. + */ + public abstract Insn toRopInsn(); + + /** + * @return true if this is a PhiInsn or a normal move insn + */ + public abstract boolean isPhiOrMove(); + + /** + * Returns true if this insn is considered to have a side effect beyond + * that of assigning to the result reg. + * + * @return true if this insn is considered to have a side effect beyond + * that of assigning to the result reg. + */ + public abstract boolean hasSideEffect(); + + /** + * @return true if this is a move (but not a move-operand or + * move-exception) instruction + */ + public boolean isNormalMoveInsn() { + return false; + } + + /** + * @return true if this is a move-exception instruction. + * These instructions must immediately follow a preceeding invoke* + */ + public boolean isMoveException() { + return false; + } + + /** + * @return true if this instruction can throw. + */ + abstract public boolean canThrow(); + + /** + * Accepts a visitor. + * + * @param v {@code non-null} the visitor + */ + public abstract void accept(Visitor v); + + /** + * Visitor interface for this class. + */ + public static interface Visitor { + /** + * Any non-phi move instruction + * @param insn {@code non-null;} the instruction to visit + */ + public void visitMoveInsn(NormalSsaInsn insn); + + /** + * Any phi insn + * @param insn {@code non-null;} the instruction to visit + */ + public void visitPhiInsn(PhiInsn insn); + + /** + * Any insn that isn't a move or a phi (which is also a move). + * @param insn {@code non-null;} the instruction to visit + */ + public void visitNonMoveInsn(NormalSsaInsn insn); + } +} diff --git a/dx/src/com/android/jack/dx/ssa/SsaMethod.java b/dx/src/com/android/jack/dx/ssa/SsaMethod.java new file mode 100644 index 00000000..f0166df3 --- /dev/null +++ b/dx/src/com/android/jack/dx/ssa/SsaMethod.java @@ -0,0 +1,873 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.ssa; + +import com.android.jack.dx.rop.code.BasicBlockList; +import com.android.jack.dx.rop.code.Insn; +import com.android.jack.dx.rop.code.PlainInsn; +import com.android.jack.dx.rop.code.RegOps; +import com.android.jack.dx.rop.code.RegisterSpec; +import com.android.jack.dx.rop.code.RegisterSpecList; +import com.android.jack.dx.rop.code.Rop; +import com.android.jack.dx.rop.code.RopMethod; +import com.android.jack.dx.rop.code.Rops; +import com.android.jack.dx.rop.code.SourcePosition; +import com.android.jack.dx.util.IntList; + +import java.util.ArrayList; +import java.util.BitSet; +import java.util.Collections; +import java.util.List; +import java.util.Set; +import java.util.Stack; + +/** + * A method in SSA form. + */ +public final class SsaMethod { + /** basic blocks, indexed by block index */ + private ArrayList blocks; + + /** Index of first executed block in method */ + private int entryBlockIndex; + + /** + * Index of exit block, which exists only in SSA form, + * or or {@code -1} if there is none + */ + private int exitBlockIndex; + + /** total number of registers required */ + private int registerCount; + + /** first register number to use for any temporary "spares" */ + private int spareRegisterBase; + + /** current count of spare registers used */ + private int borrowedSpareRegisters; + + /** really one greater than the max label */ + private int maxLabel; + + /** the total width, in register-units, of the method's parameters */ + private final int paramWidth; + + /** true if this method has no {@code this} pointer argument */ + private final boolean isStatic; + + /** + * indexed by register: the insn where said register is defined or null + * if undefined. null until (lazily) created. + */ + private SsaInsn[] definitionList; + + /** indexed by register: the list of all insns that use a register */ + private ArrayList[] useList; + + /** A version of useList with each List unmodifiable */ + private List[] unmodifiableUseList; + + /** + * "back-convert mode". Set during back-conversion when registers + * are about to be mapped into a non-SSA namespace. When true, + * use and def lists are unavailable. + * + * TODO: Remove this mode, and place the functionality elsewhere + */ + private boolean backMode; + + /** + * @param ropMethod rop-form method to convert from + * @param paramWidth the total width, in register-units, of the + * method's parameters + * @param isStatic {@code true} if this method has no {@code this} + * pointer argument + */ + public static SsaMethod newFromRopMethod(RopMethod ropMethod, + int paramWidth, boolean isStatic) { + SsaMethod result = new SsaMethod(ropMethod, paramWidth, isStatic); + + result.convertRopToSsaBlocks(ropMethod); + + return result; + } + + /** + * Constructs an instance. + * + * @param ropMethod {@code non-null;} the original rop-form method that + * this instance is based on + * @param paramWidth the total width, in register-units, of the + * method's parameters + * @param isStatic {@code true} if this method has no {@code this} + * pointer argument + */ + private SsaMethod(RopMethod ropMethod, int paramWidth, boolean isStatic) { + this.paramWidth = paramWidth; + this.isStatic = isStatic; + this.backMode = false; + this.maxLabel = ropMethod.getBlocks().getMaxLabel(); + this.registerCount = ropMethod.getBlocks().getRegCount(); + this.spareRegisterBase = registerCount; + } + + /** + * Builds a BitSet of block indices from a basic block list and a list + * of labels taken from Rop form. + * + * @param blocks Rop blocks + * @param labelList list of rop block labels + * @return BitSet of block indices + */ + static BitSet bitSetFromLabelList(BasicBlockList blocks, + IntList labelList) { + BitSet result = new BitSet(blocks.size()); + + for (int i = 0, sz = labelList.size(); i < sz; i++) { + result.set(blocks.indexOfLabel(labelList.get(i))); + } + + return result; + } + + /** + * Builds an IntList of block indices from a basic block list and a list + * of labels taken from Rop form. + * + * @param ropBlocks Rop blocks + * @param labelList list of rop block labels + * @return IntList of block indices + */ + public static IntList indexListFromLabelList(BasicBlockList ropBlocks, + IntList labelList) { + + IntList result = new IntList(labelList.size()); + + for (int i = 0, sz = labelList.size(); i < sz; i++) { + result.add(ropBlocks.indexOfLabel(labelList.get(i))); + } + + return result; + } + + private void convertRopToSsaBlocks(RopMethod rmeth) { + BasicBlockList ropBlocks = rmeth.getBlocks(); + int sz = ropBlocks.size(); + + blocks = new ArrayList(sz + 2); + + for (int i = 0; i < sz; i++) { + SsaBasicBlock sbb = SsaBasicBlock.newFromRop(rmeth, i, this); + blocks.add(sbb); + } + + // Add an no-op entry block. + int origEntryBlockIndex = rmeth.getBlocks() + .indexOfLabel(rmeth.getFirstLabel()); + + SsaBasicBlock entryBlock + = blocks.get(origEntryBlockIndex).insertNewPredecessor(); + + entryBlockIndex = entryBlock.getIndex(); + exitBlockIndex = -1; // This gets made later. + } + + /** + * Creates an exit block and attaches it to the CFG if this method + * exits. Methods that never exit will not have an exit block. This + * is called after edge-splitting and phi insertion, since the edges + * going into the exit block should not be considered in those steps. + */ + /*package*/ void makeExitBlock() { + if (exitBlockIndex >= 0) { + throw new RuntimeException("must be called at most once"); + } + + exitBlockIndex = blocks.size(); + SsaBasicBlock exitBlock + = new SsaBasicBlock(exitBlockIndex, maxLabel++, this); + + blocks.add(exitBlock); + + for (SsaBasicBlock block : blocks) { + block.exitBlockFixup(exitBlock); + } + + if (exitBlock.getPredecessors().cardinality() == 0) { + // In cases where there is no exit... + blocks.remove(exitBlockIndex); + exitBlockIndex = -1; + maxLabel--; + } + } + + /** + * Gets a new {@code GOTO} insn. + * + * @param block block to which this GOTO will be added + * (not it's destination!) + * @return an appropriately-constructed instance. + */ + private static SsaInsn getGoto(SsaBasicBlock block) { + return new NormalSsaInsn ( + new PlainInsn(Rops.GOTO, SourcePosition.NO_INFO, + null, RegisterSpecList.EMPTY), block); + } + + /** + * Makes a new basic block for this method, which is empty besides + * a single {@code GOTO}. Successors and predecessors are not yet + * set. + * + * @return new block + */ + public SsaBasicBlock makeNewGotoBlock() { + int newIndex = blocks.size(); + SsaBasicBlock newBlock = new SsaBasicBlock(newIndex, maxLabel++, this); + + newBlock.getInsns().add(getGoto(newBlock)); + blocks.add(newBlock); + + return newBlock; + } + + /** + * @return block index of first execution block + */ + public int getEntryBlockIndex() { + return entryBlockIndex; + } + + /** + * @return first execution block + */ + public SsaBasicBlock getEntryBlock() { + return blocks.get(entryBlockIndex); + } + + /** + * @return block index of exit block or {@code -1} if there is none + */ + public int getExitBlockIndex() { + return exitBlockIndex; + } + + /** + * @return {@code null-ok;} block of exit block or {@code null} if + * there is none + */ + public SsaBasicBlock getExitBlock() { + return exitBlockIndex < 0 ? null : blocks.get(exitBlockIndex); + } + + /** + * @param bi block index or {@code -1} for none + * @return rop label or {code -1} if {@code bi} was {@code -1} + */ + public int blockIndexToRopLabel(int bi) { + if (bi < 0) { + return -1; + } + return blocks.get(bi).getRopLabel(); + } + + /** + * @return count of registers used in this method + */ + public int getRegCount() { + return registerCount; + } + + /** + * @return the total width, in register units, of the method's + * parameters + */ + public int getParamWidth() { + return paramWidth; + } + + /** + * Returns {@code true} if this is a static method. + * + * @return {@code true} if this is a static method + */ + public boolean isStatic() { + return isStatic; + } + + /** + * Borrows a register to use as a temp. Used in the phi removal process. + * Call returnSpareRegisters() when done. + * + * @param category width (1 or 2) of the register + * @return register number to use + */ + public int borrowSpareRegister(int category) { + int result = spareRegisterBase + borrowedSpareRegisters; + + borrowedSpareRegisters += category; + registerCount = Math.max(registerCount, result + category); + + return result; + } + + /** + * Returns all borrowed registers. + */ + public void returnSpareRegisters() { + borrowedSpareRegisters = 0; + } + + /** + * @return {@code non-null;} basic block list. Do not modify. + */ + public ArrayList getBlocks() { + return blocks; + } + + /** + * Returns the count of reachable blocks in this method: blocks that have + * predecessors (or are the start block) + * + * @return {@code >= 0;} number of reachable basic blocks + */ + public int getCountReachableBlocks() { + int ret = 0; + + for (SsaBasicBlock b : blocks) { + // Blocks that have been disconnected don't count. + if (b.isReachable()) { + ret++; + } + } + + return ret; + } + + /** + * Computes reachability for all blocks in the method. First clears old + * values from all blocks, then starts with the entry block and walks down + * the control flow graph, marking all blocks it finds as reachable. + */ + public void computeReachability() { + for (SsaBasicBlock block : blocks) { + block.setReachable(0); + } + + ArrayList blockList = new ArrayList(); + blockList.add(this.getEntryBlock()); + + while (!blockList.isEmpty()) { + SsaBasicBlock block = blockList.remove(0); + if (block.isReachable()) continue; + + block.setReachable(1); + BitSet succs = block.getSuccessors(); + for (int i = succs.nextSetBit(0); i >= 0; + i = succs.nextSetBit(i + 1)) { + blockList.add(blocks.get(i)); + } + } + } + + /** + * Remaps unversioned registers. + * + * @param mapper maps old registers to new. + */ + public void mapRegisters(RegisterMapper mapper) { + for (SsaBasicBlock block : getBlocks()) { + for (SsaInsn insn : block.getInsns()) { + insn.mapRegisters(mapper); + } + } + + registerCount = mapper.getNewRegisterCount(); + spareRegisterBase = registerCount; + } + + /** + * Returns the insn that defines the given register + * @param reg register in question + * @return insn (actual instance from code) that defined this reg or null + * if reg is not defined. + */ + public SsaInsn getDefinitionForRegister(int reg) { + if (backMode) { + throw new RuntimeException("No def list in back mode"); + } + + if (definitionList != null) { + return definitionList[reg]; + } + + definitionList = new SsaInsn[getRegCount()]; + + forEachInsn(new SsaInsn.Visitor() { + public void visitMoveInsn (NormalSsaInsn insn) { + definitionList[insn.getResult().getReg()] = insn; + } + public void visitPhiInsn (PhiInsn phi) { + definitionList[phi.getResult().getReg()] = phi; + } + public void visitNonMoveInsn (NormalSsaInsn insn) { + RegisterSpec result = insn.getResult(); + if (result != null) { + definitionList[insn.getResult().getReg()] = insn; + } + } + }); + + return definitionList[reg]; + } + + /** + * Builds useList and unmodifiableUseList. + */ + private void buildUseList() { + if (backMode) { + throw new RuntimeException("No use list in back mode"); + } + + useList = new ArrayList[registerCount]; + + for (int i = 0; i < registerCount; i++) { + useList[i] = new ArrayList(); + } + + forEachInsn(new SsaInsn.Visitor() { + /** {@inheritDoc} */ + public void visitMoveInsn (NormalSsaInsn insn) { + addToUses(insn); + } + /** {@inheritDoc} */ + public void visitPhiInsn (PhiInsn phi) { + addToUses(phi); + } + /** {@inheritDoc} */ + public void visitNonMoveInsn (NormalSsaInsn insn) { + addToUses(insn); + } + /** + * Adds specified insn to the uses list for all of its sources. + * @param insn {@code non-null;} insn to process + */ + private void addToUses(SsaInsn insn) { + RegisterSpecList rl = insn.getSources(); + int sz = rl.size(); + + for (int i = 0; i < sz; i++) { + useList[rl.get(i).getReg()].add(insn); + } + } + }); + + unmodifiableUseList = new List[registerCount]; + + for (int i = 0; i < registerCount; i++) { + unmodifiableUseList[i] = Collections.unmodifiableList(useList[i]); + } + } + + /** + * Updates the use list for a single change in source register. + * + * @param insn {@code non-null;} insn being changed + * @param oldSource {@code null-ok;} The source that was used, if + * applicable + * @param newSource {@code non-null;} the new source being used + */ + /*package*/ void onSourceChanged(SsaInsn insn, + RegisterSpec oldSource, RegisterSpec newSource) { + if (useList == null) return; + + if (oldSource != null) { + int reg = oldSource.getReg(); + useList[reg].remove(insn); + } + + int reg = newSource.getReg(); + if (useList.length <= reg) { + useList = null; + return; + } + useList[reg].add(insn); + } + + /** + * Updates the use list for a source list change. + * + * @param insn {@code insn non-null;} insn being changed. + * {@code insn.getSources()} must return the new source list. + * @param oldSources {@code null-ok;} list of sources that were + * previously used + */ + /*package*/ void onSourcesChanged(SsaInsn insn, + RegisterSpecList oldSources) { + if (useList == null) return; + + if (oldSources != null) { + removeFromUseList(insn, oldSources); + } + + RegisterSpecList sources = insn.getSources(); + int szNew = sources.size(); + + for (int i = 0; i < szNew; i++) { + int reg = sources.get(i).getReg(); + useList[reg].add(insn); + } + } + + /** + * Removes a given {@code insn} from the use lists for the given + * {@code oldSources} (rather than the sources currently + * returned by insn.getSources()). + * + * @param insn {@code non-null;} insn in question + * @param oldSources {@code null-ok;} registers whose use lists + * {@code insn} should be removed form + */ + private void removeFromUseList(SsaInsn insn, RegisterSpecList oldSources) { + if (oldSources == null) { + return; + } + + int szNew = oldSources.size(); + for (int i = 0; i < szNew; i++) { + if (!useList[oldSources.get(i).getReg()].remove(insn)) { + throw new RuntimeException("use not found"); + } + } + } + + /** + * Adds an insn to both the use and def lists. For use when adding + * a new insn to the method. + * + * @param insn {@code non-null;} insn to add + */ + /*package*/ void onInsnAdded(SsaInsn insn) { + onSourcesChanged(insn, null); + updateOneDefinition(insn, null); + } + + /** + * Removes an instruction from use and def lists. For use during + * instruction removal. + * + * @param insn {@code non-null;} insn to remove + */ + /*package*/ void onInsnRemoved(SsaInsn insn) { + if (useList != null) { + removeFromUseList(insn, insn.getSources()); + } + + RegisterSpec resultReg = insn.getResult(); + if (definitionList != null && resultReg != null) { + definitionList[resultReg.getReg()] = null; + } + } + + /** + * Indicates that the instruction list has changed or the SSA register + * count has increased, so that internal datastructures that rely on + * it should be rebuild. In general, the various other on* methods + * should be called in preference when changes occur if they are + * applicable. + */ + public void onInsnsChanged() { + // Definition list will need to be recomputed + definitionList = null; + + // Use list will need to be recomputed + useList = null; + unmodifiableUseList = null; + } + + /** + * Updates a single definition. + * + * @param insn {@code non-null;} insn who's result should be recorded as + * a definition + * @param oldResult {@code null-ok;} a previous result that should + * be no longer considered a definition by this insn + */ + /*package*/ void updateOneDefinition(SsaInsn insn, + RegisterSpec oldResult) { + if (definitionList == null) return; + + if (oldResult != null) { + int reg = oldResult.getReg(); + definitionList[reg] = null; + } + + RegisterSpec resultReg = insn.getResult(); + + if (resultReg != null) { + int reg = resultReg.getReg(); + + if (definitionList[reg] != null) { + throw new RuntimeException("Duplicate add of insn"); + } else { + definitionList[resultReg.getReg()] = insn; + } + } + } + + /** + * Returns the list of all source uses (not results) for a register. + * + * @param reg register in question + * @return unmodifiable instruction list + */ + public List getUseListForRegister(int reg) { + + if (unmodifiableUseList == null) { + buildUseList(); + } + + return unmodifiableUseList[reg]; + } + + /** + * Returns a modifiable copy of the register use list. + * + * @return modifiable copy of the use-list, indexed by register + */ + public ArrayList[] getUseListCopy() { + if (useList == null) { + buildUseList(); + } + + ArrayList[] useListCopy + = (ArrayList[])(new ArrayList[registerCount]); + + for (int i = 0; i < registerCount; i++) { + useListCopy[i] = (ArrayList)(new ArrayList(useList[i])); + } + + return useListCopy; + } + + /** + * Checks to see if the given SSA reg is ever associated with a local + * local variable. Each SSA reg may be associated with at most one + * local var. + * + * @param spec {@code non-null;} ssa reg + * @return true if reg is ever associated with a local + */ + public boolean isRegALocal(RegisterSpec spec) { + SsaInsn defn = getDefinitionForRegister(spec.getReg()); + + if (defn == null) { + // version 0 registers are never used as locals + return false; + } + + // Does the definition have a local associated with it? + if (defn.getLocalAssignment() != null) return true; + + // If not, is there a mark-local insn? + for (SsaInsn use : getUseListForRegister(spec.getReg())) { + Insn insn = use.getOriginalRopInsn(); + + if (insn != null + && insn.getOpcode().getOpcode() == RegOps.MARK_LOCAL) { + return true; + } + } + + return false; + } + + /** + * Sets the new register count after renaming. + * + * @param newRegCount new register count + */ + /*package*/ void setNewRegCount(int newRegCount) { + registerCount = newRegCount; + spareRegisterBase = registerCount; + onInsnsChanged(); + } + + /** + * Makes a new SSA register. For use after renaming has completed. + * + * @return {@code >=0;} new SSA register. + */ + public int makeNewSsaReg() { + int reg = registerCount++; + spareRegisterBase = registerCount; + onInsnsChanged(); + return reg; + } + + /** + * Visits all insns in this method. + * + * @param visitor {@code non-null;} callback interface + */ + public void forEachInsn(SsaInsn.Visitor visitor) { + for (SsaBasicBlock block : blocks) { + block.forEachInsn(visitor); + } + } + + /** + * Visits each phi insn in this method + * @param v {@code non-null;} callback. + * + */ + public void forEachPhiInsn(PhiInsn.Visitor v) { + for (SsaBasicBlock block : blocks) { + block.forEachPhiInsn(v); + } + } + + + /** + * Walks the basic block tree in depth-first order, calling the visitor + * method once for every block. This depth-first walk may be run forward + * from the method entry point or backwards from the method exit points. + * + * @param reverse true if this should walk backwards from the exit points + * @param v {@code non-null;} callback interface. {@code parent} is set + * unless this is the root node + */ + public void forEachBlockDepthFirst(boolean reverse, + SsaBasicBlock.Visitor v) { + BitSet visited = new BitSet(blocks.size()); + + // We push the parent first, then the child on the stack. + Stack stack = new Stack(); + + SsaBasicBlock rootBlock = reverse ? getExitBlock() : getEntryBlock(); + + if (rootBlock == null) { + // in the case there's no exit block + return; + } + + stack.add(null); // Start with null parent. + stack.add(rootBlock); + + while (stack.size() > 0) { + SsaBasicBlock cur = stack.pop(); + SsaBasicBlock parent = stack.pop(); + + if (!visited.get(cur.getIndex())) { + BitSet children + = reverse ? cur.getPredecessors() : cur.getSuccessors(); + for (int i = children.nextSetBit(0); i >= 0 + ; i = children.nextSetBit(i + 1)) { + stack.add(cur); + stack.add(blocks.get(i)); + } + visited.set(cur.getIndex()); + v.visitBlock(cur, parent); + } + } + } + + /** + * Visits blocks in dom-tree order, starting at the current node. + * The {@code parent} parameter of the Visitor.visitBlock callback + * is currently always set to null. + * + * @param v {@code non-null;} callback interface + */ + public void forEachBlockDepthFirstDom(SsaBasicBlock.Visitor v) { + BitSet visited = new BitSet(getBlocks().size()); + Stack stack = new Stack(); + + stack.add(getEntryBlock()); + + while (stack.size() > 0) { + SsaBasicBlock cur = stack.pop(); + ArrayList curDomChildren = cur.getDomChildren(); + + if (!visited.get(cur.getIndex())) { + // We walk the tree this way for historical reasons... + for (int i = curDomChildren.size() - 1; i >= 0; i--) { + SsaBasicBlock child = curDomChildren.get(i); + stack.add(child); + } + visited.set(cur.getIndex()); + v.visitBlock(cur, null); + } + } + } + + /** + * Deletes all insns in the set from this method. + * + * @param deletedInsns {@code non-null;} insns to delete + */ + public void deleteInsns(Set deletedInsns) { + for (SsaBasicBlock block : getBlocks()) { + ArrayList insns = block.getInsns(); + + for (int i = insns.size() - 1; i >= 0; i--) { + SsaInsn insn = insns.get(i); + + if (deletedInsns.contains(insn)) { + onInsnRemoved(insn); + insns.remove(i); + } + } + + // Check to see if we need to add a GOTO + + int insnsSz = insns.size(); + SsaInsn lastInsn = (insnsSz == 0) ? null : insns.get(insnsSz - 1); + + if (block != getExitBlock() && (insnsSz == 0 + || lastInsn.getOriginalRopInsn() == null + || lastInsn.getOriginalRopInsn().getOpcode() + .getBranchingness() == Rop.BRANCH_NONE)) { + // We managed to eat a throwable insn + + Insn gotoInsn = new PlainInsn(Rops.GOTO, + SourcePosition.NO_INFO, null, RegisterSpecList.EMPTY); + insns.add(SsaInsn.makeFromRop(gotoInsn, block)); + + // Remove secondary successors from this block + BitSet succs = block.getSuccessors(); + for (int i = succs.nextSetBit(0); i >= 0; + i = succs.nextSetBit(i + 1)) { + if (i != block.getPrimarySuccessorIndex()) { + block.removeSuccessor(i); + } + } + } + } + } + + /** + * Sets "back-convert mode". Set during back-conversion when registers + * are about to be mapped into a non-SSA namespace. When true, + * use and def lists are unavailable. + */ + public void setBackMode() { + backMode = true; + useList = null; + definitionList = null; + } +} diff --git a/dx/src/com/android/jack/dx/ssa/SsaRenamer.java b/dx/src/com/android/jack/dx/ssa/SsaRenamer.java new file mode 100644 index 00000000..be9c51bc --- /dev/null +++ b/dx/src/com/android/jack/dx/ssa/SsaRenamer.java @@ -0,0 +1,662 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.ssa; + +import com.android.jack.dx.rop.code.LocalItem; +import com.android.jack.dx.rop.code.PlainInsn; +import com.android.jack.dx.rop.code.RegisterSpec; +import com.android.jack.dx.rop.code.RegisterSpecList; +import com.android.jack.dx.rop.code.Rops; +import com.android.jack.dx.rop.code.SourcePosition; +import com.android.jack.dx.rop.type.Type; +import com.android.jack.dx.util.IntList; + +import java.util.ArrayList; +import java.util.BitSet; +import java.util.HashMap; +import java.util.HashSet; + +/** + * Complete transformation to SSA form by renaming all registers accessed.

      + * + * See Appel algorithm 19.7

      + * + * Unlike the original algorithm presented in Appel, this renamer converts + * to a new flat (versionless) register space. The "version 0" registers, + * which represent the initial state of the Rop registers and should never + * actually be meaningfully accessed in a legal program, are represented + * as the first N registers in the SSA namespace. Subsequent assignments + * are assigned new unique names. Note that the incoming Rop representation + * has a concept of register widths, where 64-bit values are stored into + * two adjoining Rop registers. This adjoining register representation is + * ignored in SSA form conversion and while in SSA form, each register can be e + * either 32 or 64 bits wide depending on use. The adjoining-register + * represention is re-created later when converting back to Rop form.

      + * + * But, please note, the SSA Renamer's ignoring of the adjoining-register ROP + * representation means that unaligned accesses to 64-bit registers are not + * supported. For example, you cannot do a 32-bit operation on a portion of + * a 64-bit register. This will never be observed to happen when coming + * from Java code, of course.

      + * + * The implementation here, rather than keeping a single register version + * stack for the entire method as the dom tree is walked, instead keeps + * a mapping table for the current block being processed. Once the + * current block has been processed, this mapping table is then copied + * and used as the initial state for child blocks.

      + */ +public class SsaRenamer implements Runnable { + /** debug flag */ + private static final boolean DEBUG = false; + + /** method we're processing */ + private final SsaMethod ssaMeth; + + /** next available SSA register */ + private int nextSsaReg; + + /** the number of original rop registers */ + private final int ropRegCount; + + /** work only on registers above this value */ + private int threshold; + + /** + * indexed by block index; register version state for each block start. + * This list is updated by each dom parent for its children. The only + * sub-arrays that exist at any one time are the start states for blocks + * yet to be processed by a {@code BlockRenamer} instance. + */ + private final RegisterSpec[][] startsForBlocks; + + /** map of SSA register number to debug (local var names) or null of n/a */ + private final ArrayList ssaRegToLocalItems; + + /** + * maps SSA registers back to the original rop number. Used for + * debug only. + */ + private IntList ssaRegToRopReg; + + /** + * Constructs an instance of the renamer + * + * @param ssaMeth {@code non-null;} un-renamed SSA method that will + * be renamed. + */ + public SsaRenamer(SsaMethod ssaMeth) { + ropRegCount = ssaMeth.getRegCount(); + + this.ssaMeth = ssaMeth; + + /* + * Reserve the first N registers in the SSA register space for + * "version 0" registers. + */ + nextSsaReg = ropRegCount; + threshold = 0; + startsForBlocks = new RegisterSpec[ssaMeth.getBlocks().size()][]; + + ssaRegToLocalItems = new ArrayList(); + + if (DEBUG) { + ssaRegToRopReg = new IntList(ropRegCount); + } + + /* + * Appel 19.7 + * + * Initialization: + * for each variable a // register i + * Count[a] <- 0 // nextSsaReg, flattened + * Stack[a] <- 0 // versionStack + * push 0 onto Stack[a] + * + */ + + // top entry for the version stack is version 0 + RegisterSpec[] initialRegMapping = new RegisterSpec[ropRegCount]; + for (int i = 0; i < ropRegCount; i++) { + // everyone starts with a version 0 register + initialRegMapping[i] = RegisterSpec.make(i, Type.VOID); + + if (DEBUG) { + ssaRegToRopReg.add(i); + } + } + + // Initial state for entry block + startsForBlocks[ssaMeth.getEntryBlockIndex()] = initialRegMapping; + } + + /** + * Constructs an instance of the renamer with threshold set + * + * @param ssaMeth {@code non-null;} un-renamed SSA method that will + * be renamed. + * @param thresh registers below this number are unchanged + */ + public SsaRenamer(SsaMethod ssaMeth, int thresh) { + this(ssaMeth); + threshold = thresh; + } + + /** + * Performs renaming transformation, modifying the method's instructions + * in-place. + */ + public void run() { + // Rename each block in dom-tree DFS order. + ssaMeth.forEachBlockDepthFirstDom(new SsaBasicBlock.Visitor() { + public void visitBlock (SsaBasicBlock block, + SsaBasicBlock unused) { + new BlockRenamer(block).process(); + } + }); + + ssaMeth.setNewRegCount(nextSsaReg); + ssaMeth.onInsnsChanged(); + + if (DEBUG) { + System.out.println("SSA\tRop"); + /* + * We're going to compute the version of the rop register + * by keeping a running total of how many times the rop + * register has been mapped. + */ + int[] versions = new int[ropRegCount]; + + int sz = ssaRegToRopReg.size(); + for (int i = 0; i < sz; i++) { + int ropReg = ssaRegToRopReg.get(i); + System.out.println(i + "\t" + ropReg + "[" + + versions[ropReg] + "]"); + versions[ropReg]++; + } + } + } + + /** + * Duplicates a RegisterSpec array. + * + * @param orig {@code non-null;} array to duplicate + * @return {@code non-null;} new instance + */ + private static RegisterSpec[] dupArray(RegisterSpec[] orig) { + RegisterSpec[] copy = new RegisterSpec[orig.length]; + + System.arraycopy(orig, 0, copy, 0, orig.length); + + return copy; + } + + /** + * Gets a local variable item for a specified register. + * + * @param ssaReg register in SSA name space + * @return {@code null-ok;} Local variable name or null if none + */ + private LocalItem getLocalForNewReg(int ssaReg) { + if (ssaReg < ssaRegToLocalItems.size()) { + return ssaRegToLocalItems.get(ssaReg); + } else { + return null; + } + } + + /** + * Records a debug (local variable) name for a specified register. + * + * @param ssaReg non-null named register spec in SSA name space + */ + private void setNameForSsaReg(RegisterSpec ssaReg) { + int reg = ssaReg.getReg(); + LocalItem local = ssaReg.getLocalItem(); + + ssaRegToLocalItems.ensureCapacity(reg + 1); + while (ssaRegToLocalItems.size() <= reg) { + ssaRegToLocalItems.add(null); + } + + ssaRegToLocalItems.set(reg, local); + } + + /** + * Returns true if this SSA register is below the specified threshold. + * Used when most code is already in SSA form, and renaming is needed only + * for registers above a certain threshold. + * + * @param ssaReg the SSA register in question + * @return {@code true} if its register number is below the threshold + */ + private boolean isBelowThresholdRegister(int ssaReg) { + return ssaReg < threshold; + } + + /** + * Returns true if this SSA register is a "version 0" + * register. All version 0 registers are assigned the first N register + * numbers, where N is the count of original rop registers. + * + * @param ssaReg the SSA register in question + * @return true if it is a version 0 register. + */ + private boolean isVersionZeroRegister(int ssaReg) { + return ssaReg < ropRegCount; + } + + /** + * Returns true if a and b are equal or are both null. + * + * @param a null-ok + * @param b null-ok + * @return Returns true if a and b are equal or are both null + */ + private static boolean equalsHandlesNulls(Object a, Object b) { + return a == b || (a != null && a.equals(b)); + } + + /** + * Processes all insns in a block and renames their registers + * as appropriate. + */ + private class BlockRenamer implements SsaInsn.Visitor{ + /** {@code non-null;} block we're processing. */ + private final SsaBasicBlock block; + + /** + * {@code non-null;} indexed by old register name. The current + * top of the version stack as seen by this block. It's + * initialized from the ending state of its dom parent, + * updated as the block's instructions are processed, and then + * copied to each one of its dom children. + */ + private final RegisterSpec[] currentMapping; + + /** + * contains the set of moves we need to keep to preserve local + * var info. All other moves will be deleted. + */ + private final HashSet movesToKeep; + + /** + * maps the set of insns to replace after renaming is finished + * on the block. + */ + private final HashMap insnsToReplace; + + private final RenamingMapper mapper; + + /** + * Constructs a block renamer instance. Call {@code process} + * to process. + * + * @param block {@code non-null;} block to process + */ + BlockRenamer(final SsaBasicBlock block) { + this.block = block; + currentMapping = startsForBlocks[block.getIndex()]; + movesToKeep = new HashSet(); + insnsToReplace = new HashMap(); + mapper = new RenamingMapper(); + + // We don't need our own start state anymore + startsForBlocks[block.getIndex()] = null; + } + + /** + * Provides a register mapping between the old register space + * and the current renaming mapping. The mapping is updated + * as the current block's instructions are processed. + */ + private class RenamingMapper extends RegisterMapper { + public RenamingMapper() { + // This space intentionally left blank. + } + + /** {@inheritDoc} */ + @Override + public int getNewRegisterCount() { + return nextSsaReg; + } + + /** {@inheritDoc} */ + @Override + public RegisterSpec map(RegisterSpec registerSpec) { + if (registerSpec == null) return null; + + int reg = registerSpec.getReg(); + + // For debugging: assert that the mapped types are compatible. + if (DEBUG) { + RegisterSpec newVersion = currentMapping[reg]; + if (newVersion.getBasicType() != Type.BT_VOID + && registerSpec.getBasicFrameType() + != newVersion.getBasicFrameType()) { + + throw new RuntimeException( + "mapping registers of incompatible types! " + + registerSpec + + " " + currentMapping[reg]); + } + } + + return registerSpec.withReg(currentMapping[reg].getReg()); + } + } + + /** + * Renames all the variables in this block and inserts appriopriate + * phis in successor blocks. + */ + public void process() { + /* + * From Appel: + * + * Rename(n) = + * for each statement S in block n // 'statement' in 'block' + */ + + block.forEachInsn(this); + + updateSuccessorPhis(); + + // Delete all move insns in this block. + ArrayList insns = block.getInsns(); + int szInsns = insns.size(); + + for (int i = szInsns - 1; i >= 0 ; i--) { + SsaInsn insn = insns.get(i); + SsaInsn replaceInsn; + + replaceInsn = insnsToReplace.get(insn); + + if (replaceInsn != null) { + insns.set(i, replaceInsn); + } else if (insn.isNormalMoveInsn() + && !movesToKeep.contains(insn)) { + insns.remove(i); + } + } + + // Store the start states for our dom children. + boolean first = true; + for (SsaBasicBlock child : block.getDomChildren()) { + if (child != block) { + // Don't bother duplicating the array for the first child. + RegisterSpec[] childStart = first ? currentMapping + : dupArray(currentMapping); + + startsForBlocks[child.getIndex()] = childStart; + first = false; + } + } + + // currentMapping is owned by a child now. + } + + /** + * Enforces a few contraints when a register mapping is added. + * + *

        + *
      1. Ensures that all new SSA registers specs in the mapping + * table with the same register number are identical. In effect, once + * an SSA register spec has received or lost a local variable name, + * then every old-namespace register that maps to it should gain or + * lose its local variable name as well. + *
      2. Records the local name associated with the + * register so that a register is never associated with more than one + * local. + *
      3. ensures that only one SSA register + * at a time is considered to be associated with a local variable. When + * {@code currentMapping} is updated and the newly added element + * is named, strip that name from any other SSA registers. + *
      + * + * @param ropReg {@code >= 0;} rop register number + * @param ssaReg {@code non-null;} an SSA register that has just + * been added to {@code currentMapping} + */ + private void addMapping(int ropReg, RegisterSpec ssaReg) { + int ssaRegNum = ssaReg.getReg(); + LocalItem ssaRegLocal = ssaReg.getLocalItem(); + + currentMapping[ropReg] = ssaReg; + + /* + * Ensure all SSA register specs with the same reg are identical. + */ + for (int i = currentMapping.length - 1; i >= 0; i--) { + RegisterSpec cur = currentMapping[i]; + + if (ssaRegNum == cur.getReg()) { + currentMapping[i] = ssaReg; + } + } + + // All further steps are for registers with local information. + if (ssaRegLocal == null) { + return; + } + + // Record that this SSA reg has been associated with a local. + setNameForSsaReg(ssaReg); + + // Ensure that no other SSA regs are associated with this local. + for (int i = currentMapping.length - 1; i >= 0; i--) { + RegisterSpec cur = currentMapping[i]; + + if (ssaRegNum != cur.getReg() + && ssaRegLocal.equals(cur.getLocalItem())) { + currentMapping[i] = cur.withLocalItem(null); + } + } + } + + /** + * {@inheritDoc} + * + * Phi insns have their result registers renamed. + */ + public void visitPhiInsn(PhiInsn phi) { + /* don't process sources for phi's */ + processResultReg(phi); + } + + /** + * {@inheritDoc} + * + * Move insns are treated as a simple mapping operation, and + * will later be removed unless they represent a local variable + * assignment. If they represent a local variable assignement, they + * are preserved. + */ + public void visitMoveInsn(NormalSsaInsn insn) { + /* + * For moves: copy propogate the move if we can, but don't + * if we need to preserve local variable info and the + * result has a different name than the source. + */ + + RegisterSpec ropResult = insn.getResult(); + int ropResultReg = ropResult.getReg(); + int ropSourceReg = insn.getSources().get(0).getReg(); + + insn.mapSourceRegisters(mapper); + int ssaSourceReg = insn.getSources().get(0).getReg(); + + LocalItem sourceLocal + = currentMapping[ropSourceReg].getLocalItem(); + LocalItem resultLocal = ropResult.getLocalItem(); + + /* + * A move from a register that's currently associated with a local + * to one that will not be associated with a local does not need + * to be preserved, but the local association should remain. + * Hence, we inherit the sourceLocal where the resultLocal is null. + */ + + LocalItem newLocal + = (resultLocal == null) ? sourceLocal : resultLocal; + LocalItem associatedLocal = getLocalForNewReg(ssaSourceReg); + + /* + * If we take the new local, will only one local have ever + * been associated with this SSA reg? + */ + boolean onlyOneAssociatedLocal + = associatedLocal == null || newLocal == null + || newLocal.equals(associatedLocal); + + /* + * If we're going to copy-propogate, then the ssa register + * spec that's going to go into the mapping is made up of + * the source register number mapped from above, the type + * of the result, and the name either from the result (if + * specified) or inherited from the existing mapping. + * + * The move source has incomplete type information in null + * object cases, so the result type is used. + */ + RegisterSpec ssaReg + = RegisterSpec.makeLocalOptional( + ssaSourceReg, ropResult.getType(), newLocal); + + if (!Optimizer.getPreserveLocals() || (onlyOneAssociatedLocal + && equalsHandlesNulls(newLocal, sourceLocal)) && + threshold == 0) { + /* + * We don't have to keep this move to preserve local + * information. Either the name is the same, or the result + * register spec is unnamed. + */ + + addMapping(ropResultReg, ssaReg); + } else if (onlyOneAssociatedLocal && sourceLocal == null && + threshold == 0) { + /* + * The register was previously unnamed. This means that a + * local starts after it's first assignment in SSA form + */ + + RegisterSpecList ssaSources = RegisterSpecList.make( + RegisterSpec.make(ssaReg.getReg(), + ssaReg.getType(), newLocal)); + + SsaInsn newInsn + = SsaInsn.makeFromRop( + new PlainInsn(Rops.opMarkLocal(ssaReg), + SourcePosition.NO_INFO, null, ssaSources),block); + + insnsToReplace.put(insn, newInsn); + + // Just map as above. + addMapping(ropResultReg, ssaReg); + } else { + /* + * Do not copy-propogate, since the two registers have + * two different local-variable names. + */ + processResultReg(insn); + + movesToKeep.add(insn); + } + } + + /** + * {@inheritDoc} + * + * All insns that are not move or phi insns have their source registers + * mapped ot the current mapping. Their result registers are then + * renamed to a new SSA register which is then added to the current + * register mapping. + */ + public void visitNonMoveInsn(NormalSsaInsn insn) { + /* for each use of some variable X in S */ + insn.mapSourceRegisters(mapper); + + processResultReg(insn); + } + + /** + * Renames the result register of this insn and updates the + * current register mapping. Does nothing if this insn has no result. + * Applied to all non-move insns. + * + * @param insn insn to process. + */ + void processResultReg(SsaInsn insn) { + RegisterSpec ropResult = insn.getResult(); + + if (ropResult == null) { + return; + } + + int ropReg = ropResult.getReg(); + if (isBelowThresholdRegister(ropReg)) { + return; + } + + insn.changeResultReg(nextSsaReg); + addMapping(ropReg, insn.getResult()); + + if (DEBUG) { + ssaRegToRopReg.add(ropReg); + } + + nextSsaReg++; + } + + /** + * Updates the phi insns in successor blocks with operands based + * on the current mapping of the rop register the phis represent. + */ + private void updateSuccessorPhis() { + PhiInsn.Visitor visitor = new PhiInsn.Visitor() { + public void visitPhiInsn (PhiInsn insn) { + int ropReg; + + ropReg = insn.getRopResultReg(); + if (isBelowThresholdRegister(ropReg)) { + return; + } + + /* + * Never add a version 0 register as a phi + * operand. Version 0 registers represent the + * initial register state, and thus are never + * significant. Furthermore, the register liveness + * algorithm doesn't properly count them as "live + * in" at the beginning of the method. + */ + + RegisterSpec stackTop = currentMapping[ropReg]; + if (!isVersionZeroRegister(stackTop.getReg())) { + insn.addPhiOperand(stackTop, block); + } + } + }; + + BitSet successors = block.getSuccessors(); + for (int i = successors.nextSetBit(0); i >= 0; + i = successors.nextSetBit(i + 1)) { + SsaBasicBlock successor = ssaMeth.getBlocks().get(i); + successor.forEachPhiInsn(visitor); + } + } + } +} diff --git a/dx/src/com/android/jack/dx/ssa/back/FirstFitAllocator.java b/dx/src/com/android/jack/dx/ssa/back/FirstFitAllocator.java new file mode 100644 index 00000000..4fed8f2d --- /dev/null +++ b/dx/src/com/android/jack/dx/ssa/back/FirstFitAllocator.java @@ -0,0 +1,151 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.ssa.back; + +import com.android.jack.dx.rop.code.CstInsn; +import com.android.jack.dx.rop.cst.CstInteger; +import com.android.jack.dx.ssa.BasicRegisterMapper; +import com.android.jack.dx.ssa.NormalSsaInsn; +import com.android.jack.dx.ssa.RegisterMapper; +import com.android.jack.dx.ssa.SsaMethod; +import com.android.jack.dx.util.BitIntSet; +import com.android.jack.dx.util.IntSet; + +import java.util.BitSet; +import java.util.ArrayList; + +/** + * Allocates registers via a naive n^2 register allocator. + * This allocator does not try to co-locate local variables or deal + * intelligently with different size register uses. + */ +public class FirstFitAllocator extends RegisterAllocator { + /** + * If true, allocator places parameters at the top of the frame + * in calling-convention order. + */ + private static final boolean PRESLOT_PARAMS = true; + + /** indexed by old reg; the set of old regs we've mapped */ + private final BitSet mapped; + + /** {@inheritDoc} */ + public FirstFitAllocator( + final SsaMethod ssaMeth, final InterferenceGraph interference) { + super(ssaMeth, interference); + + mapped = new BitSet(ssaMeth.getRegCount()); + } + + /** {@inheritDoc} */ + @Override + public boolean wantsParamsMovedHigh() { + return PRESLOT_PARAMS; + } + + /** {@inheritDoc} */ + @Override + public RegisterMapper allocateRegisters() { + int oldRegCount = ssaMeth.getRegCount(); + + BasicRegisterMapper mapper + = new BasicRegisterMapper(oldRegCount); + + int nextNewRegister = 0; + + if (PRESLOT_PARAMS) { + /* + * Reserve space for the params at the bottom of the register + * space. Later, we'll flip the params to the end of the register + * space. + */ + + nextNewRegister = ssaMeth.getParamWidth(); + } + + for (int i = 0; i < oldRegCount; i++) { + if (mapped.get(i)) { + // we already got this one + continue; + } + + int maxCategory = getCategoryForSsaReg(i); + IntSet current = new BitIntSet(oldRegCount); + + interference.mergeInterferenceSet(i, current); + + boolean isPreslotted = false; + int newReg = 0; + + if (PRESLOT_PARAMS && isDefinitionMoveParam(i)) { + // Any move-param definition must be a NormalSsaInsn + NormalSsaInsn defInsn = (NormalSsaInsn) + ssaMeth.getDefinitionForRegister(i); + + newReg = paramNumberFromMoveParam(defInsn); + + mapper.addMapping(i, newReg, maxCategory); + isPreslotted = true; + } else { + mapper.addMapping(i, nextNewRegister, maxCategory); + newReg = nextNewRegister; + } + + for (int j = i + 1; j < oldRegCount; j++) { + if (mapped.get(j) || isDefinitionMoveParam(j)) { + continue; + } + + /* + * If reg j doesn't interfere with the current mapping. + * Also, if this is a pre-slotted method parameter, we + * can't use more than the original param width. + */ + if (!current.has(j) + && !(isPreslotted + && (maxCategory < getCategoryForSsaReg(j)))) { + + interference.mergeInterferenceSet(j, current); + + maxCategory = Math.max(maxCategory, + getCategoryForSsaReg(j)); + + mapper.addMapping(j, newReg, maxCategory); + mapped.set(j); + } + } + + mapped.set(i); + if (!isPreslotted) { + nextNewRegister += maxCategory; + } + } + + return mapper; + } + + /** + * Returns the parameter number that this move-param insn refers to + * @param ndefInsn a move-param insn (otherwise, exceptions will be thrown) + * @return parameter number (offset in the total parameter width) + */ + private int paramNumberFromMoveParam(NormalSsaInsn ndefInsn) { + CstInsn origInsn = (CstInsn) ndefInsn.getOriginalRopInsn(); + + return ((CstInteger) origInsn.getConstant()).getValue(); + } +} diff --git a/dx/src/com/android/jack/dx/ssa/back/FirstFitLocalCombiningAllocator.java b/dx/src/com/android/jack/dx/ssa/back/FirstFitLocalCombiningAllocator.java new file mode 100644 index 00000000..65a5d1d1 --- /dev/null +++ b/dx/src/com/android/jack/dx/ssa/back/FirstFitLocalCombiningAllocator.java @@ -0,0 +1,1139 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.ssa.back; + +import com.android.jack.dx.rop.code.*; +import com.android.jack.dx.rop.cst.CstInteger; +import com.android.jack.dx.ssa.InterferenceRegisterMapper; +import com.android.jack.dx.ssa.NormalSsaInsn; +import com.android.jack.dx.ssa.Optimizer; +import com.android.jack.dx.ssa.PhiInsn; +import com.android.jack.dx.ssa.RegisterMapper; +import com.android.jack.dx.ssa.SsaBasicBlock; +import com.android.jack.dx.ssa.SsaInsn; +import com.android.jack.dx.ssa.SsaMethod; +import com.android.jack.dx.util.IntIterator; +import com.android.jack.dx.util.IntSet; + +import java.util.ArrayList; +import java.util.BitSet; +import java.util.Map; +import java.util.TreeMap; + +/** + * Allocates registers in a first-fit fashion, with the bottom reserved for + * method parameters and all SSAregisters representing the same local variable + * kept together if possible. + */ +public class FirstFitLocalCombiningAllocator extends RegisterAllocator { + /** local debug flag */ + private static final boolean DEBUG = false; + + /** maps local variable to a list of associated SSA registers */ + private final Map> localVariables; + + /** list of move-result-pesudo instructions seen in this method */ + private final ArrayList moveResultPseudoInsns; + + /** list of invoke-range instructions seen in this method */ + private final ArrayList invokeRangeInsns; + + /** list of phi instructions seen in this method */ + private final ArrayList phiInsns; + + /** indexed by SSA reg; the set of SSA regs we've mapped */ + private final BitSet ssaRegsMapped; + + /** Register mapper which will be our result */ + private final InterferenceRegisterMapper mapper; + + /** end of rop registers range (starting at 0) reserved for parameters */ + private final int paramRangeEnd; + + /** set of rop registers reserved for parameters or local variables */ + private final BitSet reservedRopRegs; + + /** set of rop registers that have been used by anything */ + private final BitSet usedRopRegs; + + /** true if converter should take steps to minimize rop-form registers */ + private final boolean minimizeRegisters; + + /** + * Constructs instance. + * + * @param ssaMeth {@code non-null;} method to process + * @param interference non-null interference graph for SSA registers + * @param minimizeRegisters true if converter should take steps to + * minimize rop-form registers + */ + public FirstFitLocalCombiningAllocator( + SsaMethod ssaMeth, InterferenceGraph interference, + boolean minimizeRegisters) { + super(ssaMeth, interference); + + ssaRegsMapped = new BitSet(ssaMeth.getRegCount()); + + mapper = new InterferenceRegisterMapper( + interference, ssaMeth.getRegCount()); + + this.minimizeRegisters = minimizeRegisters; + + /* + * Reserve space for the params at the bottom of the register + * space. Later, we'll flip the params to the end of the register + * space. + */ + + paramRangeEnd = ssaMeth.getParamWidth(); + + reservedRopRegs = new BitSet(paramRangeEnd * 2); + reservedRopRegs.set(0, paramRangeEnd); + usedRopRegs = new BitSet(paramRangeEnd * 2); + localVariables = new TreeMap>(); + moveResultPseudoInsns = new ArrayList(); + invokeRangeInsns = new ArrayList(); + phiInsns = new ArrayList(); + } + + /** {@inheritDoc} */ + @Override + public boolean wantsParamsMovedHigh() { + return true; + } + + /** {@inheritDoc} */ + @Override + public RegisterMapper allocateRegisters() { + + analyzeInstructions(); + + if (DEBUG) { + printLocalVars(); + } + + if (DEBUG) System.out.println("--->Mapping local-associated params"); + handleLocalAssociatedParams(); + + if (DEBUG) System.out.println("--->Mapping other params"); + handleUnassociatedParameters(); + + if (DEBUG) System.out.println("--->Mapping invoke-range"); + handleInvokeRangeInsns(); + + if (DEBUG) { + System.out.println("--->Mapping local-associated non-params"); + } + handleLocalAssociatedOther(); + + if (DEBUG) System.out.println("--->Mapping check-cast results"); + handleCheckCastResults(); + + if (DEBUG) System.out.println("--->Mapping phis"); + handlePhiInsns(); + + if (DEBUG) System.out.println("--->Mapping others"); + handleNormalUnassociated(); + + return mapper; + } + + /** + * Dumps local variable table to stdout for debugging. + */ + private void printLocalVars() { + System.out.println("Printing local vars"); + for (Map.Entry> e : + localVariables.entrySet()) { + StringBuilder regs = new StringBuilder(); + + regs.append('{'); + regs.append(' '); + for (RegisterSpec reg : e.getValue()) { + regs.append('v'); + regs.append(reg.getReg()); + regs.append(' '); + } + regs.append('}'); + System.out.printf("Local: %s Registers: %s\n", e.getKey(), regs); + } + } + + /** + * Maps all local-associated parameters to rop registers. + */ + private void handleLocalAssociatedParams() { + for (ArrayList ssaRegs : localVariables.values()) { + int sz = ssaRegs.size(); + int paramIndex = -1; + int paramCategory = 0; + + // First, find out if this local variable is a parameter. + for (int i = 0; i < sz; i++) { + RegisterSpec ssaSpec = ssaRegs.get(i); + int ssaReg = ssaSpec.getReg(); + + paramIndex = getParameterIndexForReg(ssaReg); + + if (paramIndex >= 0) { + paramCategory = ssaSpec.getCategory(); + addMapping(ssaSpec, paramIndex); + break; + } + } + + if (paramIndex < 0) { + // This local wasn't a parameter. + continue; + } + + // Any remaining local-associated registers will be mapped later. + tryMapRegs(ssaRegs, paramIndex, paramCategory, true); + } + } + + /** + * Gets the parameter index for SSA registers that are method parameters. + * {@code -1} is returned for non-parameter registers. + * + * @param ssaReg {@code >=0;} SSA register to look up + * @return parameter index or {@code -1} if not a parameter + */ + private int getParameterIndexForReg(int ssaReg) { + SsaInsn defInsn = ssaMeth.getDefinitionForRegister(ssaReg); + if (defInsn == null) { + return -1; + } + + Rop opcode = defInsn.getOpcode(); + + // opcode == null for phi insns. + if (opcode != null && opcode.getOpcode() == RegOps.MOVE_PARAM) { + CstInsn origInsn = (CstInsn) defInsn.getOriginalRopInsn(); + return ((CstInteger) origInsn.getConstant()).getValue(); + } + + return -1; + } + + /** + * Maps all local-associated registers that are not parameters. + * Tries to find an unreserved range that's wide enough for all of + * the SSA registers, and then tries to map them all to that + * range. If not all fit, a new range is tried until all registers + * have been fit. + */ + private void handleLocalAssociatedOther() { + for (ArrayList specs : localVariables.values()) { + int ropReg = paramRangeEnd; + + boolean done = false; + do { + int maxCategory = 1; + + // Compute max category for remaining unmapped registers. + int sz = specs.size(); + for (int i = 0; i < sz; i++) { + RegisterSpec ssaSpec = specs.get(i); + int category = ssaSpec.getCategory(); + if (!ssaRegsMapped.get(ssaSpec.getReg()) + && category > maxCategory) { + maxCategory = category; + } + } + + ropReg = findRopRegForLocal(ropReg, maxCategory); + if (canMapRegs(specs, ropReg)) { + done = tryMapRegs(specs, ropReg, maxCategory, true); + } + + // Increment for next call to findRopRegForLocal. + ropReg++; + } while (!done); + } + } + + /** + * Tries to map a list of SSA registers into the a rop reg, marking + * used rop space as reserved. SSA registers that don't fit are left + * unmapped. + * + * @param specs {@code non-null;} SSA registers to attempt to map + * @param ropReg {@code >=0;} rop register to map to + * @param maxAllowedCategory {@code 1..2;} maximum category + * allowed in mapping. + * @param markReserved do so if {@code true} + * @return {@code true} if all registers were mapped, {@code false} + * if some remain unmapped + */ + private boolean tryMapRegs( + ArrayList specs, int ropReg, + int maxAllowedCategory, boolean markReserved) { + boolean remaining = false; + for (RegisterSpec spec : specs) { + if (ssaRegsMapped.get(spec.getReg())) { + continue; + } + + boolean succeeded; + succeeded = tryMapReg(spec, ropReg, maxAllowedCategory); + remaining = !succeeded || remaining; + if (succeeded && markReserved) { + // This only needs to be called once really with + // the widest category used, but + markReserved(ropReg, spec.getCategory()); + } + } + return !remaining; + } + + /** + * Tries to map an SSA register to a rop register. + * + * @param ssaSpec {@code non-null;} SSA register + * @param ropReg {@code >=0;} rop register + * @param maxAllowedCategory {@code 1..2;} the maximum category + * that the SSA register is allowed to be + * @return {@code true} if map succeeded, {@code false} if not + */ + private boolean tryMapReg(RegisterSpec ssaSpec, int ropReg, + int maxAllowedCategory) { + if (ssaSpec.getCategory() <= maxAllowedCategory + && !ssaRegsMapped.get(ssaSpec.getReg()) + && canMapReg(ssaSpec, ropReg)) { + addMapping(ssaSpec, ropReg); + return true; + } + + return false; + } + + /** + * Marks a range of rop registers as "reserved for a local variable." + * + * @param ropReg {@code >= 0;} rop register to reserve + * @param category {@code > 0;} width to reserve + */ + private void markReserved(int ropReg, int category) { + reservedRopRegs.set(ropReg, ropReg + category, true); + } + + /** + * Checks to see if any rop registers in the specified range are reserved + * for local variables or parameters. + * + * @param ropRangeStart {@code >= 0;} lowest rop register + * @param width {@code > 0;} number of rop registers in range. + * @return {@code true} if any register in range is marked reserved + */ + private boolean rangeContainsReserved(int ropRangeStart, int width) { + for (int i = ropRangeStart; i < (ropRangeStart + width); i++) { + if (reservedRopRegs.get(i)) { + return true; + } + } + return false; + } + + /** + * Returns true if given rop register represents the {@code this} pointer + * for a non-static method. + * + * @param startReg rop register + * @return true if the "this" pointer is located here. + */ + private boolean isThisPointerReg(int startReg) { + // "this" is always the first parameter. + return startReg == 0 && !ssaMeth.isStatic(); + } + + /** + * Finds a range of unreserved rop registers. + * + * @param startReg {@code >= 0;} a rop register to start the search at + * @param width {@code > 0;} the width, in registers, required. + * @return {@code >= 0;} start of available register range. + */ + private int findNextUnreservedRopReg(int startReg, int width) { + int reg; + + reg = reservedRopRegs.nextClearBit(startReg); + + while (true) { + int i = 1; + + while (i < width && !reservedRopRegs.get(reg + i)) { + i++; + } + + if (i == width) { + return reg; + } + + reg = reservedRopRegs.nextClearBit(reg + i); + } + } + + /** + * Finds a range of rop regs that can be used for local variables. + * If {@code MIX_LOCALS_AND_OTHER} is {@code false}, this means any + * rop register that has not yet been used. + * + * @param startReg {@code >= 0;} a rop register to start the search at + * @param width {@code > 0;} the width, in registers, required. + * @return {@code >= 0;} start of available register range. + */ + private int findRopRegForLocal(int startReg, int width) { + int reg; + + reg = usedRopRegs.nextClearBit(startReg); + + while (true) { + int i = 1; + + while (i < width && !usedRopRegs.get(reg + i)) { + i++; + } + + if (i == width) { + return reg; + } + + reg = usedRopRegs.nextClearBit(reg + i); + } + } + + /** + * Maps any parameter that isn't local-associated, which can happen + * in the case where there is no java debug info. + */ + private void handleUnassociatedParameters() { + int szSsaRegs = ssaMeth.getRegCount(); + + for (int ssaReg = 0; ssaReg < szSsaRegs; ssaReg++) { + if (ssaRegsMapped.get(ssaReg)) { + // We already did this one above + continue; + } + + int paramIndex = getParameterIndexForReg(ssaReg); + + RegisterSpec ssaSpec = getDefinitionSpecForSsaReg(ssaReg); + if (paramIndex >= 0) { + addMapping(ssaSpec, paramIndex); + } + } + } + + /** + * Handles all insns that want a register range for their sources. + */ + private void handleInvokeRangeInsns() { + for (NormalSsaInsn insn : invokeRangeInsns) { + adjustAndMapSourceRangeRange(insn); + } + } + + /** + * Handles check cast results to reuse the same source register. + * Inserts a move if it can't map the same register to both and the + * check cast is not caught. + */ + private void handleCheckCastResults() { + for (NormalSsaInsn insn : moveResultPseudoInsns) { + RegisterSpec moveRegSpec = insn.getResult(); + int moveReg = moveRegSpec.getReg(); + BitSet predBlocks = insn.getBlock().getPredecessors(); + + // Expect one predecessor block only + if (predBlocks.cardinality() != 1) { + continue; + } + + SsaBasicBlock predBlock = + ssaMeth.getBlocks().get(predBlocks.nextSetBit(0)); + ArrayList insnList = predBlock.getInsns(); + + /** + * If the predecessor block has a check-cast, it will be the last + * instruction + */ + SsaInsn checkCastInsn = insnList.get(insnList.size() - 1); + if (checkCastInsn.getOpcode().getOpcode() != RegOps.CHECK_CAST) { + continue; + } + + RegisterSpec checkRegSpec = checkCastInsn.getSources().get(0); + int checkReg = checkRegSpec.getReg(); + + /** + * See if either register is already mapped. Most likely the move + * result will be mapped already since the cast result is stored + * in a local variable. + */ + int category = checkRegSpec.getCategory(); + boolean moveMapped = ssaRegsMapped.get(moveReg); + boolean checkMapped = ssaRegsMapped.get(checkReg); + if (moveMapped & !checkMapped) { + int moveRopReg = mapper.oldToNew(moveReg); + checkMapped = tryMapReg(checkRegSpec, moveRopReg, category); + } + if (checkMapped & !moveMapped) { + int checkRopReg = mapper.oldToNew(checkReg); + moveMapped = tryMapReg(moveRegSpec, checkRopReg, category); + } + + // Map any unmapped registers to anything available + if (!moveMapped || !checkMapped) { + int ropReg = findNextUnreservedRopReg(paramRangeEnd, category); + ArrayList ssaRegs = + new ArrayList(2); + ssaRegs.add(moveRegSpec); + ssaRegs.add(checkRegSpec); + + while (!tryMapRegs(ssaRegs, ropReg, category, false)) { + ropReg = findNextUnreservedRopReg(ropReg + 1, category); + } + } + + /* + * If source and result have a different mapping, insert a move so + * they can have the same mapping. Don't do this if the check cast + * is caught, since it will overwrite a potentially live value. + */ + boolean hasExceptionHandlers = + checkCastInsn.getOriginalRopInsn().getCatches().size() != 0; + int moveRopReg = mapper.oldToNew(moveReg); + int checkRopReg = mapper.oldToNew(checkReg); + if (moveRopReg != checkRopReg && !hasExceptionHandlers) { + ((NormalSsaInsn) checkCastInsn).changeOneSource(0, + insertMoveBefore(checkCastInsn, checkRegSpec)); + addMapping(checkCastInsn.getSources().get(0), moveRopReg); + } + } + } + + /** + * Handles all phi instructions, trying to map them to a common register. + */ + private void handlePhiInsns() { + for (PhiInsn insn : phiInsns) { + processPhiInsn(insn); + } + } + + /** + * Maps all non-parameter, non-local variable registers. + */ + private void handleNormalUnassociated() { + int szSsaRegs = ssaMeth.getRegCount(); + + for (int ssaReg = 0; ssaReg < szSsaRegs; ssaReg++) { + if (ssaRegsMapped.get(ssaReg)) { + // We already did this one + continue; + } + + RegisterSpec ssaSpec = getDefinitionSpecForSsaReg(ssaReg); + + if (ssaSpec == null) continue; + + int category = ssaSpec.getCategory(); + // Find a rop reg that does not interfere + int ropReg = findNextUnreservedRopReg(paramRangeEnd, category); + while (!canMapReg(ssaSpec, ropReg)) { + ropReg = findNextUnreservedRopReg(ropReg + 1, category); + } + + addMapping(ssaSpec, ropReg); + } + } + + /** + * Checks to see if a list of SSA registers can all be mapped into + * the same rop reg. Ignores registers that have already been mapped, + * and checks the interference graph and ensures the range does not + * cross the parameter range. + * + * @param specs {@code non-null;} SSA registers to check + * @param ropReg {@code >=0;} rop register to check mapping to + * @return {@code true} if all unmapped registers can be mapped + */ + private boolean canMapRegs(ArrayList specs, int ropReg) { + for (RegisterSpec spec : specs) { + if (ssaRegsMapped.get(spec.getReg())) continue; + if (!canMapReg(spec, ropReg)) return false; + } + return true; + } + + /** + * Checks to see if {@code ssaSpec} can be mapped to + * {@code ropReg}. Checks interference graph and ensures + * the range does not cross the parameter range. + * + * @param ssaSpec {@code non-null;} SSA spec + * @param ropReg prosepctive new-namespace reg + * @return {@code true} if mapping is possible + */ + private boolean canMapReg(RegisterSpec ssaSpec, int ropReg) { + int category = ssaSpec.getCategory(); + return !(spansParamRange(ropReg, category) + || mapper.interferes(ssaSpec, ropReg)); + } + + /** + * Returns true if the specified rop register + category + * will cross the boundry between the lower {@code paramWidth} + * registers reserved for method params and the upper registers. We cannot + * allocate a register that spans the param block and the normal block, + * because we will be moving the param block to high registers later. + * + * @param ssaReg register in new namespace + * @param category width that the register will have + * @return {@code true} in the case noted above + */ + private boolean spansParamRange(int ssaReg, int category) { + return ((ssaReg < paramRangeEnd) + && ((ssaReg + category) > paramRangeEnd)); + } + + /** + * Analyze each instruction and find out all the local variable assignments + * and move-result-pseudo/invoke-range instrucitons. + */ + private void analyzeInstructions() { + ssaMeth.forEachInsn(new SsaInsn.Visitor() { + /** {@inheritDoc} */ + public void visitMoveInsn(NormalSsaInsn insn) { + processInsn(insn); + } + + /** {@inheritDoc} */ + public void visitPhiInsn(PhiInsn insn) { + processInsn(insn); + } + + /** {@inheritDoc} */ + public void visitNonMoveInsn(NormalSsaInsn insn) { + processInsn(insn); + } + + /** + * This method collects three types of instructions: + * + * 1) Adds a local variable assignment to the + * {@code localVariables} map. + * 2) Add move-result-pseudo to the + * {@code moveResultPseudoInsns} list. + * 3) Add invoke-range to the + * {@code invokeRangeInsns} list. + * + * @param insn {@code non-null;} insn that may represent a + * local variable assignment + */ + private void processInsn(SsaInsn insn) { + RegisterSpec assignment; + assignment = insn.getLocalAssignment(); + + if (assignment != null) { + LocalItem local = assignment.getLocalItem(); + + ArrayList regList + = localVariables.get(local); + + if (regList == null) { + regList = new ArrayList(); + localVariables.put(local, regList); + } + + regList.add(assignment); + } + + if (insn instanceof NormalSsaInsn) { + if (insn.getOpcode().getOpcode() == + RegOps.MOVE_RESULT_PSEUDO) { + moveResultPseudoInsns.add((NormalSsaInsn) insn); + } else if (Optimizer.getAdvice().requiresSourcesInOrder( + insn.getOriginalRopInsn().getOpcode(), + insn.getSources())) { + invokeRangeInsns.add((NormalSsaInsn) insn); + } + } else if (insn instanceof PhiInsn) { + phiInsns.add((PhiInsn) insn); + } + + } + }); + } + + /** + * Adds a mapping from an SSA register to a rop register. + * {@link #canMapReg} should have already been called. + * + * @param ssaSpec {@code non-null;} SSA register to map from + * @param ropReg {@code >=0;} rop register to map to + */ + private void addMapping(RegisterSpec ssaSpec, int ropReg) { + int ssaReg = ssaSpec.getReg(); + + // An assertion. + if (ssaRegsMapped.get(ssaReg) || !canMapReg(ssaSpec, ropReg)) { + throw new RuntimeException( + "attempt to add invalid register mapping"); + } + + if (DEBUG) { + System.out.printf("Add mapping s%d -> v%d c:%d\n", + ssaSpec.getReg(), ropReg, ssaSpec.getCategory()); + } + + int category = ssaSpec.getCategory(); + mapper.addMapping(ssaSpec.getReg(), ropReg, category); + ssaRegsMapped.set(ssaReg); + usedRopRegs.set(ropReg, ropReg + category); + } + + + /** + * Maps the source registers of the specified instruction such that they + * will fall in a contiguous range in rop form. Moves are inserted as + * necessary to allow the range to be allocated. + * + * @param insn {@code non-null;} insn whos sources to process + */ + private void adjustAndMapSourceRangeRange(NormalSsaInsn insn) { + int newRegStart = findRangeAndAdjust(insn); + + RegisterSpecList sources = insn.getSources(); + int szSources = sources.size(); + int nextRopReg = newRegStart; + + for (int i = 0; i < szSources; i++) { + RegisterSpec source = sources.get(i); + int sourceReg = source.getReg(); + int category = source.getCategory(); + int curRopReg = nextRopReg; + nextRopReg += category; + + if (ssaRegsMapped.get(sourceReg)) { + continue; + } + + LocalItem localItem = getLocalItemForReg(sourceReg); + addMapping(source, curRopReg); + + if (localItem != null) { + markReserved(curRopReg, category); + ArrayList similarRegisters + = localVariables.get(localItem); + + int szSimilar = similarRegisters.size(); + + /* + * Try to map all SSA registers also associated with + * this local. + */ + for (int j = 0; j < szSimilar; j++) { + RegisterSpec similarSpec = similarRegisters.get(j); + int similarReg = similarSpec.getReg(); + + // Don't map anything that's also a source. + if (-1 != sources.indexOfRegister(similarReg)) { + continue; + } + + // Registers left unmapped will get handled later. + tryMapReg(similarSpec, curRopReg, category); + } + } + } + } + + /** + * Find a contiguous rop register range that fits the specified + * instruction's sources. First, try to center the range around + * sources that have already been mapped to rop registers. If that fails, + * just find a new contiguous range that doesn't interfere. + * + * @param insn {@code non-null;} the insn whose sources need to + * fit. Must be last insn in basic block. + * @return {@code >= 0;} rop register of start of range + */ + private int findRangeAndAdjust(NormalSsaInsn insn) { + RegisterSpecList sources = insn.getSources(); + int szSources = sources.size(); + // the category for each source index + int categoriesForIndex[] = new int[szSources]; + int rangeLength = 0; + + // Compute rangeLength and categoriesForIndex + for (int i = 0; i < szSources; i++) { + int category = sources.get(i).getCategory(); + categoriesForIndex[i] = category; + rangeLength += categoriesForIndex[i]; + } + + // the highest score of fits tried so far + int maxScore = Integer.MIN_VALUE; + // the high scoring range's start + int resultRangeStart = -1; + // by source index: set of sources needing moves in high scoring plan + BitSet resultMovesRequired = null; + + /* + * First, go through each source that's already been mapped. Try + * to center the range around the rop register this source is mapped + * to. + */ + int rangeStartOffset = 0; + for (int i = 0; i < szSources; i++) { + int ssaCenterReg = sources.get(i).getReg(); + + if (i != 0) { + rangeStartOffset -= categoriesForIndex[i - 1]; + } + if (!ssaRegsMapped.get(ssaCenterReg)) { + continue; + } + + int rangeStart = mapper.oldToNew(ssaCenterReg) + rangeStartOffset; + + if (rangeStart < 0 || spansParamRange(rangeStart, rangeLength)) { + continue; + } + + BitSet curMovesRequired = new BitSet(szSources); + + int fitWidth + = fitPlanForRange(rangeStart, insn, categoriesForIndex, + curMovesRequired); + + if (fitWidth < 0) { + continue; + } + + int score = fitWidth - curMovesRequired.cardinality(); + + if (score > maxScore) { + maxScore = score; + resultRangeStart = rangeStart; + resultMovesRequired = curMovesRequired; + } + + if (fitWidth == rangeLength) { + // We can't do any better than this, so stop here + break; + } + } + + /* + * If we were unable to find a plan for a fit centered around + * an already-mapped source, just try to find a range of + * registers we can move the range into. + */ + + if (resultRangeStart == -1) { + resultMovesRequired = new BitSet(szSources); + + resultRangeStart = findAnyFittingRange(insn, rangeLength, + categoriesForIndex, resultMovesRequired); + } + + /* + * Now, insert any moves required. + */ + + for (int i = resultMovesRequired.nextSetBit(0); i >= 0; + i = resultMovesRequired.nextSetBit(i+1)) { + insn.changeOneSource(i, insertMoveBefore(insn, sources.get(i))); + } + + return resultRangeStart; + } + + /** + * Finds an unreserved range that will fit the sources of the + * specified instruction. Does not bother trying to center the range + * around an already-mapped source register; + * + * @param insn {@code non-null;} insn to build range for + * @param rangeLength {@code >=0;} length required in register units + * @param categoriesForIndex {@code non-null;} indexed by source index; + * the category for each source + * @param outMovesRequired {@code non-null;} an output parameter indexed by + * source index that will contain the set of sources which need + * moves inserted + * @return the rop register that starts the fitting range + */ + private int findAnyFittingRange(NormalSsaInsn insn, int rangeLength, + int[] categoriesForIndex, BitSet outMovesRequired) { + int rangeStart = paramRangeEnd; + while (true) { + rangeStart = findNextUnreservedRopReg(rangeStart, rangeLength); + int fitWidth + = fitPlanForRange(rangeStart, insn, + categoriesForIndex, outMovesRequired); + + if (fitWidth >= 0) { + break; + } + rangeStart++; + outMovesRequired.clear(); + } + return rangeStart; + } + + /** + * Attempts to build a plan for fitting a range of sources into rop + * registers. + * + * @param ropReg {@code >= 0;} rop reg that begins range + * @param insn {@code non-null;} insn to plan range for + * @param categoriesForIndex {@code non-null;} indexed by source index; + * the category for each source + * @param outMovesRequired {@code non-null;} an output parameter indexed by + * source index that will contain the set of sources which need + * moves inserted + * @return the width of the fit that that does not involve added moves or + * {@code -1} if "no fit possible" + */ + private int fitPlanForRange(int ropReg, NormalSsaInsn insn, + int[] categoriesForIndex, BitSet outMovesRequired) { + RegisterSpecList sources = insn.getSources(); + int szSources = sources.size(); + int fitWidth = 0; + IntSet liveOut = insn.getBlock().getLiveOutRegs(); + RegisterSpecList liveOutSpecs = ssaSetToSpecs(liveOut); + + // An SSA reg may only be mapped into a range once. + BitSet seen = new BitSet(ssaMeth.getRegCount()); + + for (int i = 0; i < szSources ; i++) { + RegisterSpec ssaSpec = sources.get(i); + int ssaReg = ssaSpec.getReg(); + int category = categoriesForIndex[i]; + + if (i != 0) { + ropReg += categoriesForIndex[i-1]; + } + + if (ssaRegsMapped.get(ssaReg) + && mapper.oldToNew(ssaReg) == ropReg) { + // This is a register that is already mapped appropriately. + fitWidth += category; + } else if (rangeContainsReserved(ropReg, category)) { + fitWidth = -1; + break; + } else if (!ssaRegsMapped.get(ssaReg) + && canMapReg(ssaSpec, ropReg) + && !seen.get(ssaReg)) { + // This is a register that can be mapped appropriately. + fitWidth += category; + } else if (!mapper.areAnyPinned(liveOutSpecs, ropReg, category) + && !mapper.areAnyPinned(sources, ropReg, category)) { + /* + * This is a source that can be moved. We can insert a + * move as long as: + * + * * no SSA register pinned to the desired rop reg + * is live out on the block + * + * * no SSA register pinned to desired rop reg is + * a source of this insn (since this may require + * overlapping moves, which we can't presently handle) + */ + + outMovesRequired.set(i); + } else { + fitWidth = -1; + break; + } + + seen.set(ssaReg); + } + return fitWidth; + } + + /** + * Converts a bit set of SSA registers into a RegisterSpecList containing + * the definition specs of all the registers. + * + * @param ssaSet {@code non-null;} set of SSA registers + * @return list of RegisterSpecs as noted above + */ + RegisterSpecList ssaSetToSpecs(IntSet ssaSet) { + RegisterSpecList result = new RegisterSpecList(ssaSet.elements()); + + IntIterator iter = ssaSet.iterator(); + + int i = 0; + while (iter.hasNext()) { + result.set(i++, getDefinitionSpecForSsaReg(iter.next())); + } + + return result; + } + + /** + * Gets a local item associated with an ssa register, if one exists. + * + * @param ssaReg {@code >= 0;} SSA register + * @return {@code null-ok;} associated local item or null + */ + private LocalItem getLocalItemForReg(int ssaReg) { + for (Map.Entry> entry : + localVariables.entrySet()) { + for (RegisterSpec spec : entry.getValue()) { + if (spec.getReg() == ssaReg) { + return entry.getKey(); + } + } + } + + return null; + } + + /** + * Attempts to map the sources and result of a phi to a common register. + * Will try existing mappings first, from most to least common. If none + * of the registers have mappings yet, a new mapping is created. + */ + private void processPhiInsn(PhiInsn insn) { + RegisterSpec result = insn.getResult(); + int resultReg = result.getReg(); + int category = result.getCategory(); + + RegisterSpecList sources = insn.getSources(); + int sourcesSize = sources.size(); + + // List of phi sources / result that need mapping + ArrayList ssaRegs = new ArrayList(); + + // Track how many times a particular mapping is found + Multiset mapSet = new Multiset(sourcesSize + 1); + + /* + * If the result of the phi has an existing mapping, get it. + * Otherwise, add it to the list of regs that need mapping. + */ + if (ssaRegsMapped.get(resultReg)) { + mapSet.add(mapper.oldToNew(resultReg)); + } else { + ssaRegs.add(result); + } + + for (int i = 0; i < sourcesSize; i++) { + RegisterSpec source = sources.get(i); + SsaInsn def = ssaMeth.getDefinitionForRegister(source.getReg()); + RegisterSpec sourceDef = def.getResult(); + int sourceReg = sourceDef.getReg(); + + /* + * If a source of the phi has an existing mapping, get it. + * Otherwise, add it to the list of regs that need mapping. + */ + if (ssaRegsMapped.get(sourceReg)) { + mapSet.add(mapper.oldToNew(sourceReg)); + } else { + ssaRegs.add(sourceDef); + } + } + + // Try all existing mappings, with the most common ones first + for (int i = 0; i < mapSet.getSize(); i++) { + int maxReg = mapSet.getAndRemoveHighestCount(); + tryMapRegs(ssaRegs, maxReg, category, false); + } + + // Map any remaining unmapped regs with whatever fits + int mapReg = findNextUnreservedRopReg(paramRangeEnd, category); + while (!tryMapRegs(ssaRegs, mapReg, category, false)) { + mapReg = findNextUnreservedRopReg(mapReg + 1, category); + } + } + + // A set that tracks how often elements are added to it. + private static class Multiset { + private final int[] reg; + private final int[] count; + private int size; + + /** + * Constructs an instance. + * + * @param maxSize the maximum distinct elements the set may have + */ + public Multiset(int maxSize) { + reg = new int[maxSize]; + count = new int[maxSize]; + size = 0; + } + + /** + * Adds an element to the set. + * + * @param element element to add + */ + public void add(int element) { + for (int i = 0; i < size; i++) { + if (reg[i] == element) { + count[i]++; + return; + } + } + + reg[size] = element; + count[size] = 1; + size++; + } + + /** + * Searches the set for the element that has been added the most. + * In the case of a tie, the element that was added first is returned. + * Then, it clears the count on that element. The size of the set + * remains unchanged. + * + * @return element with the highest count + */ + public int getAndRemoveHighestCount() { + int maxIndex = -1; + int maxReg = -1; + int maxCount = 0; + + for (int i = 0; i < size; i++) { + if (maxCount < count[i]) { + maxIndex = i; + maxReg = reg[i]; + maxCount = count[i]; + } + } + + count[maxIndex] = 0; + return maxReg; + } + + /** + * Gets the number of distinct elements in the set. + * + * @return size of the set + */ + public int getSize() { + return size; + } + } +} diff --git a/dx/src/com/android/jack/dx/ssa/back/IdenticalBlockCombiner.java b/dx/src/com/android/jack/dx/ssa/back/IdenticalBlockCombiner.java new file mode 100644 index 00000000..c679d61e --- /dev/null +++ b/dx/src/com/android/jack/dx/ssa/back/IdenticalBlockCombiner.java @@ -0,0 +1,182 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.ssa.back; + +import com.android.jack.dx.rop.code.BasicBlock; +import com.android.jack.dx.rop.code.BasicBlockList; +import com.android.jack.dx.rop.code.CstInsn; +import com.android.jack.dx.rop.code.Insn; +import com.android.jack.dx.rop.code.InsnList; +import com.android.jack.dx.rop.code.RegOps; +import com.android.jack.dx.rop.code.RopMethod; +import com.android.jack.dx.rop.code.SwitchInsn; +import com.android.jack.dx.util.IntList; + +import java.util.BitSet; + +/** + * Searches for basic blocks that all have the same successor and insns + * but different predecessors. These blocks are then combined into a single + * block and the now-unused blocks are deleted. These identical blocks + * frequently are created when catch blocks are edge-split. + */ +public class IdenticalBlockCombiner { + private final RopMethod ropMethod; + private final BasicBlockList blocks; + private final BasicBlockList newBlocks; + + /** + * Constructs instance. Call {@code process()} to run. + * + * @param rm {@code non-null;} instance to process + */ + public IdenticalBlockCombiner(RopMethod rm) { + ropMethod = rm; + blocks = ropMethod.getBlocks(); + newBlocks = blocks.getMutableCopy(); + } + + /** + * Runs algorithm. TODO: This is n^2, and could be made linear-ish with + * a hash. In particular, hash the contents of each block and only + * compare blocks with the same hash. + * + * @return {@code non-null;} new method that has been processed + */ + public RopMethod process() { + int szBlocks = blocks.size(); + // indexed by label + BitSet toDelete = new BitSet(blocks.getMaxLabel()); + + // For each non-deleted block... + for (int bindex = 0; bindex < szBlocks; bindex++) { + BasicBlock b = blocks.get(bindex); + + if (toDelete.get(b.getLabel())) { + // doomed block + continue; + } + + IntList preds = ropMethod.labelToPredecessors(b.getLabel()); + + // ...look at all of it's predecessors that have only one succ... + int szPreds = preds.size(); + for (int i = 0; i < szPreds; i++) { + int iLabel = preds.get(i); + + BasicBlock iBlock = blocks.labelToBlock(iLabel); + + if (toDelete.get(iLabel) + || iBlock.getSuccessors().size() > 1 + || iBlock.getFirstInsn().getOpcode().getOpcode() == + RegOps.MOVE_RESULT) { + continue; + } + + IntList toCombine = new IntList(); + + // ...and see if they can be combined with any other preds... + for (int j = i + 1; j < szPreds; j++) { + int jLabel = preds.get(j); + BasicBlock jBlock = blocks.labelToBlock(jLabel); + + if (jBlock.getSuccessors().size() == 1 + && compareInsns(iBlock, jBlock)) { + + toCombine.add(jLabel); + toDelete.set(jLabel); + } + } + + combineBlocks(iLabel, toCombine); + } + } + + for (int i = szBlocks - 1; i >= 0; i--) { + if (toDelete.get(newBlocks.get(i).getLabel())) { + newBlocks.set(i, null); + } + } + + newBlocks.shrinkToFit(); + newBlocks.setImmutable(); + + return new RopMethod(newBlocks, ropMethod.getFirstLabel()); + } + + /** + * Helper method to compare the contents of two blocks. + * + * @param a {@code non-null;} a block to compare + * @param b {@code non-null;} another block to compare + * @return {@code true} iff the two blocks' instructions are the same + */ + private static boolean compareInsns(BasicBlock a, BasicBlock b) { + return a.getInsns().contentEquals(b.getInsns()); + } + + /** + * Combines blocks proven identical into one alpha block, re-writing + * all of the successor links that point to the beta blocks to point + * to the alpha block instead. + * + * @param alphaLabel block that will replace all the beta block + * @param betaLabels label list of blocks to combine + */ + private void combineBlocks(int alphaLabel, IntList betaLabels) { + int szBetas = betaLabels.size(); + + for (int i = 0; i < szBetas; i++) { + int betaLabel = betaLabels.get(i); + BasicBlock bb = blocks.labelToBlock(betaLabel); + IntList preds = ropMethod.labelToPredecessors(bb.getLabel()); + int szPreds = preds.size(); + + for (int j = 0; j < szPreds; j++) { + BasicBlock predBlock = newBlocks.labelToBlock(preds.get(j)); + replaceSucc(predBlock, betaLabel, alphaLabel); + } + } + } + + /** + * Replaces one of a block's successors with a different label. Constructs + * an updated BasicBlock instance and places it in {@code newBlocks}. + * + * @param block block to replace + * @param oldLabel label of successor to replace + * @param newLabel label of new successor + */ + private void replaceSucc(BasicBlock block, int oldLabel, int newLabel) { + IntList newSuccessors = block.getSuccessors().mutableCopy(); + int newPrimarySuccessor; + + newSuccessors.set(newSuccessors.indexOf(oldLabel), newLabel); + newPrimarySuccessor = block.getPrimarySuccessor(); + + if (newPrimarySuccessor == oldLabel) { + newPrimarySuccessor = newLabel; + } + + newSuccessors.setImmutable(); + + BasicBlock newBB = new BasicBlock(block.getLabel(), + block.getInsns(), newSuccessors, newPrimarySuccessor); + + newBlocks.set(newBlocks.indexOfLabel(block.getLabel()), newBB); + } +} diff --git a/dx/src/com/android/jack/dx/ssa/back/InterferenceGraph.java b/dx/src/com/android/jack/dx/ssa/back/InterferenceGraph.java new file mode 100644 index 00000000..00a2c699 --- /dev/null +++ b/dx/src/com/android/jack/dx/ssa/back/InterferenceGraph.java @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.ssa.back; + +import com.android.jack.dx.rop.code.RegisterSpec; +import com.android.jack.dx.ssa.PhiInsn; +import com.android.jack.dx.ssa.SetFactory; +import com.android.jack.dx.ssa.SsaBasicBlock; +import com.android.jack.dx.ssa.SsaInsn; +import com.android.jack.dx.ssa.SsaMethod; +import com.android.jack.dx.util.BitIntSet; +import com.android.jack.dx.util.IntSet; +import com.android.jack.dx.util.ListIntSet; + +import java.util.BitSet; +import java.util.List; +import java.util.ArrayList; + +/** + * A register interference graph + */ +public class InterferenceGraph { + /** + * {@code non-null;} interference graph, indexed by register in + * both dimensions + */ + private final ArrayList interference; + + /** + * Creates a new graph. + * + * @param countRegs {@code >= 0;} the start count of registers in + * the namespace. New registers can be added subsequently. + */ + public InterferenceGraph(int countRegs) { + interference = new ArrayList(countRegs); + + for (int i = 0; i < countRegs; i++) { + interference.add(SetFactory.makeInterferenceSet(countRegs)); + } + } + + /** + * Adds a register pair to the interference/liveness graph. Parameter + * order is insignificant. + * + * @param regV one register index + * @param regW another register index + */ + public void add(int regV, int regW) { + ensureCapacity(Math.max(regV, regW) + 1); + + interference.get(regV).add(regW); + interference.get(regW).add(regV); + } + + /** + * Dumps interference graph to stdout for debugging. + */ + public void dumpToStdout() { + int oldRegCount = interference.size(); + + for (int i = 0; i < oldRegCount; i++) { + StringBuilder sb = new StringBuilder(); + + sb.append("Reg " + i + ":" + interference.get(i).toString()); + + System.out.println(sb.toString()); + } + } + + /** + * Merges the interference set for a register into a given bit set + * + * @param reg {@code >= 0;} register + * @param set {@code non-null;} interference set; will be merged + * with set for given register + */ + public void mergeInterferenceSet(int reg, IntSet set) { + if (reg < interference.size()) { + set.merge(interference.get(reg)); + } + } + + /** + * Ensures that the interference graph is appropriately sized. + * + * @param size requested minumum size + */ + private void ensureCapacity(int size) { + int countRegs = interference.size(); + + interference.ensureCapacity(size); + + for (int i = countRegs; i < size; i++) { + interference.add(SetFactory.makeInterferenceSet(size)); + } + } +} diff --git a/dx/src/com/android/jack/dx/ssa/back/LivenessAnalyzer.java b/dx/src/com/android/jack/dx/ssa/back/LivenessAnalyzer.java new file mode 100644 index 00000000..44c9964c --- /dev/null +++ b/dx/src/com/android/jack/dx/ssa/back/LivenessAnalyzer.java @@ -0,0 +1,287 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.ssa.back; + +import com.android.jack.dx.rop.code.RegisterSpec; +import com.android.jack.dx.ssa.PhiInsn; +import com.android.jack.dx.ssa.SsaBasicBlock; +import com.android.jack.dx.ssa.SsaInsn; +import com.android.jack.dx.ssa.SsaMethod; + +import java.util.BitSet; +import java.util.List; +import java.util.ArrayList; + +/** + * From Appel "Modern Compiler Implementation in Java" algorithm 19.17 + * Calculate the live ranges for register {@code reg}.

      + * + * v = regV

      + * s = insn

      + * M = visitedBlocks

      + */ +public class LivenessAnalyzer { + /** + * {@code non-null;} index by basic block indexed set of basic blocks + * that have already been visited. "M" as written in the original Appel + * algorithm. + */ + private final BitSet visitedBlocks; + + /** + * {@code non-null;} set of blocks remaing to visit as "live out as block" + */ + private final BitSet liveOutBlocks; + + /** + * {@code >=0;} SSA register currently being analyzed. + * "v" in the original Appel algorithm. + */ + private final int regV; + + /** method to process */ + private final SsaMethod ssaMeth; + + /** interference graph being updated */ + private final InterferenceGraph interference; + + /** block "n" in Appel 19.17 */ + private SsaBasicBlock blockN; + + /** index of statement {@code s} in {@code blockN} */ + private int statementIndex; + + /** the next function to call */ + private NextFunction nextFunction; + + /** constants for {@link #nextFunction} */ + private static enum NextFunction { + LIVE_IN_AT_STATEMENT, + LIVE_OUT_AT_STATEMENT, + LIVE_OUT_AT_BLOCK, + DONE; + } + + /** + * Runs register liveness algorithm for a method, updating the + * live in/out information in {@code SsaBasicBlock} instances and + * returning an interference graph. + * + * @param ssaMeth {@code non-null;} method to process + * @return {@code non-null;} interference graph indexed by SSA + * registers in both directions + */ + public static InterferenceGraph constructInterferenceGraph( + SsaMethod ssaMeth) { + int szRegs = ssaMeth.getRegCount(); + InterferenceGraph interference = new InterferenceGraph(szRegs); + + for (int i = 0; i < szRegs; i++) { + new LivenessAnalyzer(ssaMeth, i, interference).run(); + } + + coInterferePhis(ssaMeth, interference); + + return interference; + } + + /** + * Makes liveness analyzer instance for specific register. + * + * @param ssaMeth {@code non-null;} method to process + * @param reg register whose liveness to analyze + * @param interference {@code non-null;} indexed by SSA reg in + * both dimensions; graph to update + * + */ + private LivenessAnalyzer(SsaMethod ssaMeth, int reg, + InterferenceGraph interference) { + int blocksSz = ssaMeth.getBlocks().size(); + + this.ssaMeth = ssaMeth; + this.regV = reg; + visitedBlocks = new BitSet(blocksSz); + liveOutBlocks = new BitSet(blocksSz); + this.interference = interference; + } + + /** + * The algorithm in Appel is presented in partial tail-recursion + * form. Obviously, that's not efficient in java, so this function + * serves as the dispatcher instead. + */ + private void handleTailRecursion() { + while (nextFunction != NextFunction.DONE) { + switch (nextFunction) { + case LIVE_IN_AT_STATEMENT: + nextFunction = NextFunction.DONE; + liveInAtStatement(); + break; + + case LIVE_OUT_AT_STATEMENT: + nextFunction = NextFunction.DONE; + liveOutAtStatement(); + break; + + case LIVE_OUT_AT_BLOCK: + nextFunction = NextFunction.DONE; + liveOutAtBlock(); + break; + + default: + } + } + } + + /** + * From Appel algorithm 19.17. + */ + public void run() { + List useList = ssaMeth.getUseListForRegister(regV); + + for (SsaInsn insn : useList) { + nextFunction = NextFunction.DONE; + + if (insn instanceof PhiInsn) { + // If s is a phi-function with V as it's ith argument. + PhiInsn phi = (PhiInsn) insn; + + for (SsaBasicBlock pred : + phi.predBlocksForReg(regV, ssaMeth)) { + blockN = pred; + + nextFunction = NextFunction.LIVE_OUT_AT_BLOCK; + handleTailRecursion(); + } + } else { + // Special management for registers of category 2, indeed they use + // two 32-bits registers thus there is an implicit interference between + // the result register and operands contrary to category 1 where there + // is no writing of registers before all operands are read. + RegisterSpec resultSpec = insn.getResult(); + if (resultSpec != null && resultSpec.getCategory() == 2 + && insn.getSources().specForRegister(regV).getCategory() == 2) { + interference.add(regV, resultSpec.getReg()); + } + + blockN = insn.getBlock(); + statementIndex = blockN.getInsns().indexOf(insn); + + if (statementIndex < 0) { + throw new RuntimeException( + "insn not found in it's own block"); + } + + nextFunction = NextFunction.LIVE_IN_AT_STATEMENT; + handleTailRecursion(); + } + } + + int nextLiveOutBlock; + while ((nextLiveOutBlock = liveOutBlocks.nextSetBit(0)) >= 0) { + blockN = ssaMeth.getBlocks().get(nextLiveOutBlock); + liveOutBlocks.clear(nextLiveOutBlock); + nextFunction = NextFunction.LIVE_OUT_AT_BLOCK; + handleTailRecursion(); + } + } + + /** + * "v is live-out at n." + */ + private void liveOutAtBlock() { + if (! visitedBlocks.get(blockN.getIndex())) { + visitedBlocks.set(blockN.getIndex()); + + blockN.addLiveOut(regV); + + ArrayList insns; + + insns = blockN.getInsns(); + + // Live out at last statement in blockN + statementIndex = insns.size() - 1; + nextFunction = NextFunction.LIVE_OUT_AT_STATEMENT; + } + } + + /** + * "v is live-in at s." + */ + private void liveInAtStatement() { + // if s is the first statement in block N + if (statementIndex == 0) { + // v is live-in at n + blockN.addLiveIn(regV); + + BitSet preds = blockN.getPredecessors(); + + liveOutBlocks.or(preds); + } else { + // Let s' be the statement preceeding s + statementIndex -= 1; + nextFunction = NextFunction.LIVE_OUT_AT_STATEMENT; + } + } + + /** + * "v is live-out at s." + */ + private void liveOutAtStatement() { + SsaInsn statement = blockN.getInsns().get(statementIndex); + RegisterSpec rs = statement.getResult(); + + if (!statement.isResultReg(regV)) { + if (rs != null) { + interference.add(regV, rs.getReg()); + } + nextFunction = NextFunction.LIVE_IN_AT_STATEMENT; + } + } + + /** + * Ensures that all the phi result registers for all the phis in the + * same basic block interfere with each other. This is needed since + * the dead code remover has allowed through "dead-end phis" whose + * results are not used except as local assignments. Without this step, + * a the result of a dead-end phi might be assigned the same register + * as the result of another phi, and the phi removal move scheduler may + * generate moves that over-write the live result. + * + * @param ssaMeth {@code non-null;} method to pricess + * @param interference {@code non-null;} interference graph + */ + private static void coInterferePhis(SsaMethod ssaMeth, + InterferenceGraph interference) { + for (SsaBasicBlock b : ssaMeth.getBlocks()) { + List phis = b.getPhiInsns(); + + int szPhis = phis.size(); + + for (int i = 0; i < szPhis; i++) { + for (int j = 0; j < szPhis; j++) { + if (i == j) { + continue; + } + + interference.add(phis.get(i).getResult().getReg(), + phis.get(j).getResult().getReg()); + } + } + } + } +} diff --git a/dx/src/com/android/jack/dx/ssa/back/NullRegisterAllocator.java b/dx/src/com/android/jack/dx/ssa/back/NullRegisterAllocator.java new file mode 100644 index 00000000..28b20009 --- /dev/null +++ b/dx/src/com/android/jack/dx/ssa/back/NullRegisterAllocator.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.ssa.back; + +import com.android.jack.dx.ssa.BasicRegisterMapper; +import com.android.jack.dx.ssa.RegisterMapper; +import com.android.jack.dx.ssa.SsaMethod; + +import java.util.BitSet; +import java.util.ArrayList; + +/** + * A register allocator that maps SSA register n to Rop register 2*n, + * essentially preserving the original mapping and remaining agnostic + * about normal or wide categories. Used for debugging. + */ +public class NullRegisterAllocator extends RegisterAllocator { + /** {@inheritDoc} */ + public NullRegisterAllocator(SsaMethod ssaMeth, + InterferenceGraph interference) { + super(ssaMeth, interference); + } + + /** {@inheritDoc} */ + @Override + public boolean wantsParamsMovedHigh() { + // We're not smart enough for this. + return false; + } + + /** {@inheritDoc} */ + @Override + public RegisterMapper allocateRegisters() { + int oldRegCount = ssaMeth.getRegCount(); + + BasicRegisterMapper mapper = new BasicRegisterMapper(oldRegCount); + + for (int i = 0; i < oldRegCount; i++) { + mapper.addMapping(i, i*2, 2); + } + + return mapper; + } +} diff --git a/dx/src/com/android/jack/dx/ssa/back/RegisterAllocator.java b/dx/src/com/android/jack/dx/ssa/back/RegisterAllocator.java new file mode 100644 index 00000000..e81a9d68 --- /dev/null +++ b/dx/src/com/android/jack/dx/ssa/back/RegisterAllocator.java @@ -0,0 +1,197 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.ssa.back; + +import com.android.jack.dx.rop.code.PlainInsn; +import com.android.jack.dx.rop.code.RegOps; +import com.android.jack.dx.rop.code.RegisterSpec; +import com.android.jack.dx.rop.code.RegisterSpecList; +import com.android.jack.dx.rop.code.Rops; +import com.android.jack.dx.rop.code.SourcePosition; +import com.android.jack.dx.ssa.NormalSsaInsn; +import com.android.jack.dx.ssa.RegisterMapper; +import com.android.jack.dx.ssa.SsaBasicBlock; +import com.android.jack.dx.ssa.SsaInsn; +import com.android.jack.dx.ssa.SsaMethod; +import com.android.jack.dx.util.IntIterator; +import com.android.jack.dx.util.IntSet; + +import java.util.BitSet; +import java.util.ArrayList; + +/** + * Base class of all register allocators. + */ +public abstract class RegisterAllocator { + /** method being processed */ + protected final SsaMethod ssaMeth; + + /** interference graph, indexed by register in both dimensions */ + protected final InterferenceGraph interference; + + /** + * Creates an instance. Call {@code allocateRegisters} to run. + * @param ssaMeth method to process. + * @param interference Interference graph, indexed by register in both + * dimensions. + */ + public RegisterAllocator(SsaMethod ssaMeth, + InterferenceGraph interference) { + this.ssaMeth = ssaMeth; + this.interference = interference; + } + + /** + * Indicates whether the method params were allocated at the bottom + * of the namespace, and thus should be moved up to the top of the + * namespace after phi removal. + * + * @return {@code true} if params should be moved from low to high + */ + public abstract boolean wantsParamsMovedHigh(); + + /** + * Runs the algorithm. + * + * @return a register mapper to apply to the {@code SsaMethod} + */ + public abstract RegisterMapper allocateRegisters(); + + /** + * Returns the category (width) of the definition site of the register. + * Returns {@code 1} for undefined registers. + * + * @param reg register + * @return {@code 1..2} + */ + protected final int getCategoryForSsaReg(int reg) { + SsaInsn definition = ssaMeth.getDefinitionForRegister(reg); + + if (definition == null) { + // an undefined reg + return 1; + } else { + return definition.getResult().getCategory(); + } + } + + /** + * Returns the RegisterSpec of the definition of the register. + * + * @param reg {@code >= 0;} SSA register + * @return definition spec of the register or null if it is never defined + * (for the case of "version 0" SSA registers) + */ + protected final RegisterSpec getDefinitionSpecForSsaReg(int reg) { + SsaInsn definition = ssaMeth.getDefinitionForRegister(reg); + + return definition == null ? null : definition.getResult(); + } + + /** + * Returns true if the definition site of this register is a + * move-param (ie, this is a method parameter). + * + * @param reg register in question + * @return {@code true} if this is a method parameter + */ + protected boolean isDefinitionMoveParam(int reg) { + SsaInsn defInsn = ssaMeth.getDefinitionForRegister(reg); + + if (defInsn instanceof NormalSsaInsn) { + NormalSsaInsn ndefInsn = (NormalSsaInsn) defInsn; + + return ndefInsn.getOpcode().getOpcode() == RegOps.MOVE_PARAM; + } + + return false; + } + + /** + * Inserts a move instruction for a specified SSA register before a + * specified instruction, creating a new SSA register and adjusting the + * interference graph in the process. The insn currently must be the + * last insn in a block. + * + * @param insn {@code non-null;} insn to insert move before, must + * be last insn in block + * @param reg {@code non-null;} SSA register to duplicate + * @return {@code non-null;} spec of new SSA register created by move + */ + protected final RegisterSpec insertMoveBefore(SsaInsn insn, + RegisterSpec reg) { + SsaBasicBlock block = insn.getBlock(); + ArrayList insns = block.getInsns(); + int insnIndex = insns.indexOf(insn); + + if (insnIndex < 0) { + throw new IllegalArgumentException ( + "specified insn is not in this block"); + } + + if (insnIndex != insns.size() - 1) { + /* + * Presently, the interference updater only works when + * adding before the last insn, and the last insn must have no + * result + */ + throw new IllegalArgumentException( + "Adding move here not supported:" + insn.toHuman()); + } + + /* + * Get new register and make new move instruction. + */ + + // The new result must not have an associated local variable. + RegisterSpec newRegSpec = RegisterSpec.make(ssaMeth.makeNewSsaReg(), + reg.getTypeBearer()); + + SsaInsn toAdd = SsaInsn.makeFromRop( + new PlainInsn(Rops.opMove(newRegSpec.getType()), + SourcePosition.NO_INFO, newRegSpec, + RegisterSpecList.make(reg)), block); + + insns.add(insnIndex, toAdd); + + int newReg = newRegSpec.getReg(); + + /* + * Adjust interference graph based on what's live out of the current + * block and what's used by the final instruction. + */ + + IntSet liveOut = block.getLiveOutRegs(); + IntIterator liveOutIter = liveOut.iterator(); + + while (liveOutIter.hasNext()) { + interference.add(newReg, liveOutIter.next()); + } + + // Everything that's a source in the last insn interferes. + RegisterSpecList sources = insn.getSources(); + int szSources = sources.size(); + + for (int i = 0; i < szSources; i++) { + interference.add(newReg, sources.get(i).getReg()); + } + + ssaMeth.onInsnsChanged(); + + return newRegSpec; + } +} diff --git a/dx/src/com/android/jack/dx/ssa/back/SsaToRop.java b/dx/src/com/android/jack/dx/ssa/back/SsaToRop.java new file mode 100644 index 00000000..2c63a55f --- /dev/null +++ b/dx/src/com/android/jack/dx/ssa/back/SsaToRop.java @@ -0,0 +1,380 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.ssa.back; + +import com.android.jack.dx.rop.code.BasicBlock; +import com.android.jack.dx.rop.code.BasicBlockList; +import com.android.jack.dx.rop.code.InsnList; +import com.android.jack.dx.rop.code.RegisterSpec; +import com.android.jack.dx.rop.code.RegisterSpecList; +import com.android.jack.dx.rop.code.Rop; +import com.android.jack.dx.rop.code.RopMethod; +import com.android.jack.dx.rop.code.Rops; +import com.android.jack.dx.ssa.BasicRegisterMapper; +import com.android.jack.dx.ssa.PhiInsn; +import com.android.jack.dx.ssa.RegisterMapper; +import com.android.jack.dx.ssa.SsaBasicBlock; +import com.android.jack.dx.ssa.SsaInsn; +import com.android.jack.dx.ssa.SsaMethod; +import com.android.jack.dx.util.Hex; +import com.android.jack.dx.util.IntList; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.BitSet; +import java.util.Comparator; + +/** + * Converts a method in SSA form to ROP form. + */ +public class SsaToRop { + /** local debug flag */ + private static final boolean DEBUG = false; + + /** {@code non-null;} method to process */ + private final SsaMethod ssaMeth; + + /** + * {@code true} if the converter should attempt to minimize + * the rop-form register count + */ + private final boolean minimizeRegisters; + + /** {@code non-null;} interference graph */ + private final InterferenceGraph interference; + + /** + * Converts a method in SSA form to ROP form. + * + * @param ssaMeth {@code non-null;} method to process + * @param minimizeRegisters {@code true} if the converter should + * attempt to minimize the rop-form register count + * @return {@code non-null;} rop-form output + */ + public static RopMethod convertToRopMethod(SsaMethod ssaMeth, + boolean minimizeRegisters) { + return new SsaToRop(ssaMeth, minimizeRegisters).convert(); + } + + /** + * Constructs an instance. + * + * @param ssaMeth {@code non-null;} method to process + * @param minimizeRegisters {@code true} if the converter should + * attempt to minimize the rop-form register count + */ + private SsaToRop(SsaMethod ssaMethod, boolean minimizeRegisters) { + this.minimizeRegisters = minimizeRegisters; + this.ssaMeth = ssaMethod; + this.interference = + LivenessAnalyzer.constructInterferenceGraph(ssaMethod); + } + + /** + * Performs the conversion. + * + * @return {@code non-null;} rop-form output + */ + private RopMethod convert() { + if (DEBUG) { + interference.dumpToStdout(); + } + + // These are other allocators for debugging or historical comparison: + // allocator = new NullRegisterAllocator(ssaMeth, interference); + // allocator = new FirstFitAllocator(ssaMeth, interference); + + RegisterAllocator allocator = + new FirstFitLocalCombiningAllocator(ssaMeth, interference, + minimizeRegisters); + + RegisterMapper mapper = allocator.allocateRegisters(); + + if (DEBUG) { + System.out.println("Printing reg map"); + System.out.println(((BasicRegisterMapper)mapper).toHuman()); + } + + ssaMeth.setBackMode(); + + ssaMeth.mapRegisters(mapper); + + removePhiFunctions(); + + if (allocator.wantsParamsMovedHigh()) { + moveParametersToHighRegisters(); + } + + removeEmptyGotos(); + + RopMethod ropMethod = new RopMethod(convertBasicBlocks(), + ssaMeth.blockIndexToRopLabel(ssaMeth.getEntryBlockIndex())); + ropMethod = new IdenticalBlockCombiner(ropMethod).process(); + + return ropMethod; + } + + /** + * Removes all blocks containing only GOTOs from the control flow. + * Although much of this work will be done later when converting + * from rop to dex, not all simplification cases can be handled + * there. Furthermore, any no-op block between the exit block and + * blocks containing the real return or throw statements must be + * removed. + */ + private void removeEmptyGotos() { + final ArrayList blocks = ssaMeth.getBlocks(); + + ssaMeth.forEachBlockDepthFirst(false, new SsaBasicBlock.Visitor() { + public void visitBlock(SsaBasicBlock b, SsaBasicBlock parent) { + ArrayList insns = b.getInsns(); + + if ((insns.size() == 1) + && (insns.get(0).getOpcode() == Rops.GOTO)) { + BitSet preds = (BitSet) b.getPredecessors().clone(); + + for (int i = preds.nextSetBit(0); i >= 0; + i = preds.nextSetBit(i + 1)) { + SsaBasicBlock pb = blocks.get(i); + pb.replaceSuccessor(b.getIndex(), + b.getPrimarySuccessorIndex()); + } + } + } + }); + } + + /** + * See Appel 19.6. To remove the phi instructions in an edge-split + * SSA representation we know we can always insert a move in a + * predecessor block. + */ + private void removePhiFunctions() { + ArrayList blocks = ssaMeth.getBlocks(); + + for (SsaBasicBlock block : blocks) { + // Add moves in all the pred blocks for each phi insn. + block.forEachPhiInsn(new PhiVisitor(blocks)); + + // Delete the phi insns. + block.removeAllPhiInsns(); + } + + /* + * After all move insns have been added, sort them so they don't + * destructively interfere. + */ + for (SsaBasicBlock block : blocks) { + block.scheduleMovesFromPhis(); + } + } + + /** + * Helper for {@link #removePhiFunctions}: PhiSuccessorUpdater for + * adding move instructions to predecessors based on phi insns. + */ + private static class PhiVisitor implements PhiInsn.Visitor { + private final ArrayList blocks; + + public PhiVisitor(ArrayList blocks) { + this.blocks = blocks; + } + + public void visitPhiInsn(PhiInsn insn) { + RegisterSpecList sources = insn.getSources(); + RegisterSpec result = insn.getResult(); + int sz = sources.size(); + + for (int i = 0; i < sz; i++) { + RegisterSpec source = sources.get(i); + SsaBasicBlock predBlock = blocks.get( + insn.predBlockIndexForSourcesIndex(i)); + + predBlock.addMoveToEnd(result, source); + } + } + } + + /** + * Moves the parameter registers, which allocateRegisters() places + * at the bottom of the frame, up to the top of the frame to match + * Dalvik calling convention. + */ + private void moveParametersToHighRegisters() { + int paramWidth = ssaMeth.getParamWidth(); + BasicRegisterMapper mapper + = new BasicRegisterMapper(ssaMeth.getRegCount()); + int regCount = ssaMeth.getRegCount(); + + for (int i = 0; i < regCount; i++) { + if (i < paramWidth) { + mapper.addMapping(i, regCount - paramWidth + i, 1); + } else { + mapper.addMapping(i, i - paramWidth, 1); + } + } + + if (DEBUG) { + System.out.printf("Moving %d registers from 0 to %d\n", + paramWidth, regCount - paramWidth); + } + + ssaMeth.mapRegisters(mapper); + } + + /** + * @return rop-form basic block list + */ + private BasicBlockList convertBasicBlocks() { + ArrayList blocks = ssaMeth.getBlocks(); + + // Exit block may be null. + SsaBasicBlock exitBlock = ssaMeth.getExitBlock(); + + ssaMeth.computeReachability(); + int ropBlockCount = ssaMeth.getCountReachableBlocks(); + + // Don't count the exit block, if it exists and is reachable. + ropBlockCount -= (exitBlock != null && exitBlock.isReachable()) ? 1 : 0; + + BasicBlockList result = new BasicBlockList(ropBlockCount); + + // Convert all the reachable blocks except the exit block. + int ropBlockIndex = 0; + for (SsaBasicBlock b : blocks) { + if (b.isReachable() && b != exitBlock) { + result.set(ropBlockIndex++, convertBasicBlock(b)); + } + } + + // The exit block, which is discarded, must do nothing. + if (exitBlock != null && exitBlock.getInsns().size() != 0) { + throw new RuntimeException( + "Exit block must have no insns when leaving SSA form"); + } + + return result; + } + + /** + * Validates that a basic block is a valid end predecessor. It must + * end in a RETURN or a THROW. Throws a runtime exception on error. + * + * @param b {@code non-null;} block to validate + * @throws RuntimeException on error + */ + private void verifyValidExitPredecessor(SsaBasicBlock b) { + ArrayList insns = b.getInsns(); + SsaInsn lastInsn = insns.get(insns.size() - 1); + Rop opcode = lastInsn.getOpcode(); + + if (opcode.getBranchingness() != Rop.BRANCH_RETURN + && opcode != Rops.THROW) { + throw new RuntimeException("Exit predecessor must end" + + " in valid exit statement."); + } + } + + /** + * Converts a single basic block to rop form. + * + * @param block SSA block to process + * @return {@code non-null;} ROP block + */ + private BasicBlock convertBasicBlock(SsaBasicBlock block) { + IntList successorList = block.getRopLabelSuccessorList(); + int primarySuccessorLabel = block.getPrimarySuccessorRopLabel(); + + // Filter out any reference to the SSA form's exit block. + + // Exit block may be null. + SsaBasicBlock exitBlock = ssaMeth.getExitBlock(); + int exitRopLabel = (exitBlock == null) ? -1 : exitBlock.getRopLabel(); + + if (successorList.contains(exitRopLabel)) { + if (successorList.size() > 1) { + throw new RuntimeException( + "Exit predecessor must have no other successors" + + Hex.u2(block.getRopLabel())); + } else { + successorList = IntList.EMPTY; + primarySuccessorLabel = -1; + + verifyValidExitPredecessor(block); + } + } + + successorList.setImmutable(); + + BasicBlock result = new BasicBlock( + block.getRopLabel(), convertInsns(block.getInsns()), + successorList, + primarySuccessorLabel); + + return result; + } + + /** + * Converts an insn list to rop form. + * + * @param ssaInsns {@code non-null;} old instructions + * @return {@code non-null;} immutable instruction list + */ + private InsnList convertInsns(ArrayList ssaInsns) { + int insnCount = ssaInsns.size(); + InsnList result = new InsnList(insnCount); + + for (int i = 0; i < insnCount; i++) { + result.set(i, ssaInsns.get(i).toRopInsn()); + } + + result.setImmutable(); + + return result; + } + + /** + * Note: This method is not presently used. + * + * @return a list of registers ordered by most-frequently-used to + * least-frequently-used. Each register is listed once and only + * once. + */ + public int[] getRegistersByFrequency() { + int regCount = ssaMeth.getRegCount(); + Integer[] ret = new Integer[regCount]; + + for (int i = 0; i < regCount; i++) { + ret[i] = i; + } + + Arrays.sort(ret, new Comparator() { + public int compare(Integer o1, Integer o2) { + return ssaMeth.getUseListForRegister(o2).size() + - ssaMeth.getUseListForRegister(o1).size(); + } + }); + + int result[] = new int[regCount]; + + for (int i = 0; i < regCount; i++) { + result[i] = ret[i]; + } + + return result; + } +} diff --git a/dx/src/com/android/jack/dx/ssa/package-info.java b/dx/src/com/android/jack/dx/ssa/package-info.java new file mode 100644 index 00000000..eb704fde --- /dev/null +++ b/dx/src/com/android/jack/dx/ssa/package-info.java @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.ssa; + +/** + *

      An introduction to SSA Form

      + * + * This package contains classes associated with dx's {@code SSA} + * intermediate form. This form is a static-single-assignment representation of + * Rop-form a method with Rop-form-like instructions (with the addition of a + * {@link PhiInsn phi instriction}. This form is intended to make it easy to + * implement basic optimization steps and register allocation so that a + * reasonably efficient register machine representation can be produced from a + * stack machine source bytecode.

      + * + *

      Key Classes

      + * + *

      Classes related to conversion and lifetime

      + *
        + *
      • {@link Optimizer} is a singleton class containing methods for + * converting, optimizing, and then back-converting Rop-form methods. It's the + * typical gateway into the rest of the package. + *
      • {@link SsaConverter} converts a Rop-form method to SSA form. + *
      • {@link SsaToRop} converts an SSA-form method back to Rop form. + *
      + * + *

      Classes related to method representation

      + *
        + *
      • A {@link SsaMethod} instance represents a method. + *
      • A {@link SsaBasicBlock} instance represents a basic block, whose + * semantics are quite similar to basic blocks in + * {@link com.android.jack.dx.rop Rop form}. + *
      • {@link PhiInsn} instances represent "phi" operators defined in SSA + * literature. They must be the first N instructions in a basic block. + *
      • {@link NormalSsaInsn} instances represent instructions that directly + * correspond to {@code Rop} form. + *
      + * + *

      Classes related to optimization steps

      + *
        + *
      • {@link MoveParamCombiner} is a simple step that ensures each method + * parameter is represented by at most one SSA register. + *
      • {@link SCCP} is a (partially implemented) sparse-conditional + * constant propogator. + *
      • {@link LiteralOpUpgrader} is a step that attempts to use constant + * information to convert math and comparison instructions into + * constant-bearing "literal ops" in cases where they can be represented in the + * output form (see {@link TranslationAdvice#hasConstantOperation}). + *
      • {@link ConstCollector} is a step that attempts to trade (modest) + * increased register space for decreased instruction count in cases where + * the same constant value is used repeatedly in a single method. + *
      • {@link DeadCodeRemover} is a dead code remover. This phase must + * always be run to remove unused phi instructions. + *
      + * + *

      SSA Lifetime

      + * The representation of a method in SSA form obeys slightly different + * constraints depending upon whether it is in the process of being converted + * into or out of SSA form. + * + *

      Conversion into SSA Form

      + * + * {@link SsaConverter#convertToSsaMethod} takes a {@code RopMethod} and + * returns a fully-converted {@code SsaMethod}. The conversion process + * is roughly as follows: + * + *
        + *
      1. The Rop-form method, its blocks and their instructions are directly + * wrapped in {@code SsaMethod}, {@code SsaBasicBlock} and + * {@code SsaInsn} instances. Nothing else changes. + *
      2. Critical control-flow graph edges are {@link SsaConverter#edgeSplit + * split} and new basic blocks inserted as required to meet the constraints + * necessary for the ultimate SSA representation. + *
      3. A {@link LocalVariableExtractor} is run to produce a table of + * Rop registers to local variables necessary during phi placement. This + * step could also be done in Rop form and then updated through the preceding + * steps. + *
      4. {@code Phi} instructions are {link SsaConverter#placePhiFunctions} + * placed in a semi-pruned fashion, which requires computation of {@link + * Dominators dominance graph} and each node's {@link DomFront + * dominance-frontier set}. + *
      5. Finally, source and result registers for all instructions are {@link + * SsaRenamer renamed} such that each assignment is given a unique register + * number (register categories or widths, significant in Rop form, do not + * exist in SSA). Move instructions are eliminated except where necessary + * to preserve local variable assignments. + *
      + * + */ diff --git a/dx/src/com/android/jack/dx/util/AnnotatedOutput.java b/dx/src/com/android/jack/dx/util/AnnotatedOutput.java new file mode 100644 index 00000000..6bbe4b75 --- /dev/null +++ b/dx/src/com/android/jack/dx/util/AnnotatedOutput.java @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.util; + +/** + * Interface for a binary output destination that may be augmented + * with textual annotations. + */ +public interface AnnotatedOutput + extends Output { + /** + * Get whether this instance will actually keep annotations. + * + * @return {@code true} iff annotations are being kept + */ + public boolean annotates(); + + /** + * Get whether this instance is intended to keep verbose annotations. + * Annotators may use the result of calling this method to inform their + * annotation activity. + * + * @return {@code true} iff annotations are to be verbose + */ + public boolean isVerbose(); + + /** + * Add an annotation for the subsequent output. Any previously + * open annotation will be closed by this call, and the new + * annotation marks all subsequent output until another annotation + * call. + * + * @param msg {@code non-null;} the annotation message + */ + public void annotate(String msg); + + /** + * Add an annotation for a specified amount of subsequent + * output. Any previously open annotation will be closed by this + * call. If there is already pending annotation from one or more + * previous calls to this method, the new call "consumes" output + * after all the output covered by the previous calls. + * + * @param amt {@code >= 0;} the amount of output for this annotation to + * cover + * @param msg {@code non-null;} the annotation message + */ + public void annotate(int amt, String msg); + + /** + * End the most recent annotation. Subsequent output will be unannotated, + * until the next call to {@link #annotate}. + */ + public void endAnnotation(); + + /** + * Get the maximum width of the annotated output. This is advisory: + * Implementations of this interface are encouraged to deal with too-wide + * output, but annotaters are encouraged to attempt to avoid exceeding + * the indicated width. + * + * @return {@code >= 1;} the maximum width + */ + public int getAnnotationWidth(); +} diff --git a/dx/src/com/android/jack/dx/util/BitIntSet.java b/dx/src/com/android/jack/dx/util/BitIntSet.java new file mode 100644 index 00000000..a4f51563 --- /dev/null +++ b/dx/src/com/android/jack/dx/util/BitIntSet.java @@ -0,0 +1,145 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.util; + +import java.util.NoSuchElementException; + +/** + * A set of integers, represented by a bit set + */ +public class BitIntSet implements IntSet { + + /** also accessed in ListIntSet */ + int[] bits; + + /** + * Constructs an instance. + * + * @param max the maximum value of ints in this set. + */ + public BitIntSet(int max) { + bits = Bits.makeBitSet(max); + } + + /** @inheritDoc */ + public void add(int value) { + ensureCapacity(value); + Bits.set(bits, value, true); + } + + /** + * Ensures that the bit set has the capacity to represent the given value. + * + * @param value {@code >= 0;} value to represent + */ + private void ensureCapacity(int value) { + if (value >= Bits.getMax(bits)) { + int[] newBits = Bits.makeBitSet( + Math.max(value + 1, 2 * Bits.getMax(bits))); + System.arraycopy(bits, 0, newBits, 0, bits.length); + bits = newBits; + } + } + + /** @inheritDoc */ + public void remove(int value) { + if (value < Bits.getMax(bits)) { + Bits.set(bits, value, false); + } + } + + /** @inheritDoc */ + public boolean has(int value) { + return (value < Bits.getMax(bits)) && Bits.get(bits, value); + } + + /** @inheritDoc */ + public void merge(IntSet other) { + if (other instanceof BitIntSet) { + BitIntSet o = (BitIntSet) other; + ensureCapacity(Bits.getMax(o.bits) + 1); + Bits.or(bits, o.bits); + } else if (other instanceof ListIntSet) { + ListIntSet o = (ListIntSet) other; + int sz = o.ints.size(); + + if (sz > 0) { + ensureCapacity(o.ints.get(sz - 1)); + } + for (int i = 0; i < o.ints.size(); i++) { + Bits.set(bits, o.ints.get(i), true); + } + } else { + IntIterator iter = other.iterator(); + while (iter.hasNext()) { + add(iter.next()); + } + } + } + + /** @inheritDoc */ + public int elements() { + return Bits.bitCount(bits); + } + + /** @inheritDoc */ + public IntIterator iterator() { + return new IntIterator() { + private int idx = Bits.findFirst(bits, 0); + + /** @inheritDoc */ + public boolean hasNext() { + return idx >= 0; + } + + /** @inheritDoc */ + public int next() { + if (!hasNext()) { + throw new NoSuchElementException(); + } + + int ret = idx; + + idx = Bits.findFirst(bits, idx+1); + + return ret; + } + }; + } + + /** @inheritDoc */ + public String toString() { + StringBuilder sb = new StringBuilder(); + + sb.append('{'); + + boolean first = true; + for (int i = Bits.findFirst(bits, 0) + ; i >= 0 + ; i = Bits.findFirst(bits, i + 1)) { + if (!first) { + sb.append(", "); + } + first = false; + sb.append(i); + } + + sb.append('}'); + + return sb.toString(); + } +} diff --git a/dx/src/com/android/jack/dx/util/Bits.java b/dx/src/com/android/jack/dx/util/Bits.java new file mode 100644 index 00000000..629d5cf5 --- /dev/null +++ b/dx/src/com/android/jack/dx/util/Bits.java @@ -0,0 +1,236 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.util; + +/** + * Utilities for treating {@code int[]}s as bit sets. + */ +public final class Bits { + /** + * This class is uninstantiable. + */ + private Bits() { + // This space intentionally left blank. + } + + /** + * Constructs a bit set to contain bits up to the given index (exclusive). + * + * @param max {@code >= 0;} the maximum bit index (exclusive) + * @return {@code non-null;} an appropriately-constructed instance + */ + public static int[] makeBitSet(int max) { + int size = (max + 0x1f) >> 5; + return new int[size]; + } + + /** + * Gets the maximum index (exclusive) for the given bit set. + * + * @param bits {@code non-null;} bit set in question + * @return {@code >= 0;} the maximum index (exclusive) that may be set + */ + public static int getMax(int[] bits) { + return bits.length * 0x20; + } + + /** + * Gets the value of the bit at the given index. + * + * @param bits {@code non-null;} bit set to operate on + * @param idx {@code >= 0, < getMax(set);} which bit + * @return the value of the indicated bit + */ + public static boolean get(int[] bits, int idx) { + int arrayIdx = idx >> 5; + int bit = 1 << (idx & 0x1f); + return (bits[arrayIdx] & bit) != 0; + } + + /** + * Sets the given bit to the given value. + * + * @param bits {@code non-null;} bit set to operate on + * @param idx {@code >= 0, < getMax(set);} which bit + * @param value the new value for the bit + */ + public static void set(int[] bits, int idx, boolean value) { + int arrayIdx = idx >> 5; + int bit = 1 << (idx & 0x1f); + + if (value) { + bits[arrayIdx] |= bit; + } else { + bits[arrayIdx] &= ~bit; + } + } + + /** + * Sets the given bit to {@code true}. + * + * @param bits {@code non-null;} bit set to operate on + * @param idx {@code >= 0, < getMax(set);} which bit + */ + public static void set(int[] bits, int idx) { + int arrayIdx = idx >> 5; + int bit = 1 << (idx & 0x1f); + bits[arrayIdx] |= bit; + } + + /** + * Sets the given bit to {@code false}. + * + * @param bits {@code non-null;} bit set to operate on + * @param idx {@code >= 0, < getMax(set);} which bit + */ + public static void clear(int[] bits, int idx) { + int arrayIdx = idx >> 5; + int bit = 1 << (idx & 0x1f); + bits[arrayIdx] &= ~bit; + } + + /** + * Returns whether or not the given bit set is empty, that is, whether + * no bit is set to {@code true}. + * + * @param bits {@code non-null;} bit set to operate on + * @return {@code true} iff all bits are {@code false} + */ + public static boolean isEmpty(int[] bits) { + int len = bits.length; + + for (int i = 0; i < len; i++) { + if (bits[i] != 0) { + return false; + } + } + + return true; + } + + /** + * Gets the number of bits set to {@code true} in the given bit set. + * + * @param bits {@code non-null;} bit set to operate on + * @return {@code >= 0;} the bit count (aka population count) of the set + */ + public static int bitCount(int[] bits) { + int len = bits.length; + int count = 0; + + for (int i = 0; i < len; i++) { + count += Integer.bitCount(bits[i]); + } + + return count; + } + + /** + * Returns whether any bits are set to {@code true} in the + * specified range. + * + * @param bits {@code non-null;} bit set to operate on + * @param start {@code >= 0;} index of the first bit in the range (inclusive) + * @param end {@code >= 0;} index of the last bit in the range (exclusive) + * @return {@code true} if any bit is set to {@code true} in + * the indicated range + */ + public static boolean anyInRange(int[] bits, int start, int end) { + int idx = findFirst(bits, start); + return (idx >= 0) && (idx < end); + } + + /** + * Finds the lowest-order bit set at or after the given index in the + * given bit set. + * + * @param bits {@code non-null;} bit set to operate on + * @param idx {@code >= 0;} minimum index to return + * @return {@code >= -1;} lowest-order bit set at or after {@code idx}, + * or {@code -1} if there is no appropriate bit index to return + */ + public static int findFirst(int[] bits, int idx) { + int len = bits.length; + int minBit = idx & 0x1f; + + for (int arrayIdx = idx >> 5; arrayIdx < len; arrayIdx++) { + int word = bits[arrayIdx]; + if (word != 0) { + int bitIdx = findFirst(word, minBit); + if (bitIdx >= 0) { + return (arrayIdx << 5) + bitIdx; + } + } + minBit = 0; + } + + return -1; + } + + /** + * Finds the lowest-order bit set at or after the given index in the + * given {@code int}. + * + * @param value the value in question + * @param idx 0..31 the minimum bit index to return + * @return {@code >= -1;} lowest-order bit set at or after {@code idx}, + * or {@code -1} if there is no appropriate bit index to return + */ + public static int findFirst(int value, int idx) { + value &= ~((1 << idx) - 1); // Mask off too-low bits. + int result = Integer.numberOfTrailingZeros(value); + return (result == 32) ? -1 : result; + } + + /** + * Ors bit array {@code b} into bit array {@code a}. + * {@code a.length} must be greater than or equal to + * {@code b.length}. + * + * @param a {@code non-null;} int array to be ored with other argument. This + * argument is modified. + * @param b {@code non-null;} int array to be ored into {@code a}. This + * argument is not modified. + */ + public static void or(int[] a, int[] b) { + for (int i = 0; i < b.length; i++) { + a[i] |= b[i]; + } + } + + public static String toHuman(int[] bits) { + StringBuilder sb = new StringBuilder(); + + boolean needsComma = false; + + sb.append('{'); + + int bitsLength = 32 * bits.length; + for (int i = 0; i < bitsLength; i++) { + if (Bits.get(bits, i)) { + if (needsComma) { + sb.append(','); + } + needsComma = true; + sb.append(i); + } + } + sb.append('}'); + + return sb.toString(); + } +} diff --git a/dx/src/com/android/jack/dx/util/ByteArray.java b/dx/src/com/android/jack/dx/util/ByteArray.java new file mode 100644 index 00000000..b5ff8001 --- /dev/null +++ b/dx/src/com/android/jack/dx/util/ByteArray.java @@ -0,0 +1,361 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.util; + +import java.io.DataInputStream; +import java.io.IOException; +import java.io.InputStream; + +/** + * Wrapper for a {@code byte[]}, which provides read-only access and + * can "reveal" a partial slice of the underlying array. + * + * Note: Multibyte accessors all use big-endian order. + */ +public final class ByteArray { + /** {@code non-null;} underlying array */ + private final byte[] bytes; + + /** {@code >= 0}; start index of the slice (inclusive) */ + private final int start; + + /** {@code >= 0, <= bytes.length}; size computed as + * {@code end - start} (in the constructor) */ + private final int size; + + /** + * Constructs an instance. + * + * @param bytes {@code non-null;} the underlying array + * @param start {@code >= 0;} start index of the slice (inclusive) + * @param end {@code >= start, <= bytes.length;} end index of + * the slice (exclusive) + */ + public ByteArray(byte[] bytes, int start, int end) { + if (bytes == null) { + throw new NullPointerException("bytes == null"); + } + + if (start < 0) { + throw new IllegalArgumentException("start < 0"); + } + + if (end < start) { + throw new IllegalArgumentException("end < start"); + } + + if (end > bytes.length) { + throw new IllegalArgumentException("end > bytes.length"); + } + + this.bytes = bytes; + this.start = start; + this.size = end - start; + } + + /** + * Constructs an instance from an entire {@code byte[]}. + * + * @param bytes {@code non-null;} the underlying array + */ + public ByteArray(byte[] bytes) { + this(bytes, 0, bytes.length); + } + + /** + * Gets the size of the array, in bytes. + * + * @return {@code >= 0;} the size + */ + public int size() { + return size; + } + + /** + * Returns a slice (that is, a sub-array) of this instance. + * + * @param start {@code >= 0;} start index of the slice (inclusive) + * @param end {@code >= start, <= size();} end index of + * the slice (exclusive) + * @return {@code non-null;} the slice + */ + public ByteArray slice(int start, int end) { + checkOffsets(start, end); + return new ByteArray(bytes, start + this.start, end + this.start); + } + + /** + * Returns the offset into the given array represented by the given + * offset into this instance. + * + * @param offset offset into this instance + * @param bytes {@code non-null;} (alleged) underlying array + * @return corresponding offset into {@code bytes} + * @throws IllegalArgumentException thrown if {@code bytes} is + * not the underlying array of this instance + */ + public int underlyingOffset(int offset, byte[] bytes) { + if (bytes != this.bytes) { + throw new IllegalArgumentException("wrong bytes"); + } + + return start + offset; + } + + /** + * Gets the {@code signed byte} value at a particular offset. + * + * @param off {@code >= 0, < size();} offset to fetch + * @return {@code signed byte} at that offset + */ + public int getByte(int off) { + checkOffsets(off, off + 1); + return getByte0(off); + } + + /** + * Gets the {@code signed short} value at a particular offset. + * + * @param off {@code >= 0, < (size() - 1);} offset to fetch + * @return {@code signed short} at that offset + */ + public int getShort(int off) { + checkOffsets(off, off + 2); + return (getByte0(off) << 8) | getUnsignedByte0(off + 1); + } + + /** + * Gets the {@code signed int} value at a particular offset. + * + * @param off {@code >= 0, < (size() - 3);} offset to fetch + * @return {@code signed int} at that offset + */ + public int getInt(int off) { + checkOffsets(off, off + 4); + return (getByte0(off) << 24) | + (getUnsignedByte0(off + 1) << 16) | + (getUnsignedByte0(off + 2) << 8) | + getUnsignedByte0(off + 3); + } + + /** + * Gets the {@code signed long} value at a particular offset. + * + * @param off {@code >= 0, < (size() - 7);} offset to fetch + * @return {@code signed int} at that offset + */ + public long getLong(int off) { + checkOffsets(off, off + 8); + int part1 = (getByte0(off) << 24) | + (getUnsignedByte0(off + 1) << 16) | + (getUnsignedByte0(off + 2) << 8) | + getUnsignedByte0(off + 3); + int part2 = (getByte0(off + 4) << 24) | + (getUnsignedByte0(off + 5) << 16) | + (getUnsignedByte0(off + 6) << 8) | + getUnsignedByte0(off + 7); + + return (part2 & 0xffffffffL) | ((long) part1) << 32; + } + + /** + * Gets the {@code unsigned byte} value at a particular offset. + * + * @param off {@code >= 0, < size();} offset to fetch + * @return {@code unsigned byte} at that offset + */ + public int getUnsignedByte(int off) { + checkOffsets(off, off + 1); + return getUnsignedByte0(off); + } + + /** + * Gets the {@code unsigned short} value at a particular offset. + * + * @param off {@code >= 0, < (size() - 1);} offset to fetch + * @return {@code unsigned short} at that offset + */ + public int getUnsignedShort(int off) { + checkOffsets(off, off + 2); + return (getUnsignedByte0(off) << 8) | getUnsignedByte0(off + 1); + } + + /** + * Copies the contents of this instance into the given raw + * {@code byte[]} at the given offset. The given array must be + * large enough. + * + * @param out {@code non-null;} array to hold the output + * @param offset {@code non-null;} index into {@code out} for the first + * byte of output + */ + public void getBytes(byte[] out, int offset) { + if ((out.length - offset) < size) { + throw new IndexOutOfBoundsException("(out.length - offset) < " + + "size()"); + } + + System.arraycopy(bytes, start, out, offset, size); + } + + /** + * Checks a range of offsets for validity, throwing if invalid. + * + * @param s start offset (inclusive) + * @param e end offset (exclusive) + */ + private void checkOffsets(int s, int e) { + if ((s < 0) || (e < s) || (e > size)) { + throw new IllegalArgumentException("bad range: " + s + ".." + e + + "; actual size " + size); + } + } + + /** + * Gets the {@code signed byte} value at the given offset, + * without doing any argument checking. + * + * @param off offset to fetch + * @return byte at that offset + */ + private int getByte0(int off) { + return bytes[start + off]; + } + + /** + * Gets the {@code unsigned byte} value at the given offset, + * without doing any argument checking. + * + * @param off offset to fetch + * @return byte at that offset + */ + private int getUnsignedByte0(int off) { + return bytes[start + off] & 0xff; + } + + /** + * Gets a {@code DataInputStream} that reads from this instance, + * with the cursor starting at the beginning of this instance's data. + * Note: The returned instance may be cast to {@link #GetCursor} + * if needed. + * + * @return {@code non-null;} an appropriately-constructed + * {@code DataInputStream} instance + */ + public MyDataInputStream makeDataInputStream() { + return new MyDataInputStream(makeInputStream()); + } + + /** + * Gets a {@code InputStream} that reads from this instance, + * with the cursor starting at the beginning of this instance's data. + * Note: The returned instance may be cast to {@link #GetCursor} + * if needed. + * + * @return {@code non-null;} an appropriately-constructed + * {@code InputStream} instancex + */ + public MyInputStream makeInputStream() { + return new MyInputStream(); + } + + /** + * Helper interface that allows one to get the cursor (of a stream). + */ + public interface GetCursor { + /** + * Gets the current cursor. + * + * @return {@code 0..size();} the cursor + */ + public int getCursor(); + } + + /** + * Helper class for {@link #makeInputStream}, which implements the + * stream functionality. + */ + public class MyInputStream extends InputStream { + /** 0..size; the cursor */ + private int cursor; + + /** 0..size; the mark */ + private int mark; + + public MyInputStream() { + cursor = 0; + mark = 0; + } + + public int read() throws IOException { + if (cursor >= size) { + return -1; + } + + int result = getUnsignedByte0(cursor); + cursor++; + return result; + } + + public int read(byte[] arr, int offset, int length) { + if ((offset + length) > arr.length) { + length = arr.length - offset; + } + + int maxLength = size - cursor; + if (length > maxLength) { + length = maxLength; + } + + System.arraycopy(bytes, cursor + start, arr, offset, length); + cursor += length; + return length; + } + + public int available() { + return size - cursor; + } + + public void mark(int reserve) { + mark = cursor; + } + + public void reset() { + cursor = mark; + } + + public boolean markSupported() { + return true; + } + } + + /** + * Helper class for {@link #makeDataInputStream}. This is used + * simply so that the cursor of a wrapped {@link #MyInputStream} + * instance may be easily determined. + */ + public static class MyDataInputStream extends DataInputStream { + /** {@code non-null;} the underlying {@link #MyInputStream} */ + private final MyInputStream wrapped; + + public MyDataInputStream(MyInputStream wrapped) { + super(wrapped); + + this.wrapped = wrapped; + } + } +} diff --git a/dx/src/com/android/jack/dx/util/ByteArrayAnnotatedOutput.java b/dx/src/com/android/jack/dx/util/ByteArrayAnnotatedOutput.java new file mode 100644 index 00000000..09174e01 --- /dev/null +++ b/dx/src/com/android/jack/dx/util/ByteArrayAnnotatedOutput.java @@ -0,0 +1,632 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.util; + +import java.io.IOException; +import java.io.Writer; +import java.util.ArrayList; + +/** + * Implementation of {@link AnnotatedOutput} which stores the written data + * into a {@code byte[]}. + * + *

      Note: As per the {@link Output} interface, multi-byte + * writes all use little-endian order.

      + */ +public final class ByteArrayAnnotatedOutput + implements AnnotatedOutput, ByteOutput { + /** default size for stretchy instances */ + private static final int DEFAULT_SIZE = 1000; + + /** + * whether the instance is stretchy, that is, whether its array + * may be resized to increase capacity + */ + private final boolean stretchy; + + /** {@code non-null;} the data itself */ + private byte[] data; + + /** {@code >= 0;} current output cursor */ + private int cursor; + + /** whether annotations are to be verbose */ + private boolean verbose; + + /** + * {@code null-ok;} list of annotations, or {@code null} if this instance + * isn't keeping them + */ + private ArrayList annotations; + + /** {@code >= 40 (if used);} the desired maximum annotation width */ + private int annotationWidth; + + /** + * {@code >= 8 (if used);} the number of bytes of hex output to use + * in annotations + */ + private int hexCols; + + /** + * Constructs an instance with a fixed maximum size. Note that the + * given array is the only one that will be used to store data. In + * particular, no reallocation will occur in order to expand the + * capacity of the resulting instance. Also, the constructed + * instance does not keep annotations by default. + * + * @param data {@code non-null;} data array to use for output + */ + public ByteArrayAnnotatedOutput(byte[] data) { + this(data, false); + } + + /** + * Constructs a "stretchy" instance. The underlying array may be + * reallocated. The constructed instance does not keep annotations + * by default. + */ + public ByteArrayAnnotatedOutput() { + this(DEFAULT_SIZE); + } + + /** + * Constructs a "stretchy" instance with initial size {@code size}. The + * underlying array may be reallocated. The constructed instance does not + * keep annotations by default. + */ + public ByteArrayAnnotatedOutput(int size) { + this(new byte[size], true); + } + + /** + * Internal constructor. + * + * @param data {@code non-null;} data array to use for output + * @param stretchy whether the instance is to be stretchy + */ + private ByteArrayAnnotatedOutput(byte[] data, boolean stretchy) { + if (data == null) { + throw new NullPointerException("data == null"); + } + + this.stretchy = stretchy; + this.data = data; + this.cursor = 0; + this.verbose = false; + this.annotations = null; + this.annotationWidth = 0; + this.hexCols = 0; + } + + /** + * Gets the underlying {@code byte[]} of this instance, which + * may be larger than the number of bytes written + * + * @see #toByteArray + * + * @return {@code non-null;} the {@code byte[]} + */ + public byte[] getArray() { + return data; + } + + /** + * Constructs and returns a new {@code byte[]} that contains + * the written contents exactly (that is, with no extra unwritten + * bytes at the end). + * + * @see #getArray + * + * @return {@code non-null;} an appropriately-constructed array + */ + public byte[] toByteArray() { + byte[] result = new byte[cursor]; + System.arraycopy(data, 0, result, 0, cursor); + return result; + } + + /** {@inheritDoc} */ + public int getCursor() { + return cursor; + } + + /** {@inheritDoc} */ + public void assertCursor(int expectedCursor) { + if (cursor != expectedCursor) { + throw new ExceptionWithContext("expected cursor " + + expectedCursor + "; actual value: " + cursor); + } + } + + /** {@inheritDoc} */ + public void writeByte(int value) { + int writeAt = cursor; + int end = writeAt + 1; + + if (stretchy) { + ensureCapacity(end); + } else if (end > data.length) { + throwBounds(); + return; + } + + data[writeAt] = (byte) value; + cursor = end; + } + + /** {@inheritDoc} */ + public void writeShort(int value) { + int writeAt = cursor; + int end = writeAt + 2; + + if (stretchy) { + ensureCapacity(end); + } else if (end > data.length) { + throwBounds(); + return; + } + + data[writeAt] = (byte) value; + data[writeAt + 1] = (byte) (value >> 8); + cursor = end; + } + + /** {@inheritDoc} */ + public void writeInt(int value) { + int writeAt = cursor; + int end = writeAt + 4; + + if (stretchy) { + ensureCapacity(end); + } else if (end > data.length) { + throwBounds(); + return; + } + + data[writeAt] = (byte) value; + data[writeAt + 1] = (byte) (value >> 8); + data[writeAt + 2] = (byte) (value >> 16); + data[writeAt + 3] = (byte) (value >> 24); + cursor = end; + } + + /** {@inheritDoc} */ + public void writeLong(long value) { + int writeAt = cursor; + int end = writeAt + 8; + + if (stretchy) { + ensureCapacity(end); + } else if (end > data.length) { + throwBounds(); + return; + } + + int half = (int) value; + data[writeAt] = (byte) half; + data[writeAt + 1] = (byte) (half >> 8); + data[writeAt + 2] = (byte) (half >> 16); + data[writeAt + 3] = (byte) (half >> 24); + + half = (int) (value >> 32); + data[writeAt + 4] = (byte) half; + data[writeAt + 5] = (byte) (half >> 8); + data[writeAt + 6] = (byte) (half >> 16); + data[writeAt + 7] = (byte) (half >> 24); + + cursor = end; + } + + /** {@inheritDoc} */ + public int writeUleb128(int value) { + if (stretchy) { + ensureCapacity(cursor + 5); // pessimistic + } + int cursorBefore = cursor; + Leb128Utils.writeUnsignedLeb128(this, value); + return (cursor - cursorBefore); + } + + /** {@inheritDoc} */ + public int writeSleb128(int value) { + if (stretchy) { + ensureCapacity(cursor + 5); // pessimistic + } + int cursorBefore = cursor; + Leb128Utils.writeSignedLeb128(this, value); + return (cursor - cursorBefore); + } + + /** {@inheritDoc} */ + public void write(ByteArray bytes) { + int blen = bytes.size(); + int writeAt = cursor; + int end = writeAt + blen; + + if (stretchy) { + ensureCapacity(end); + } else if (end > data.length) { + throwBounds(); + return; + } + + bytes.getBytes(data, writeAt); + cursor = end; + } + + /** {@inheritDoc} */ + public void write(byte[] bytes, int offset, int length) { + int writeAt = cursor; + int end = writeAt + length; + int bytesEnd = offset + length; + + // twos-complement math trick: ((x < 0) || (y < 0)) <=> ((x|y) < 0) + if (((offset | length | end) < 0) || (bytesEnd > bytes.length)) { + throw new IndexOutOfBoundsException("bytes.length " + + bytes.length + "; " + + offset + "..!" + end); + } + + if (stretchy) { + ensureCapacity(end); + } else if (end > data.length) { + throwBounds(); + return; + } + + System.arraycopy(bytes, offset, data, writeAt, length); + cursor = end; + } + + /** {@inheritDoc} */ + public void write(byte[] bytes) { + write(bytes, 0, bytes.length); + } + + /** {@inheritDoc} */ + public void writeZeroes(int count) { + if (count < 0) { + throw new IllegalArgumentException("count < 0"); + } + + int end = cursor + count; + + if (stretchy) { + ensureCapacity(end); + } else if (end > data.length) { + throwBounds(); + return; + } + + /* + * There is no need to actually write zeroes, since the array is + * already preinitialized with zeroes. + */ + + cursor = end; + } + + /** {@inheritDoc} */ + public void alignTo(int alignment) { + int mask = alignment - 1; + + if ((alignment < 0) || ((mask & alignment) != 0)) { + throw new IllegalArgumentException("bogus alignment"); + } + + int end = (cursor + mask) & ~mask; + + if (stretchy) { + ensureCapacity(end); + } else if (end > data.length) { + throwBounds(); + return; + } + + /* + * There is no need to actually write zeroes, since the array is + * already preinitialized with zeroes. + */ + + cursor = end; + } + + /** {@inheritDoc} */ + public boolean annotates() { + return (annotations != null); + } + + /** {@inheritDoc} */ + public boolean isVerbose() { + return verbose; + } + + /** {@inheritDoc} */ + public void annotate(String msg) { + if (annotations == null) { + return; + } + + endAnnotation(); + annotations.add(new Annotation(cursor, msg)); + } + + /** {@inheritDoc} */ + public void annotate(int amt, String msg) { + if (annotations == null) { + return; + } + + endAnnotation(); + + int asz = annotations.size(); + int lastEnd = (asz == 0) ? 0 : annotations.get(asz - 1).getEnd(); + int startAt; + + if (lastEnd <= cursor) { + startAt = cursor; + } else { + startAt = lastEnd; + } + + annotations.add(new Annotation(startAt, startAt + amt, msg)); + } + + /** {@inheritDoc} */ + public void endAnnotation() { + if (annotations == null) { + return; + } + + int sz = annotations.size(); + + if (sz != 0) { + annotations.get(sz - 1).setEndIfUnset(cursor); + } + } + + /** {@inheritDoc} */ + public int getAnnotationWidth() { + int leftWidth = 8 + (hexCols * 2) + (hexCols / 2); + + return annotationWidth - leftWidth; + } + + /** + * Indicates that this instance should keep annotations. This method may + * be called only once per instance, and only before any data has been + * written to the it. + * + * @param annotationWidth {@code >= 40;} the desired maximum annotation width + * @param verbose whether or not to indicate verbose annotations + */ + public void enableAnnotations(int annotationWidth, boolean verbose) { + if ((annotations != null) || (cursor != 0)) { + throw new RuntimeException("cannot enable annotations"); + } + + if (annotationWidth < 40) { + throw new IllegalArgumentException("annotationWidth < 40"); + } + + int hexCols = (((annotationWidth - 7) / 15) + 1) & ~1; + if (hexCols < 6) { + hexCols = 6; + } else if (hexCols > 10) { + hexCols = 10; + } + + this.annotations = new ArrayList(1000); + this.annotationWidth = annotationWidth; + this.hexCols = hexCols; + this.verbose = verbose; + } + + /** + * Finishes up annotation processing. This closes off any open + * annotations and removes annotations that don't refer to written + * data. + */ + public void finishAnnotating() { + // Close off the final annotation, if any. + endAnnotation(); + + // Remove annotations that refer to unwritten data. + if (annotations != null) { + int asz = annotations.size(); + while (asz > 0) { + Annotation last = annotations.get(asz - 1); + if (last.getStart() > cursor) { + annotations.remove(asz - 1); + asz--; + } else if (last.getEnd() > cursor) { + last.setEnd(cursor); + break; + } else { + break; + } + } + } + } + + /** + * Writes the annotated content of this instance to the given writer. + * + * @param out {@code non-null;} where to write to + */ + public void writeAnnotationsTo(Writer out) throws IOException { + int width2 = getAnnotationWidth(); + int width1 = annotationWidth - width2 - 1; + + TwoColumnOutput twoc = new TwoColumnOutput(out, width1, width2, "|"); + Writer left = twoc.getLeft(); + Writer right = twoc.getRight(); + int leftAt = 0; // left-hand byte output cursor + int rightAt = 0; // right-hand annotation index + int rightSz = annotations.size(); + + while ((leftAt < cursor) && (rightAt < rightSz)) { + Annotation a = annotations.get(rightAt); + int start = a.getStart(); + int end; + String text; + + if (leftAt < start) { + // This is an area with no annotation. + end = start; + start = leftAt; + text = ""; + } else { + // This is an area with an annotation. + end = a.getEnd(); + text = a.getText(); + rightAt++; + } + + left.write(Hex.dump(data, start, end - start, start, hexCols, 6)); + right.write(text); + twoc.flush(); + leftAt = end; + } + + if (leftAt < cursor) { + // There is unannotated output at the end. + left.write(Hex.dump(data, leftAt, cursor - leftAt, leftAt, + hexCols, 6)); + } + + while (rightAt < rightSz) { + // There are zero-byte annotations at the end. + right.write(annotations.get(rightAt).getText()); + rightAt++; + } + + twoc.flush(); + } + + /** + * Throws the excpetion for when an attempt is made to write past the + * end of the instance. + */ + private static void throwBounds() { + throw new IndexOutOfBoundsException("attempt to write past the end"); + } + + /** + * Reallocates the underlying array if necessary. Calls to this method + * should be guarded by a test of {@link #stretchy}. + * + * @param desiredSize {@code >= 0;} the desired minimum total size of the array + */ + private void ensureCapacity(int desiredSize) { + if (data.length < desiredSize) { + byte[] newData = new byte[desiredSize * 2 + 1000]; + System.arraycopy(data, 0, newData, 0, cursor); + data = newData; + } + } + + /** + * Annotation on output. + */ + private static class Annotation { + /** {@code >= 0;} start of annotated range (inclusive) */ + private final int start; + + /** + * {@code >= 0;} end of annotated range (exclusive); + * {@code Integer.MAX_VALUE} if unclosed + */ + private int end; + + /** {@code non-null;} annotation text */ + private final String text; + + /** + * Constructs an instance. + * + * @param start {@code >= 0;} start of annotated range + * @param end {@code >= start;} end of annotated range (exclusive) or + * {@code Integer.MAX_VALUE} if unclosed + * @param text {@code non-null;} annotation text + */ + public Annotation(int start, int end, String text) { + this.start = start; + this.end = end; + this.text = text; + } + + /** + * Constructs an instance. It is initally unclosed. + * + * @param start {@code >= 0;} start of annotated range + * @param text {@code non-null;} annotation text + */ + public Annotation(int start, String text) { + this(start, Integer.MAX_VALUE, text); + } + + /** + * Sets the end as given, but only if the instance is unclosed; + * otherwise, do nothing. + * + * @param end {@code >= start;} the end + */ + public void setEndIfUnset(int end) { + if (this.end == Integer.MAX_VALUE) { + this.end = end; + } + } + + /** + * Sets the end as given. + * + * @param end {@code >= start;} the end + */ + public void setEnd(int end) { + this.end = end; + } + + /** + * Gets the start. + * + * @return the start + */ + public int getStart() { + return start; + } + + /** + * Gets the end. + * + * @return the end + */ + public int getEnd() { + return end; + } + + /** + * Gets the text. + * + * @return {@code non-null;} the text + */ + public String getText() { + return text; + } + } +} diff --git a/dx/src/com/android/jack/dx/util/ByteArrayByteInput.java b/dx/src/com/android/jack/dx/util/ByteArrayByteInput.java new file mode 100644 index 00000000..8908997a --- /dev/null +++ b/dx/src/com/android/jack/dx/util/ByteArrayByteInput.java @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.util; + +public final class ByteArrayByteInput implements ByteInput { + + private final byte[] bytes; + private int position; + + public ByteArrayByteInput(byte... bytes) { + this.bytes = bytes; + } + + @Override public byte readByte() { + return bytes[position++]; + } +} diff --git a/dx/src/com/android/jack/dx/util/ByteInput.java b/dx/src/com/android/jack/dx/util/ByteInput.java new file mode 100644 index 00000000..00246e6c --- /dev/null +++ b/dx/src/com/android/jack/dx/util/ByteInput.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.util; + +/** + * A byte source. + */ +public interface ByteInput { + + /** + * Returns a byte. + * + * @throws IndexOutOfBoundsException if all bytes have been read. + */ + byte readByte(); +} diff --git a/dx/src/com/android/jack/dx/util/ByteOutput.java b/dx/src/com/android/jack/dx/util/ByteOutput.java new file mode 100644 index 00000000..1a2b8f4b --- /dev/null +++ b/dx/src/com/android/jack/dx/util/ByteOutput.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.util; + +/** + * A byte sink. + */ +public interface ByteOutput { + + /** + * Writes a byte. + * + * @throws IndexOutOfBoundsException if all bytes have been written. + */ + void writeByte(int i); +} diff --git a/dx/src/com/android/jack/dx/util/DexException.java b/dx/src/com/android/jack/dx/util/DexException.java new file mode 100644 index 00000000..c06c9f35 --- /dev/null +++ b/dx/src/com/android/jack/dx/util/DexException.java @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.util; + +/** + * Thrown when there's a format problem reading, writing, or generally + * processing a dex file. + */ +public final class DexException extends ExceptionWithContext { + public DexException(String message) { + super(message); + } + + public DexException(Throwable cause) { + super(cause); + } +} diff --git a/dx/src/com/android/jack/dx/util/ExceptionWithContext.java b/dx/src/com/android/jack/dx/util/ExceptionWithContext.java new file mode 100644 index 00000000..8951eaf2 --- /dev/null +++ b/dx/src/com/android/jack/dx/util/ExceptionWithContext.java @@ -0,0 +1,149 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.util; + +import java.io.PrintStream; +import java.io.PrintWriter; + +/** + * Exception which carries around structured context. + */ +public class ExceptionWithContext + extends RuntimeException { + /** {@code non-null;} human-oriented context of the exception */ + private StringBuffer context; + + /** + * Augments the given exception with the given context, and return the + * result. The result is either the given exception if it was an + * {@link ExceptionWithContext}, or a newly-constructed exception if it + * was not. + * + * @param ex {@code non-null;} the exception to augment + * @param str {@code non-null;} context to add + * @return {@code non-null;} an appropriate instance + */ + public static ExceptionWithContext withContext(Throwable ex, String str) { + ExceptionWithContext ewc; + + if (ex instanceof ExceptionWithContext) { + ewc = (ExceptionWithContext) ex; + } else { + ewc = new ExceptionWithContext(ex); + } + + ewc.addContext(str); + return ewc; + } + + /** + * Constructs an instance. + * + * @param message human-oriented message + */ + public ExceptionWithContext(String message) { + this(message, null); + } + + /** + * Constructs an instance. + * + * @param cause {@code null-ok;} exception that caused this one + */ + public ExceptionWithContext(Throwable cause) { + this(null, cause); + } + + /** + * Constructs an instance. + * + * @param message human-oriented message + * @param cause {@code null-ok;} exception that caused this one + */ + public ExceptionWithContext(String message, Throwable cause) { + super((message != null) ? message : + (cause != null) ? cause.getMessage() : null, + cause); + + if (cause instanceof ExceptionWithContext) { + String ctx = ((ExceptionWithContext) cause).context.toString(); + context = new StringBuffer(ctx.length() + 200); + context.append(ctx); + } else { + context = new StringBuffer(200); + } + } + + /** {@inheritDoc} */ + @Override + public void printStackTrace(PrintStream out) { + super.printStackTrace(out); + out.println(context); + } + + /** {@inheritDoc} */ + @Override + public void printStackTrace(PrintWriter out) { + super.printStackTrace(out); + out.println(context); + } + + /** + * Adds a line of context to this instance. + * + * @param str {@code non-null;} new context + */ + public void addContext(String str) { + if (str == null) { + throw new NullPointerException("str == null"); + } + + context.append(str); + if (!str.endsWith("\n")) { + context.append('\n'); + } + } + + /** + * Gets the context. + * + * @return {@code non-null;} the context + */ + public String getContext() { + return context.toString(); + } + + /** + * Prints the message and context. + * + * @param out {@code non-null;} where to print to + */ + public void printContext(PrintStream out) { + out.println(getMessage()); + out.print(context); + } + + /** + * Prints the message and context. + * + * @param out {@code non-null;} where to print to + */ + public void printContext(PrintWriter out) { + out.println(getMessage()); + out.print(context); + } +} diff --git a/dx/src/com/android/jack/dx/util/FileUtils.java b/dx/src/com/android/jack/dx/util/FileUtils.java new file mode 100644 index 00000000..4c71d522 --- /dev/null +++ b/dx/src/com/android/jack/dx/util/FileUtils.java @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.util; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; + +/** + * File I/O utilities. + */ +public final class FileUtils { + /** + * This class is uninstantiable. + */ + private FileUtils() { + // This space intentionally left blank. + } + + /** + * Reads the named file, translating {@link IOException} to a + * {@link RuntimeException} of some sort. + * + * @param fileName {@code non-null;} name of the file to read + * @return {@code non-null;} contents of the file + */ + public static byte[] readFile(String fileName) { + File file = new File(fileName); + return readFile(file); + } + + /** + * Reads the given file, translating {@link IOException} to a + * {@link RuntimeException} of some sort. + * + * @param file {@code non-null;} the file to read + * @return {@code non-null;} contents of the file + */ + public static byte[] readFile(File file) { + if (!file.exists()) { + throw new RuntimeException(file + ": file not found"); + } + + if (!file.isFile()) { + throw new RuntimeException(file + ": not a file"); + } + + if (!file.canRead()) { + throw new RuntimeException(file + ": file not readable"); + } + + long longLength = file.length(); + int length = (int) longLength; + if (length != longLength) { + throw new RuntimeException(file + ": file too long"); + } + + byte[] result = new byte[length]; + + try { + FileInputStream in = new FileInputStream(file); + int at = 0; + while (length > 0) { + int amt = in.read(result, at, length); + if (amt == -1) { + throw new RuntimeException(file + ": unexpected EOF"); + } + at += amt; + length -= amt; + } + in.close(); + } catch (IOException ex) { + throw new RuntimeException(file + ": trouble reading", ex); + } + + return result; + } + + /** + * Returns true if {@code fileName} names a .zip, .jar, or .apk. + */ + public static boolean hasArchiveSuffix(String fileName) { + return fileName.endsWith(".zip") + || fileName.endsWith(".jar") + || fileName.endsWith(".apk"); + } +} diff --git a/dx/src/com/android/jack/dx/util/FixedSizeList.java b/dx/src/com/android/jack/dx/util/FixedSizeList.java new file mode 100644 index 00000000..a8badf03 --- /dev/null +++ b/dx/src/com/android/jack/dx/util/FixedSizeList.java @@ -0,0 +1,276 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.util; + +import java.util.Arrays; + +/** + * Simple (mostly) fixed-size list of objects, which may be made immutable. + */ +public class FixedSizeList + extends MutabilityControl implements ToHuman { + /** {@code non-null;} array of elements */ + private Object[] arr; + + /** + * Constructs an instance. All indices initially contain {@code null}. + * + * @param size the size of the list + */ + public FixedSizeList(int size) { + super(size != 0); + + try { + arr = new Object[size]; + } catch (NegativeArraySizeException ex) { + // Translate the exception. + throw new IllegalArgumentException("size < 0"); + } + } + + /** {@inheritDoc} */ + @Override + public boolean equals(Object other) { + if (this == other) { + // Easy out. + return true; + } + + if ((other == null) || (getClass() != other.getClass())) { + // Another easy out. + return false; + } + + FixedSizeList list = (FixedSizeList) other; + return Arrays.equals(arr, list.arr); + } + + /** {@inheritDoc} */ + @Override + public int hashCode() { + return Arrays.hashCode(arr); + } + + /** {@inheritDoc} */ + @Override + public String toString() { + String name = getClass().getName(); + + return toString0(name.substring(name.lastIndexOf('.') + 1) + '{', + ", ", + "}", + false); + } + + /** + * {@inheritDoc} + * + * This method will only work if every element of the list + * implements {@link ToHuman}. + */ + public String toHuman() { + String name = getClass().getName(); + + return toString0(name.substring(name.lastIndexOf('.') + 1) + '{', + ", ", + "}", + true); + } + + /** + * Gets a customized string form for this instance. + * + * @param prefix {@code null-ok;} prefix for the start of the result + * @param separator {@code null-ok;} separator to insert between each item + * @param suffix {@code null-ok;} suffix for the end of the result + * @return {@code non-null;} the custom string + */ + public String toString(String prefix, String separator, String suffix) { + return toString0(prefix, separator, suffix, false); + } + + /** + * Gets a customized human string for this instance. This method will + * only work if every element of the list implements {@link + * ToHuman}. + * + * @param prefix {@code null-ok;} prefix for the start of the result + * @param separator {@code null-ok;} separator to insert between each item + * @param suffix {@code null-ok;} suffix for the end of the result + * @return {@code non-null;} the custom string + */ + public String toHuman(String prefix, String separator, String suffix) { + return toString0(prefix, separator, suffix, true); + } + + /** + * Gets the number of elements in this list. + */ + public final int size() { + return arr.length; + } + + /** + * Shrinks this instance to fit, by removing any unset + * ({@code null}) elements, leaving the remaining elements in + * their original order. + */ + public void shrinkToFit() { + int sz = arr.length; + int newSz = 0; + + for (int i = 0; i < sz; i++) { + if (arr[i] != null) { + newSz++; + } + } + + if (sz == newSz) { + return; + } + + throwIfImmutable(); + + Object[] newa = new Object[newSz]; + int at = 0; + + for (int i = 0; i < sz; i++) { + Object one = arr[i]; + if (one != null) { + newa[at] = one; + at++; + } + } + + arr = newa; + if (newSz == 0) { + setImmutable(); + } + } + + /** + * Gets the indicated element. It is an error to call this with the + * index for an element which was never set; if you do that, this + * will throw {@code NullPointerException}. This method is + * protected so that subclasses may offer a safe type-checked + * public interface to their clients. + * + * @param n {@code >= 0, < size();} which element + * @return {@code non-null;} the indicated element + */ + protected final Object get0(int n) { + try { + Object result = arr[n]; + + if (result == null) { + throw new NullPointerException("unset: " + n); + } + + return result; + } catch (ArrayIndexOutOfBoundsException ex) { + // Translate the exception. + return throwIndex(n); + } + } + + /** + * Gets the indicated element, allowing {@code null}s to be + * returned. This method is protected so that subclasses may + * (optionally) offer a safe type-checked public interface to + * their clients. + * + * @param n {@code >= 0, < size();} which element + * @return {@code null-ok;} the indicated element + */ + protected final Object getOrNull0(int n) { + return arr[n]; + } + + /** + * Sets the element at the given index, but without doing any type + * checks on the element. This method is protected so that + * subclasses may offer a safe type-checked public interface to + * their clients. + * + * @param n {@code >= 0, < size();} which element + * @param obj {@code null-ok;} the value to store + */ + protected final void set0(int n, Object obj) { + throwIfImmutable(); + + try { + arr[n] = obj; + } catch (ArrayIndexOutOfBoundsException ex) { + // Translate the exception. + throwIndex(n); + } + } + + /** + * Throws the appropriate exception for the given index value. + * + * @param n the index value + * @return never + * @throws IndexOutOfBoundsException always thrown + */ + private Object throwIndex(int n) { + if (n < 0) { + throw new IndexOutOfBoundsException("n < 0"); + } + + throw new IndexOutOfBoundsException("n >= size()"); + } + + /** + * Helper for {@link #toString} and {@link #toHuman}, which both of + * those call to pretty much do everything. + * + * @param prefix {@code null-ok;} prefix for the start of the result + * @param separator {@code null-ok;} separator to insert between each item + * @param suffix {@code null-ok;} suffix for the end of the result + * @param human whether the output is to be human + * @return {@code non-null;} the custom string + */ + private String toString0(String prefix, String separator, String suffix, + boolean human) { + int len = arr.length; + StringBuffer sb = new StringBuffer(len * 10 + 10); + + if (prefix != null) { + sb.append(prefix); + } + + for (int i = 0; i < len; i++) { + if ((i != 0) && (separator != null)) { + sb.append(separator); + } + + if (human) { + sb.append(((ToHuman) arr[i]).toHuman()); + } else { + sb.append(arr[i]); + } + } + + if (suffix != null) { + sb.append(suffix); + } + + return sb.toString(); + } + +} diff --git a/dx/src/com/android/jack/dx/util/Hex.java b/dx/src/com/android/jack/dx/util/Hex.java new file mode 100644 index 00000000..e928265c --- /dev/null +++ b/dx/src/com/android/jack/dx/util/Hex.java @@ -0,0 +1,303 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.util; + +/** + * Utilities for formatting numbers as hexadecimal. + */ +public final class Hex { + /** + * This class is uninstantiable. + */ + private Hex() { + // This space intentionally left blank. + } + + /** + * Formats a {@code long} as an 8-byte unsigned hex value. + * + * @param v value to format + * @return {@code non-null;} formatted form + */ + public static String u8(long v) { + char[] result = new char[16]; + for (int i = 0; i < 16; i++) { + result[15 - i] = Character.forDigit((int) v & 0x0f, 16); + v >>= 4; + } + + return new String(result); + } + + /** + * Formats an {@code int} as a 4-byte unsigned hex value. + * + * @param v value to format + * @return {@code non-null;} formatted form + */ + public static String u4(int v) { + char[] result = new char[8]; + for (int i = 0; i < 8; i++) { + result[7 - i] = Character.forDigit(v & 0x0f, 16); + v >>= 4; + } + + return new String(result); + } + + /** + * Formats an {@code int} as a 3-byte unsigned hex value. + * + * @param v value to format + * @return {@code non-null;} formatted form + */ + public static String u3(int v) { + char[] result = new char[6]; + for (int i = 0; i < 6; i++) { + result[5 - i] = Character.forDigit(v & 0x0f, 16); + v >>= 4; + } + + return new String(result); + } + + /** + * Formats an {@code int} as a 2-byte unsigned hex value. + * + * @param v value to format + * @return {@code non-null;} formatted form + */ + public static String u2(int v) { + char[] result = new char[4]; + for (int i = 0; i < 4; i++) { + result[3 - i] = Character.forDigit(v & 0x0f, 16); + v >>= 4; + } + + return new String(result); + } + + /** + * Formats an {@code int} as either a 2-byte unsigned hex value + * (if the value is small enough) or a 4-byte unsigned hex value (if + * not). + * + * @param v value to format + * @return {@code non-null;} formatted form + */ + public static String u2or4(int v) { + if (v == (char) v) { + return u2(v); + } else { + return u4(v); + } + } + + /** + * Formats an {@code int} as a 1-byte unsigned hex value. + * + * @param v value to format + * @return {@code non-null;} formatted form + */ + public static String u1(int v) { + char[] result = new char[2]; + for (int i = 0; i < 2; i++) { + result[1 - i] = Character.forDigit(v & 0x0f, 16); + v >>= 4; + } + + return new String(result); + } + + /** + * Formats an {@code int} as a 4-bit unsigned hex nibble. + * + * @param v value to format + * @return {@code non-null;} formatted form + */ + public static String uNibble(int v) { + char[] result = new char[1]; + + result[0] = Character.forDigit(v & 0x0f, 16); + return new String(result); + } + + /** + * Formats a {@code long} as an 8-byte signed hex value. + * + * @param v value to format + * @return {@code non-null;} formatted form + */ + public static String s8(long v) { + char[] result = new char[17]; + + if (v < 0) { + result[0] = '-'; + v = -v; + } else { + result[0] = '+'; + } + + for (int i = 0; i < 16; i++) { + result[16 - i] = Character.forDigit((int) v & 0x0f, 16); + v >>= 4; + } + + return new String(result); + } + + /** + * Formats an {@code int} as a 4-byte signed hex value. + * + * @param v value to format + * @return {@code non-null;} formatted form + */ + public static String s4(int v) { + char[] result = new char[9]; + + if (v < 0) { + result[0] = '-'; + v = -v; + } else { + result[0] = '+'; + } + + for (int i = 0; i < 8; i++) { + result[8 - i] = Character.forDigit(v & 0x0f, 16); + v >>= 4; + } + + return new String(result); + } + + /** + * Formats an {@code int} as a 2-byte signed hex value. + * + * @param v value to format + * @return {@code non-null;} formatted form + */ + public static String s2(int v) { + char[] result = new char[5]; + + if (v < 0) { + result[0] = '-'; + v = -v; + } else { + result[0] = '+'; + } + + for (int i = 0; i < 4; i++) { + result[4 - i] = Character.forDigit(v & 0x0f, 16); + v >>= 4; + } + + return new String(result); + } + + /** + * Formats an {@code int} as a 1-byte signed hex value. + * + * @param v value to format + * @return {@code non-null;} formatted form + */ + public static String s1(int v) { + char[] result = new char[3]; + + if (v < 0) { + result[0] = '-'; + v = -v; + } else { + result[0] = '+'; + } + + for (int i = 0; i < 2; i++) { + result[2 - i] = Character.forDigit(v & 0x0f, 16); + v >>= 4; + } + + return new String(result); + } + + /** + * Formats a hex dump of a portion of a {@code byte[]}. The result + * is always newline-terminated, unless the passed-in length was zero, + * in which case the result is always the empty string ({@code ""}). + * + * @param arr {@code non-null;} array to format + * @param offset {@code >= 0;} offset to the part to dump + * @param length {@code >= 0;} number of bytes to dump + * @param outOffset {@code >= 0;} first output offset to print + * @param bpl {@code >= 0;} number of bytes of output per line + * @param addressLength {@code {2,4,6,8};} number of characters for each address + * header + * @return {@code non-null;} a string of the dump + */ + public static String dump(byte[] arr, int offset, int length, + int outOffset, int bpl, int addressLength) { + int end = offset + length; + + // twos-complement math trick: ((x < 0) || (y < 0)) <=> ((x|y) < 0) + if (((offset | length | end) < 0) || (end > arr.length)) { + throw new IndexOutOfBoundsException("arr.length " + + arr.length + "; " + + offset + "..!" + end); + } + + if (outOffset < 0) { + throw new IllegalArgumentException("outOffset < 0"); + } + + if (length == 0) { + return ""; + } + + StringBuffer sb = new StringBuffer(length * 4 + 6); + boolean bol = true; + int col = 0; + + while (length > 0) { + if (col == 0) { + String astr; + switch (addressLength) { + case 2: astr = Hex.u1(outOffset); break; + case 4: astr = Hex.u2(outOffset); break; + case 6: astr = Hex.u3(outOffset); break; + default: astr = Hex.u4(outOffset); break; + } + sb.append(astr); + sb.append(": "); + } else if ((col & 1) == 0) { + sb.append(' '); + } + sb.append(Hex.u1(arr[offset])); + outOffset++; + offset++; + col++; + if (col == bpl) { + sb.append('\n'); + col = 0; + } + length--; + } + + if (col != 0) { + sb.append('\n'); + } + + return sb.toString(); + } +} diff --git a/dx/src/com/android/jack/dx/util/HexParser.java b/dx/src/com/android/jack/dx/util/HexParser.java new file mode 100644 index 00000000..551180ac --- /dev/null +++ b/dx/src/com/android/jack/dx/util/HexParser.java @@ -0,0 +1,145 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.util; + +/** + * Utilities for parsing hexadecimal text. + */ +public final class HexParser { + /** + * This class is uninstantiable. + */ + private HexParser() { + // This space intentionally left blank. + } + + /** + * Parses the given text as hex, returning a {@code byte[]} + * corresponding to the text. The format is simple: Each line may + * start with a hex offset followed by a colon (which is verified + * and presumably used just as a comment), and then consists of + * hex digits freely interspersed with whitespace. If a pound sign + * is encountered, it and the rest of the line are ignored as a + * comment. If a double quote is encountered, then the ASCII value + * of the subsequent characters is used, until the next double + * quote. Quoted strings may not span multiple lines. + * + * @param src {@code non-null;} the source string + * @return {@code non-null;} the parsed form + */ + public static byte[] parse(String src) { + int len = src.length(); + byte[] result = new byte[len / 2]; + int at = 0; + int outAt = 0; + + while (at < len) { + int nlAt = src.indexOf('\n', at); + if (nlAt < 0) { + nlAt = len; + } + int poundAt = src.indexOf('#', at); + + String line; + if ((poundAt >= 0) && (poundAt < nlAt)) { + line = src.substring(at, poundAt); + } else { + line = src.substring(at, nlAt); + } + at = nlAt + 1; + + int colonAt = line.indexOf(':'); + + atCheck: + if (colonAt != -1) { + int quoteAt = line.indexOf('\"'); + if ((quoteAt != -1) && (quoteAt < colonAt)) { + break atCheck; + } + + String atStr = line.substring(0, colonAt).trim(); + line = line.substring(colonAt + 1); + int alleged = Integer.parseInt(atStr, 16); + if (alleged != outAt) { + throw new RuntimeException("bogus offset marker: " + + atStr); + } + } + + int lineLen = line.length(); + int value = -1; + boolean quoteMode = false; + + for (int i = 0; i < lineLen; i++) { + char c = line.charAt(i); + + if (quoteMode) { + if (c == '\"') { + quoteMode = false; + } else { + result[outAt] = (byte) c; + outAt++; + } + continue; + } + + if (c <= ' ') { + continue; + } + if (c == '\"') { + if (value != -1) { + throw new RuntimeException("spare digit around " + + "offset " + Hex.u4(outAt)); + } + quoteMode = true; + continue; + } + + int digVal = Character.digit(c, 16); + if (digVal == -1) { + throw new RuntimeException("bogus digit character: \"" + + c + "\""); + } + if (value == -1) { + value = digVal; + } else { + result[outAt] = (byte) ((value << 4) | digVal); + outAt++; + value = -1; + } + } + + if (value != -1) { + throw new RuntimeException("spare digit around offset " + + Hex.u4(outAt)); + } + + if (quoteMode) { + throw new RuntimeException("unterminated quote around " + + "offset " + Hex.u4(outAt)); + } + } + + if (outAt < result.length) { + byte[] newr = new byte[outAt]; + System.arraycopy(result, 0, newr, 0, outAt); + result = newr; + } + + return result; + } +} diff --git a/dx/src/com/android/jack/dx/util/IndentingWriter.java b/dx/src/com/android/jack/dx/util/IndentingWriter.java new file mode 100644 index 00000000..5fa042a7 --- /dev/null +++ b/dx/src/com/android/jack/dx/util/IndentingWriter.java @@ -0,0 +1,169 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.util; + +import java.io.FilterWriter; +import java.io.IOException; +import java.io.Writer; + +/** + * Writer that wraps another writer and passes width-limited and + * optionally-prefixed output to its subordinate. When lines are + * wrapped they are automatically indented based on the start of the + * line. + */ +public final class IndentingWriter extends FilterWriter { + /** {@code null-ok;} optional prefix for every line */ + private final String prefix; + + /** {@code > 0;} the maximum output width */ + private final int width; + + /** {@code > 0;} the maximum indent */ + private final int maxIndent; + + /** {@code >= 0;} current output column (zero-based) */ + private int column; + + /** whether indent spaces are currently being collected */ + private boolean collectingIndent; + + /** {@code >= 0;} current indent amount */ + private int indent; + + /** + * Constructs an instance. + * + * @param out {@code non-null;} writer to send final output to + * @param width {@code >= 0;} the maximum output width (not including + * {@code prefix}), or {@code 0} for no maximum + * @param prefix {@code non-null;} the prefix for each line + */ + public IndentingWriter(Writer out, int width, String prefix) { + super(out); + + if (out == null) { + throw new NullPointerException("out == null"); + } + + if (width < 0) { + throw new IllegalArgumentException("width < 0"); + } + + if (prefix == null) { + throw new NullPointerException("prefix == null"); + } + + this.width = (width != 0) ? width : Integer.MAX_VALUE; + this.maxIndent = width >> 1; + this.prefix = (prefix.length() == 0) ? null : prefix; + + bol(); + } + + /** + * Constructs a no-prefix instance. + * + * @param out {@code non-null;} writer to send final output to + * @param width {@code >= 0;} the maximum output width (not including + * {@code prefix}), or {@code 0} for no maximum + */ + public IndentingWriter(Writer out, int width) { + this(out, width, ""); + } + + /** {@inheritDoc} */ + @Override + public void write(int c) throws IOException { + synchronized (lock) { + if (collectingIndent) { + if (c == ' ') { + indent++; + if (indent >= maxIndent) { + indent = maxIndent; + collectingIndent = false; + } + } else { + collectingIndent = false; + } + } + + if ((column == width) && (c != '\n')) { + out.write('\n'); + column = 0; + /* + * Note: No else, so this should fall through to the next + * if statement. + */ + } + + if (column == 0) { + if (prefix != null) { + out.write(prefix); + } + + if (!collectingIndent) { + for (int i = 0; i < indent; i++) { + out.write(' '); + } + column = indent; + } + } + + out.write(c); + + if (c == '\n') { + bol(); + } else { + column++; + } + } + } + + /** {@inheritDoc} */ + @Override + public void write(char[] cbuf, int off, int len) throws IOException { + synchronized (lock) { + while (len > 0) { + write(cbuf[off]); + off++; + len--; + } + } + } + + /** {@inheritDoc} */ + @Override + public void write(String str, int off, int len) throws IOException { + synchronized (lock) { + while (len > 0) { + write(str.charAt(off)); + off++; + len--; + } + } + } + + /** + * Indicates that output is at the beginning of a line. + */ + private void bol() { + column = 0; + collectingIndent = (maxIndent != 0); + indent = 0; + } +} diff --git a/dx/src/com/android/jack/dx/util/IntIterator.java b/dx/src/com/android/jack/dx/util/IntIterator.java new file mode 100644 index 00000000..cd28378e --- /dev/null +++ b/dx/src/com/android/jack/dx/util/IntIterator.java @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.util; + +/** + * An iterator for a list of ints. + */ +public interface IntIterator { + + /** + * Checks to see if the iterator has a next value. + * + * @return true if next() will succeed + */ + boolean hasNext(); + + /** + * Returns the next value in the iterator. + * + * @return next value + * @throws java.util.NoSuchElementException if no next element exists + */ + int next(); +} diff --git a/dx/src/com/android/jack/dx/util/IntList.java b/dx/src/com/android/jack/dx/util/IntList.java new file mode 100644 index 00000000..0a7ce7c8 --- /dev/null +++ b/dx/src/com/android/jack/dx/util/IntList.java @@ -0,0 +1,453 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.util; + +import java.util.Arrays; + +/** + * Simple list of {@code int}s. + */ +public final class IntList extends MutabilityControl { + /** {@code non-null;} immutable, no-element instance */ + public static final IntList EMPTY = new IntList(0); + + /** {@code non-null;} array of elements */ + private int[] values; + + /** {@code >= 0;} current size of the list */ + private int size; + + /** whether the values are currently sorted */ + private boolean sorted; + + static { + EMPTY.setImmutable(); + } + + /** + * Constructs a new immutable instance with the given element. + * + * @param value the sole value in the list + */ + public static IntList makeImmutable(int value) { + IntList result = new IntList(1); + + result.add(value); + result.setImmutable(); + + return result; + } + + /** + * Constructs a new immutable instance with the given elements. + * + * @param value0 the first value in the list + * @param value1 the second value in the list + */ + public static IntList makeImmutable(int value0, int value1) { + IntList result = new IntList(2); + + result.add(value0); + result.add(value1); + result.setImmutable(); + + return result; + } + + /** + * Constructs an empty instance with a default initial capacity. + */ + public IntList() { + this(4); + } + + /** + * Constructs an empty instance. + * + * @param initialCapacity {@code >= 0;} initial capacity of the list + */ + public IntList(int initialCapacity) { + super(true); + + try { + values = new int[initialCapacity]; + } catch (NegativeArraySizeException ex) { + // Translate the exception. + throw new IllegalArgumentException("size < 0"); + } + + size = 0; + sorted = true; + } + + /** {@inheritDoc} */ + @Override + public int hashCode() { + int result = 0; + + for (int i = 0; i < size; i++) { + result = (result * 31) + values[i]; + } + + return result; + } + + /** {@inheritDoc} */ + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (! (other instanceof IntList)) { + return false; + } + + IntList otherList = (IntList) other; + + if (sorted != otherList.sorted) { + return false; + } + + if (size != otherList.size) { + return false; + } + + for (int i = 0; i < size; i++) { + if (values[i] != otherList.values[i]) { + return false; + } + } + + return true; + } + + /** {@inheritDoc} */ + @Override + public String toString() { + StringBuffer sb = new StringBuffer(size * 5 + 10); + + sb.append('{'); + + for (int i = 0; i < size; i++) { + if (i != 0) { + sb.append(", "); + } + sb.append(values[i]); + } + + sb.append('}'); + + return sb.toString(); + } + + /** + * Gets the number of elements in this list. + */ + public int size() { + return size; + } + + /** + * Gets the indicated value. + * + * @param n {@code >= 0, < size();} which element + * @return the indicated element's value + */ + public int get(int n) { + if (n >= size) { + throw new IndexOutOfBoundsException("n >= size()"); + } + + try { + return values[n]; + } catch (ArrayIndexOutOfBoundsException ex) { + // Translate exception. + throw new IndexOutOfBoundsException("n < 0"); + } + } + + /** + * Sets the value at the given index. + * + * @param n {@code >= 0, < size();} which element + * @param value value to store + */ + public void set(int n, int value) { + throwIfImmutable(); + + if (n >= size) { + throw new IndexOutOfBoundsException("n >= size()"); + } + + try { + values[n] = value; + sorted = false; + } catch (ArrayIndexOutOfBoundsException ex) { + // Translate the exception. + if (n < 0) { + throw new IllegalArgumentException("n < 0"); + } + } + } + + /** + * Adds an element to the end of the list. This will increase the + * list's capacity if necessary. + * + * @param value the value to add + */ + public void add(int value) { + throwIfImmutable(); + + growIfNeeded(); + + values[size++] = value; + + if (sorted && (size > 1)) { + sorted = (value >= values[size - 2]); + } + } + + /** + * Inserts element into specified index, moving elements at and above + * that index up one. May not be used to insert at an index beyond the + * current size (that is, insertion as a last element is legal but + * no further). + * + * @param n {@code >= 0, <=size();} index of where to insert + * @param value value to insert + */ + public void insert(int n, int value) { + if (n > size) { + throw new IndexOutOfBoundsException("n > size()"); + } + + growIfNeeded(); + + System.arraycopy (values, n, values, n+1, size - n); + values[n] = value; + size++; + + sorted = sorted + && (n == 0 || value > values[n-1]) + && (n == (size - 1) || value < values[n+1]); + } + + /** + * Removes an element at a given index, shifting elements at greater + * indicies down one. + * + * @param n {@code >=0, < size();} index of element to remove + */ + public void removeIndex(int n) { + if (n >= size) { + throw new IndexOutOfBoundsException("n >= size()"); + } + + System.arraycopy (values, n + 1, values, n, size - n - 1); + size--; + + // sort status is unchanged + } + + /** + * Increases size of array if needed + */ + private void growIfNeeded() { + if (size == values.length) { + // Resize. + int[] newv = new int[size * 3 / 2 + 10]; + System.arraycopy(values, 0, newv, 0, size); + values = newv; + } + } + + /** + * Returns the last element in the array without modifying the array + * + * @return last value in the array + * @throws IndexOutOfBoundsException if stack is empty + */ + public int top() { + return get(size - 1); + } + + /** + * Pops an element off the end of the list and decreasing the size by one. + * + * @return value from what was the last element + * @throws IndexOutOfBoundsException if stack is empty + */ + public int pop() { + throwIfImmutable(); + + int result; + + result = get(size-1); + size--; + + return result; + } + + /** + * Pops N elements off the end of the list and decreasing the size by N. + * + * @param n {@code >= 0;} number of elements to remove from end + * @throws IndexOutOfBoundsException if stack is smaller than N + */ + public void pop(int n) { + throwIfImmutable(); + + size -= n; + } + + /** + * Shrinks the size of the list. + * + * @param newSize {@code >= 0;} the new size + */ + public void shrink(int newSize) { + if (newSize < 0) { + throw new IllegalArgumentException("newSize < 0"); + } + + if (newSize > size) { + throw new IllegalArgumentException("newSize > size"); + } + + throwIfImmutable(); + + size = newSize; + } + + /** + * Makes and returns a mutable copy of the list. + * + * @return {@code non-null;} an appropriately-constructed instance + */ + public IntList mutableCopy() { + int sz = size; + IntList result = new IntList(sz); + + for (int i = 0; i < sz; i++) { + result.add(values[i]); + } + + return result; + } + + /** + * Sorts the elements in the list in-place. + */ + public void sort() { + throwIfImmutable(); + + if (!sorted) { + Arrays.sort(values, 0, size); + sorted = true; + } + } + + /** + * Returns the index of the given value, or -1 if the value does not + * appear in the list. This will do a binary search if the list is + * sorted or a linear search if not. + * + * @param value value to find + * @return index of value or -1 + */ + public int indexOf(int value) { + int ret = binarysearch(value); + + return ret >= 0 ? ret : -1; + + } + + /** + * Performs a binary search on a sorted list, returning the index of + * the given value if it is present or + * {@code (-(insertion point) - 1)} if the value is not present. + * If the list is not sorted, then reverts to linear search and returns + * {@code -size()} if the element is not found. + * + * @param value value to find + * @return index of value or {@code (-(insertion point) - 1)} if the + * value is not present + */ + public int binarysearch(int value) { + int sz = size; + + if (!sorted) { + // Linear search. + for (int i = 0; i < sz; i++) { + if (values[i] == value) { + return i; + } + } + + return -sz; + } + + /* + * Binary search. This variant does only one value comparison + * per iteration but does one more iteration on average than + * the variant that includes a value equality check per + * iteration. + */ + + int min = -1; + int max = sz; + + while (max > (min + 1)) { + /* + * The guessIdx calculation is equivalent to ((min + max) + * / 2) but won't go wonky when min and max are close to + * Integer.MAX_VALUE. + */ + int guessIdx = min + ((max - min) >> 1); + int guess = values[guessIdx]; + + if (value <= guess) { + max = guessIdx; + } else { + min = guessIdx; + } + } + + if ((max != sz)) { + return (value == values[max]) ? max : (-max - 1); + } else { + return -sz - 1; + } + } + + + /** + * Returns whether or not the given value appears in the list. + * This will do a binary search if the list is sorted or a linear + * search if not. + * + * @see #sort + * + * @param value value to look for + * @return whether the list contains the given value + */ + public boolean contains(int value) { + return indexOf(value) >= 0; + } +} diff --git a/dx/src/com/android/jack/dx/util/IntSet.java b/dx/src/com/android/jack/dx/util/IntSet.java new file mode 100644 index 00000000..9d056fe0 --- /dev/null +++ b/dx/src/com/android/jack/dx/util/IntSet.java @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.util; + +/** + * A set of integers + */ +public interface IntSet { + + /** + * Adds an int to a set + * + * @param value int to add + */ + void add(int value); + + /** + * Removes an int from a set. + * + * @param value int to remove + */ + void remove(int value); + + /** + * Checks to see if a value is in the set + * + * @param value int to check + * @return true if in set + */ + boolean has(int value); + + /** + * Merges {@code other} into this set, so this set becomes the + * union of the two. + * + * @param other {@code non-null;} other set to merge with. + */ + void merge(IntSet other); + + /** + * Returns the count of unique elements in this set. + * + * @return {@code > = 0;} count of unique elements + */ + int elements(); + + /** + * Iterates the set + * + * @return {@code non-null;} a set iterator + */ + IntIterator iterator(); +} diff --git a/dx/src/com/android/jack/dx/util/LabeledItem.java b/dx/src/com/android/jack/dx/util/LabeledItem.java new file mode 100644 index 00000000..3883e388 --- /dev/null +++ b/dx/src/com/android/jack/dx/util/LabeledItem.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.util; + +/** + * An item that has an integer label. + */ +public interface LabeledItem { + + /* + * Gets the label of this block. + * + * @return {@code >= 0;} the label + */ + public int getLabel(); +} diff --git a/dx/src/com/android/jack/dx/util/LabeledList.java b/dx/src/com/android/jack/dx/util/LabeledList.java new file mode 100644 index 00000000..323d9975 --- /dev/null +++ b/dx/src/com/android/jack/dx/util/LabeledList.java @@ -0,0 +1,187 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.util; + +import java.util.Arrays; + +/** + * A list of labeled items, allowing easy lookup by label. + */ +public class LabeledList extends FixedSizeList { + /** + * Sparse array indexed by label to FixedSizeList index; + * {@code -1} for an invalid label. + */ + private final IntList labelToIndex; + + /** @inheritDoc */ + public LabeledList(int size) { + super(size); + + labelToIndex = new IntList(size); + } + + /** + * Constructs a new instance that is a copy of the old instance. + * + * @param old instance to copy + */ + public LabeledList(LabeledList old) { + super(old.size()); + labelToIndex = old.labelToIndex.mutableCopy(); + + int sz = old.size(); + + for (int i = 0; i < sz; i++) { + Object one = old.get0(i); + if (one != null) { + set0(i, one); + } + } + } + + /** + * Gets the maximum label (exclusive) of any block added to this instance. + * + * @return {@code >= 0;} the maximum label + */ + public final int getMaxLabel() { + int sz = labelToIndex.size(); + + // Gobble any deleted labels that may be at the end. + int i; + for (i = sz - 1; (i >= 0) && (labelToIndex.get(i) < 0); i--) + /*empty*/ ; + + int newSize = i + 1; + + labelToIndex.shrink(newSize); + + return newSize; + } + + /** + * Removes a label from the label-to-index mapping. + * + * @param oldLabel label to remove + */ + private void removeLabel(int oldLabel) { + labelToIndex.set(oldLabel, -1); + } + + /** + * Adds a label and index to the label-to-index mapping. + * + * @param label new label + * @param index index of block. + */ + private void addLabelIndex(int label, int index) { + int origSz = labelToIndex.size(); + + for (int i = 0; i <= (label - origSz); i++) { + labelToIndex.add(-1); + } + + labelToIndex.set(label, index); + } + + /** + * Gets the index of the first item in the list with the given + * label, if any. + * + * @param label {@code >= 0;} the label to look for + * @return {@code >= -1;} the index of the so-labelled item, or {@code -1} + * if none is found + */ + public final int indexOfLabel(int label) { + if (label >= labelToIndex.size()) { + return -1; + } else { + return labelToIndex.get(label); + } + } + + /** + * Gets an array containing all of the labels used in this instance, + * in order. The returned array is freshly-allocated and so may be + * modified safely by the caller without impacting this instance. + * + * @return {@code non-null;} ordered array of labels + * @throws NullPointerException thrown if there are any {@code null} + * items in this instance + */ + public final int[] getLabelsInOrder() { + int sz = size(); + int[] result = new int[sz]; + + for (int i = 0; i < sz; i++) { + LabeledItem li = (LabeledItem) get0(i); + if (li == null) { + throw new NullPointerException("null at index " + i); + } + result[i] = li.getLabel(); + } + + Arrays.sort(result); + return result; + } + + /** @inheritDoc */ + @Override + public void shrinkToFit() { + super.shrinkToFit(); + + rebuildLabelToIndex(); + } + + /** + * Rebuilds the label-to-index mapping after a {@code shrinkToFit()}. + * Note: This assumes that the labels that are in the list are the + * same, although the indicies may have changed. + */ + private void rebuildLabelToIndex() { + int szItems = size(); + + for (int i = 0; i < szItems; i++) { + LabeledItem li = (LabeledItem) get0(i); + + if (li != null) { + labelToIndex.set(li.getLabel(), i); + } + } + } + + /** + * Sets the element at the given index. + * + * @param n {@code >= 0, < size();} which element + * @param item {@code null-ok;} the value to store + */ + protected void set(int n, LabeledItem item) { + LabeledItem old = (LabeledItem) getOrNull0(n); + + set0(n, item); + + if (old != null) { + removeLabel(old.getLabel()); + } + + if (item != null) { + addLabelIndex(item.getLabel(), n); + } + } +} diff --git a/dx/src/com/android/jack/dx/util/Leb128Utils.java b/dx/src/com/android/jack/dx/util/Leb128Utils.java new file mode 100644 index 00000000..f6d85d1a --- /dev/null +++ b/dx/src/com/android/jack/dx/util/Leb128Utils.java @@ -0,0 +1,162 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.util; + +/** + * Reads and writes DWARFv3 LEB 128 signed and unsigned integers. See DWARF v3 + * section 7.6. + */ +public final class Leb128Utils { + /** + * This class is uninstantiable. + */ + private Leb128Utils() { + // This space intentionally left blank. + } + + /** + * Gets the number of bytes in the unsigned LEB128 encoding of the + * given value. + * + * @param value the value in question + * @return its write size, in bytes + */ + public static int unsignedLeb128Size(int value) { + // TODO: This could be much cleverer. + + int remaining = value >> 7; + int count = 0; + + while (remaining != 0) { + remaining >>= 7; + count++; + } + + return count + 1; + } + + /** + * Gets the number of bytes in the signed LEB128 encoding of the + * given value. + * + * @param value the value in question + * @return its write size, in bytes + */ + public static int signedLeb128Size(int value) { + // TODO: This could be much cleverer. + + int remaining = value >> 7; + int count = 0; + boolean hasMore = true; + int end = ((value & Integer.MIN_VALUE) == 0) ? 0 : -1; + + while (hasMore) { + hasMore = (remaining != end) + || ((remaining & 1) != ((value >> 6) & 1)); + + value = remaining; + remaining >>= 7; + count++; + } + + return count; + } + + /** + * Reads an signed integer from {@code in}. + */ + public static int readSignedLeb128(ByteInput in) { + int result = 0; + int cur; + int count = 0; + int signBits = -1; + + do { + cur = in.readByte() & 0xff; + result |= (cur & 0x7f) << (count * 7); + signBits <<= 7; + count++; + } while (((cur & 0x80) == 0x80) && count < 5); + + if ((cur & 0x80) == 0x80) { + throw new DexException("invalid LEB128 sequence"); + } + + // Sign extend if appropriate + if (((signBits >> 1) & result) != 0 ) { + result |= signBits; + } + + return result; + } + + /** + * Reads an unsigned integer from {@code in}. + */ + public static int readUnsignedLeb128(ByteInput in) { + int result = 0; + int cur; + int count = 0; + + do { + cur = in.readByte() & 0xff; + result |= (cur & 0x7f) << (count * 7); + count++; + } while (((cur & 0x80) == 0x80) && count < 5); + + if ((cur & 0x80) == 0x80) { + throw new DexException("invalid LEB128 sequence"); + } + + return result; + } + + /** + * Writes {@code value} as an unsigned integer to {@code out}, starting at + * {@code offset}. Returns the number of bytes written. + */ + public static void writeUnsignedLeb128(ByteOutput out, int value) { + int remaining = value >>> 7; + + while (remaining != 0) { + out.writeByte((byte) ((value & 0x7f) | 0x80)); + value = remaining; + remaining >>>= 7; + } + + out.writeByte((byte) (value & 0x7f)); + } + + /** + * Writes {@code value} as a signed integer to {@code out}, starting at + * {@code offset}. Returns the number of bytes written. + */ + public static void writeSignedLeb128(ByteOutput out, int value) { + int remaining = value >> 7; + boolean hasMore = true; + int end = ((value & Integer.MIN_VALUE) == 0) ? 0 : -1; + + while (hasMore) { + hasMore = (remaining != end) + || ((remaining & 1) != ((value >> 6) & 1)); + + out.writeByte((byte) ((value & 0x7f) | (hasMore ? 0x80 : 0))); + value = remaining; + remaining >>= 7; + } + } +} diff --git a/dx/src/com/android/jack/dx/util/ListIntSet.java b/dx/src/com/android/jack/dx/util/ListIntSet.java new file mode 100644 index 00000000..36f18914 --- /dev/null +++ b/dx/src/com/android/jack/dx/util/ListIntSet.java @@ -0,0 +1,132 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.util; + +import java.util.NoSuchElementException; + +/** + * A set of integers, represented by a list + */ +public class ListIntSet implements IntSet { + + /** also accessed in BitIntSet */ + final IntList ints; + + /** + * Constructs an instance + */ + public ListIntSet() { + ints = new IntList(); + ints.sort(); + } + + /** @inheritDoc */ + public void add(int value) { + int index = ints.binarysearch(value); + + if (index < 0) { + ints.insert(-(index + 1), value); + } + } + + /** @inheritDoc */ + public void remove(int value) { + int index = ints.indexOf(value); + + if (index >= 0) { + ints.removeIndex(index); + } + } + + /** @inheritDoc */ + public boolean has(int value) { + return ints.indexOf(value) >= 0; + } + + /** @inheritDoc */ + public void merge(IntSet other) { + if (other instanceof ListIntSet) { + ListIntSet o = (ListIntSet) other; + int szThis = ints.size(); + int szOther = o.ints.size(); + + int i = 0; + int j = 0; + + while (j < szOther && i < szThis) { + while (j < szOther && o.ints.get(j) < ints.get(i)) { + add(o.ints.get(j++)); + } + if (j == szOther) { + break; + } + while (i < szThis && o.ints.get(j) >= ints.get(i)) { + i++; + } + } + + while (j < szOther) { + add(o.ints.get(j++)); + } + + ints.sort(); + } else if (other instanceof BitIntSet) { + BitIntSet o = (BitIntSet) other; + + for (int i = 0; i >= 0; i = Bits.findFirst(o.bits, i + 1)) { + ints.add(i); + } + ints.sort(); + } else { + IntIterator iter = other.iterator(); + while (iter.hasNext()) { + add(iter.next()); + } + } + } + + /** @inheritDoc */ + public int elements() { + return ints.size(); + } + + /** @inheritDoc */ + public IntIterator iterator() { + return new IntIterator() { + private int idx = 0; + + /** @inheritDoc */ + public boolean hasNext() { + return idx < ints.size(); + } + + /** @inheritDoc */ + public int next() { + if (!hasNext()) { + throw new NoSuchElementException(); + } + + return ints.get(idx++); + } + }; + } + + /** @inheritDoc */ + public String toString() { + return ints.toString(); + } +} diff --git a/dx/src/com/android/jack/dx/util/MutabilityControl.java b/dx/src/com/android/jack/dx/util/MutabilityControl.java new file mode 100644 index 00000000..d716ffe6 --- /dev/null +++ b/dx/src/com/android/jack/dx/util/MutabilityControl.java @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.util; + +/** + * Very simple base class that implements a flag to control the mutability + * of instances. This class just provides the flag and a utility to check + * and throw the right exception, but it is up to subclasses to place calls + * to the checker in all the right places. + */ +public class MutabilityControl { + /** whether this instance is mutable */ + private boolean mutable; + + /** + * Constructs an instance. It is initially mutable. + */ + public MutabilityControl() { + mutable = true; + } + + /** + * Constructs an instance, explicitly indicating the mutability. + * + * @param mutable {@code true} iff this instance is mutable + */ + public MutabilityControl(boolean mutable) { + this.mutable = mutable; + } + + /** + * Makes this instance immutable. + */ + public void setImmutable() { + mutable = false; + } + + /** + * Checks to see whether or not this instance is immutable. This is the + * same as calling {@code !isMutable()}. + * + * @return {@code true} iff this instance is immutable + */ + public final boolean isImmutable() { + return !mutable; + } + + /** + * Checks to see whether or not this instance is mutable. + * + * @return {@code true} iff this instance is mutable + */ + public final boolean isMutable() { + return mutable; + } + + /** + * Throws {@link MutabilityException} if this instance is + * immutable. + */ + public final void throwIfImmutable() { + if (!mutable) { + throw new MutabilityException("immutable instance"); + } + } + + /** + * Throws {@link MutabilityException} if this instance is mutable. + */ + public final void throwIfMutable() { + if (mutable) { + throw new MutabilityException("mutable instance"); + } + } +} diff --git a/dx/src/com/android/jack/dx/util/MutabilityException.java b/dx/src/com/android/jack/dx/util/MutabilityException.java new file mode 100644 index 00000000..7e598832 --- /dev/null +++ b/dx/src/com/android/jack/dx/util/MutabilityException.java @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.util; + +/** + * Exception due to a mutability problem. + */ +public class MutabilityException + extends ExceptionWithContext { + public MutabilityException(String message) { + super(message); + } + + public MutabilityException(Throwable cause) { + super(cause); + } + + public MutabilityException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/dx/src/com/android/jack/dx/util/Mutf8.java b/dx/src/com/android/jack/dx/util/Mutf8.java new file mode 100644 index 00000000..5f01693f --- /dev/null +++ b/dx/src/com/android/jack/dx/util/Mutf8.java @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.util; + +import java.io.UTFDataFormatException; + +/** + * Modified UTF-8 as described in the dex file format spec. + * + *

      Derived from libcore's MUTF-8 encoder at java.nio.charset.ModifiedUtf8. + */ +public final class Mutf8 { + private Mutf8() {} + + /** + * Decodes bytes from {@code in} into {@code out} until a delimiter 0x00 is + * encountered. Returns a new string containing the decoded characters. + */ + public static String decode(ByteInput in, char[] out) throws UTFDataFormatException { + int s = 0; + while (true) { + char a = (char) (in.readByte() & 0xff); + if (a == 0) { + return new String(out, 0, s); + } + out[s] = a; + if (a < '\u0080') { + s++; + } else if ((a & 0xe0) == 0xc0) { + int b = in.readByte() & 0xff; + if ((b & 0xC0) != 0x80) { + throw new UTFDataFormatException("bad second byte"); + } + out[s++] = (char) (((a & 0x1F) << 6) | (b & 0x3F)); + } else if ((a & 0xf0) == 0xe0) { + int b = in.readByte() & 0xff; + int c = in.readByte() & 0xff; + if (((b & 0xC0) != 0x80) || ((c & 0xC0) != 0x80)) { + throw new UTFDataFormatException("bad second or third byte"); + } + out[s++] = (char) (((a & 0x0F) << 12) | ((b & 0x3F) << 6) | (c & 0x3F)); + } else { + throw new UTFDataFormatException("bad byte"); + } + } + } + + /** + * Returns the number of bytes the modified UTF8 representation of 's' would take. + */ + private static long countBytes(String s, boolean shortLength) throws UTFDataFormatException { + long result = 0; + final int length = s.length(); + for (int i = 0; i < length; ++i) { + char ch = s.charAt(i); + if (ch != 0 && ch <= 127) { // U+0000 uses two bytes. + ++result; + } else if (ch <= 2047) { + result += 2; + } else { + result += 3; + } + if (shortLength && result > 65535) { + throw new UTFDataFormatException("String more than 65535 UTF bytes long"); + } + } + return result; + } + + /** + * Encodes the modified UTF-8 bytes corresponding to {@code s} into {@code + * dst}, starting at {@code offset}. + */ + public static void encode(byte[] dst, int offset, String s) { + final int length = s.length(); + for (int i = 0; i < length; i++) { + char ch = s.charAt(i); + if (ch != 0 && ch <= 127) { // U+0000 uses two bytes. + dst[offset++] = (byte) ch; + } else if (ch <= 2047) { + dst[offset++] = (byte) (0xc0 | (0x1f & (ch >> 6))); + dst[offset++] = (byte) (0x80 | (0x3f & ch)); + } else { + dst[offset++] = (byte) (0xe0 | (0x0f & (ch >> 12))); + dst[offset++] = (byte) (0x80 | (0x3f & (ch >> 6))); + dst[offset++] = (byte) (0x80 | (0x3f & ch)); + } + } + } + + /** + * Returns an array containing the modified UTF-8 form of {@code s}. + */ + public static byte[] encode(String s) throws UTFDataFormatException { + int utfCount = (int) countBytes(s, true); + byte[] result = new byte[utfCount]; + encode(result, 0, s); + return result; + } +} diff --git a/dx/src/com/android/jack/dx/util/Output.java b/dx/src/com/android/jack/dx/util/Output.java new file mode 100644 index 00000000..42640b7a --- /dev/null +++ b/dx/src/com/android/jack/dx/util/Output.java @@ -0,0 +1,129 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.util; + +/** + * Interface for a sink for binary output. This is similar to + * {@code java.util.DataOutput}, but no {@code IOExceptions} + * are declared, and multibyte output is defined to be little-endian. + */ +public interface Output extends ByteOutput { + /** + * Gets the current cursor position. This is the same as the number of + * bytes written to this instance. + * + * @return {@code >= 0;} the cursor position + */ + public int getCursor(); + + /** + * Asserts that the cursor is the given value. + * + * @param expectedCursor the expected cursor value + * @throws RuntimeException thrown if {@code getCursor() != + * expectedCursor} + */ + public void assertCursor(int expectedCursor); + + /** + * Writes a {@code byte} to this instance. + * + * @param value the value to write; all but the low 8 bits are ignored + */ + public void writeByte(int value); + + /** + * Writes a {@code short} to this instance. + * + * @param value the value to write; all but the low 16 bits are ignored + */ + public void writeShort(int value); + + /** + * Writes an {@code int} to this instance. + * + * @param value the value to write + */ + public void writeInt(int value); + + /** + * Writes a {@code long} to this instance. + * + * @param value the value to write + */ + public void writeLong(long value); + + /** + * Writes a DWARFv3-style unsigned LEB128 integer. For details, + * see the "Dalvik Executable Format" document or DWARF v3 section + * 7.6. + * + * @param value value to write, treated as an unsigned value + * @return {@code 1..5;} the number of bytes actually written + */ + public int writeUleb128(int value); + + /** + * Writes a DWARFv3-style unsigned LEB128 integer. For details, + * see the "Dalvik Executable Format" document or DWARF v3 section + * 7.6. + * + * @param value value to write + * @return {@code 1..5;} the number of bytes actually written + */ + public int writeSleb128(int value); + + /** + * Writes a {@link ByteArray} to this instance. + * + * @param bytes {@code non-null;} the array to write + */ + public void write(ByteArray bytes); + + /** + * Writes a portion of a {@code byte[]} to this instance. + * + * @param bytes {@code non-null;} the array to write + * @param offset {@code >= 0;} offset into {@code bytes} for the first + * byte to write + * @param length {@code >= 0;} number of bytes to write + */ + public void write(byte[] bytes, int offset, int length); + + /** + * Writes a {@code byte[]} to this instance. This is just + * a convenient shorthand for {@code write(bytes, 0, bytes.length)}. + * + * @param bytes {@code non-null;} the array to write + */ + public void write(byte[] bytes); + + /** + * Writes the given number of {@code 0} bytes. + * + * @param count {@code >= 0;} the number of zeroes to write + */ + public void writeZeroes(int count); + + /** + * Adds extra bytes if necessary (with value {@code 0}) to + * force alignment of the output cursor as given. + * + * @param alignment {@code > 0;} the alignment; must be a power of two + */ + public void alignTo(int alignment); +} diff --git a/dx/src/com/android/jack/dx/util/ToHuman.java b/dx/src/com/android/jack/dx/util/ToHuman.java new file mode 100644 index 00000000..a74e6703 --- /dev/null +++ b/dx/src/com/android/jack/dx/util/ToHuman.java @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.util; + +/** + * Simple interface for objects that can return a "human" (as opposed to + * a complete but often hard to read) string form. + */ +public interface ToHuman { + /** + * Return the "human" string form of this instance. This is + * generally less "debuggy" than {@code toString()}. + * + * @return {@code non-null;} the human string form + */ + public String toHuman(); +} diff --git a/dx/src/com/android/jack/dx/util/TwoColumnOutput.java b/dx/src/com/android/jack/dx/util/TwoColumnOutput.java new file mode 100644 index 00000000..afce5bb3 --- /dev/null +++ b/dx/src/com/android/jack/dx/util/TwoColumnOutput.java @@ -0,0 +1,254 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.util; + +import java.io.IOException; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.StringWriter; +import java.io.Writer; + +/** + * Class that takes a combined output destination and provides two + * output writers, one of which ends up writing to the left column and + * one which goes on the right. + */ +public final class TwoColumnOutput { + /** {@code non-null;} underlying writer for final output */ + private final Writer out; + + /** {@code > 0;} the left column width */ + private final int leftWidth; + + /** {@code non-null;} pending left column output */ + private final StringBuffer leftBuf; + + /** {@code non-null;} pending right column output */ + private final StringBuffer rightBuf; + + /** {@code non-null;} left column writer */ + private final IndentingWriter leftColumn; + + /** {@code non-null;} right column writer */ + private final IndentingWriter rightColumn; + + /** + * Turns the given two strings (with widths) and spacer into a formatted + * two-column string. + * + * @param s1 {@code non-null;} first string + * @param width1 {@code > 0;} width of the first column + * @param spacer {@code non-null;} spacer string + * @param s2 {@code non-null;} second string + * @param width2 {@code > 0;} width of the second column + * @return {@code non-null;} an appropriately-formatted string + */ + public static String toString(String s1, int width1, String spacer, + String s2, int width2) { + int len1 = s1.length(); + int len2 = s2.length(); + + StringWriter sw = new StringWriter((len1 + len2) * 3); + TwoColumnOutput twoOut = + new TwoColumnOutput(sw, width1, width2, spacer); + + try { + twoOut.getLeft().write(s1); + twoOut.getRight().write(s2); + } catch (IOException ex) { + throw new RuntimeException("shouldn't happen", ex); + } + + twoOut.flush(); + return sw.toString(); + } + + /** + * Constructs an instance. + * + * @param out {@code non-null;} writer to send final output to + * @param leftWidth {@code > 0;} width of the left column, in characters + * @param rightWidth {@code > 0;} width of the right column, in characters + * @param spacer {@code non-null;} spacer string to sit between the two columns + */ + public TwoColumnOutput(Writer out, int leftWidth, int rightWidth, + String spacer) { + if (out == null) { + throw new NullPointerException("out == null"); + } + + if (leftWidth < 1) { + throw new IllegalArgumentException("leftWidth < 1"); + } + + if (rightWidth < 1) { + throw new IllegalArgumentException("rightWidth < 1"); + } + + if (spacer == null) { + throw new NullPointerException("spacer == null"); + } + + StringWriter leftWriter = new StringWriter(1000); + StringWriter rightWriter = new StringWriter(1000); + + this.out = out; + this.leftWidth = leftWidth; + this.leftBuf = leftWriter.getBuffer(); + this.rightBuf = rightWriter.getBuffer(); + this.leftColumn = new IndentingWriter(leftWriter, leftWidth); + this.rightColumn = + new IndentingWriter(rightWriter, rightWidth, spacer); + } + + /** + * Constructs an instance. + * + * @param out {@code non-null;} stream to send final output to + * @param leftWidth {@code >= 1;} width of the left column, in characters + * @param rightWidth {@code >= 1;} width of the right column, in characters + * @param spacer {@code non-null;} spacer string to sit between the two columns + */ + public TwoColumnOutput(OutputStream out, int leftWidth, int rightWidth, + String spacer) { + this(new OutputStreamWriter(out), leftWidth, rightWidth, spacer); + } + + /** + * Gets the writer to use to write to the left column. + * + * @return {@code non-null;} the left column writer + */ + public Writer getLeft() { + return leftColumn; + } + + /** + * Gets the writer to use to write to the right column. + * + * @return {@code non-null;} the right column writer + */ + public Writer getRight() { + return rightColumn; + } + + /** + * Flushes the output. If there are more lines of pending output in one + * column, then the other column will get filled with blank lines. + */ + public void flush() { + try { + appendNewlineIfNecessary(leftBuf, leftColumn); + appendNewlineIfNecessary(rightBuf, rightColumn); + outputFullLines(); + flushLeft(); + flushRight(); + } catch (IOException ex) { + throw new RuntimeException(ex); + } + } + + /** + * Outputs to the final destination as many full line pairs as + * there are in the pending output, removing those lines from + * their respective buffers. This method terminates when at + * least one of the two column buffers is empty. + */ + private void outputFullLines() throws IOException { + for (;;) { + int leftLen = leftBuf.indexOf("\n"); + if (leftLen < 0) { + return; + } + + int rightLen = rightBuf.indexOf("\n"); + if (rightLen < 0) { + return; + } + + if (leftLen != 0) { + out.write(leftBuf.substring(0, leftLen)); + } + + if (rightLen != 0) { + writeSpaces(out, leftWidth - leftLen); + out.write(rightBuf.substring(0, rightLen)); + } + + out.write('\n'); + + leftBuf.delete(0, leftLen + 1); + rightBuf.delete(0, rightLen + 1); + } + } + + /** + * Flushes the left column buffer, printing it and clearing the buffer. + * If the buffer is already empty, this does nothing. + */ + private void flushLeft() throws IOException { + appendNewlineIfNecessary(leftBuf, leftColumn); + + while (leftBuf.length() != 0) { + rightColumn.write('\n'); + outputFullLines(); + } + } + + /** + * Flushes the right column buffer, printing it and clearing the buffer. + * If the buffer is already empty, this does nothing. + */ + private void flushRight() throws IOException { + appendNewlineIfNecessary(rightBuf, rightColumn); + + while (rightBuf.length() != 0) { + leftColumn.write('\n'); + outputFullLines(); + } + } + + /** + * Appends a newline to the given buffer via the given writer, but + * only if it isn't empty and doesn't already end with one. + * + * @param buf {@code non-null;} the buffer in question + * @param out {@code non-null;} the writer to use + */ + private static void appendNewlineIfNecessary(StringBuffer buf, + Writer out) + throws IOException { + int len = buf.length(); + + if ((len != 0) && (buf.charAt(len - 1) != '\n')) { + out.write('\n'); + } + } + + /** + * Writes the given number of spaces to the given writer. + * + * @param out {@code non-null;} where to write + * @param amt {@code >= 0;} the number of spaces to write + */ + private static void writeSpaces(Writer out, int amt) throws IOException { + while (amt > 0) { + out.write(' '); + amt--; + } + } +} diff --git a/dx/src/com/android/jack/dx/util/Uint.java b/dx/src/com/android/jack/dx/util/Uint.java new file mode 100644 index 00000000..f84a988b --- /dev/null +++ b/dx/src/com/android/jack/dx/util/Uint.java @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.util; + +/** + * An unsigned integer. + */ +public final class Uint implements Comparable { + public final int intValue; + + public Uint(int value) { + this.intValue = value; + } + + public int compareTo(Uint uint) { + return Unsigned.compare(intValue, uint.intValue); + } +} diff --git a/dx/src/com/android/jack/dx/util/Unsigned.java b/dx/src/com/android/jack/dx/util/Unsigned.java new file mode 100644 index 00000000..63c8e1d9 --- /dev/null +++ b/dx/src/com/android/jack/dx/util/Unsigned.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.util; + +/** + * Unsigned arithmetic over Java's signed types. + */ +public final class Unsigned { + private Unsigned() {} + + public static int compare(short ushortA, short ushortB) { + if (ushortA == ushortB) { + return 0; + } + int a = ushortA & 0xFFFF; + int b = ushortB & 0xFFFF; + return a < b ? -1 : 1; + } + + public static int compare(int uintA, int uintB) { + if (uintA == uintB) { + return 0; + } + long a = uintA & 0xFFFFFFFFL; + long b = uintB & 0xFFFFFFFFL; + return a < b ? -1 : 1; + } +} diff --git a/dx/src/com/android/jack/dx/util/Warning.java b/dx/src/com/android/jack/dx/util/Warning.java new file mode 100644 index 00000000..effb49f6 --- /dev/null +++ b/dx/src/com/android/jack/dx/util/Warning.java @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.util; + +/** + * Exception which is meant to indicate a non-fatal warning. + */ +public class Warning extends RuntimeException { + /** + * Constructs an instance. + * + * @param message human-oriented message + */ + public Warning(String message) { + super(message); + } +} diff --git a/dx/src/com/android/jack/dx/util/Writers.java b/dx/src/com/android/jack/dx/util/Writers.java new file mode 100644 index 00000000..3d5c8d1c --- /dev/null +++ b/dx/src/com/android/jack/dx/util/Writers.java @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.util; + +import java.io.PrintWriter; +import java.io.Writer; + +/** + * Utilities for dealing with {@code Writer}s. + */ +public final class Writers { + /** + * This class is uninstantiable. + */ + private Writers() { + // This space intentionally left blank. + } + + /** + * Makes a {@code PrintWriter} for the given {@code Writer}, + * returning the given writer if it already happens to be the right + * class. + * + * @param writer {@code non-null;} writer to (possibly) wrap + * @return {@code non-null;} an appropriate instance + */ + public static PrintWriter printWriterFor(Writer writer) { + if (writer instanceof PrintWriter) { + return (PrintWriter) writer; + } + + return new PrintWriter(writer); + } +} diff --git a/dx/src/com/android/jack/dx/util/package.html b/dx/src/com/android/jack/dx/util/package.html new file mode 100644 index 00000000..8e81e947 --- /dev/null +++ b/dx/src/com/android/jack/dx/util/package.html @@ -0,0 +1,3 @@ + +

      Utility classes for class file access/manipulation.

      + -- cgit v1.2.3