diff options
| -rw-r--r-- | startop/view_compiler/dex_builder.cc | 33 | ||||
| -rw-r--r-- | startop/view_compiler/dex_builder.h | 16 | ||||
| -rw-r--r-- | startop/view_compiler/dex_builder_test/src/android/startop/test/DexBuilderTest.java | 34 | ||||
| -rw-r--r-- | startop/view_compiler/dex_testcase_generator.cc | 65 |
4 files changed, 137 insertions, 11 deletions
diff --git a/startop/view_compiler/dex_builder.cc b/startop/view_compiler/dex_builder.cc index 33df6f9c37d7..85357bb79bc0 100644 --- a/startop/view_compiler/dex_builder.cc +++ b/startop/view_compiler/dex_builder.cc @@ -49,6 +49,9 @@ std::ostream& operator<<(std::ostream& out, const Instruction::Op& opcode) { case Instruction::Op::kReturn: out << "kReturn"; return out; + case Instruction::Op::kReturnObject: + out << "kReturnObject"; + return out; case Instruction::Op::kMove: out << "kMove"; return out; @@ -137,6 +140,9 @@ ir::String* DexBuilder::GetOrAddString(const std::string& string) { entry = Alloc<ir::String>(); // +1 for null terminator entry->data = slicer::MemView{buffer.get(), header_length + string.size() + 1}; + ::dex::u4 const new_index = dex_file_->strings_indexes.AllocateIndex(); + dex_file_->strings_map[new_index] = entry; + entry->orig_index = new_index; string_data_.push_back(std::move(buffer)); } return entry; @@ -240,8 +246,9 @@ void MethodBuilder::AddInstruction(Instruction instruction) { void MethodBuilder::BuildReturn() { AddInstruction(Instruction::OpNoArgs(Op::kReturn)); } -void MethodBuilder::BuildReturn(Value src) { - AddInstruction(Instruction::OpWithArgs(Op::kReturn, /*destination=*/{}, src)); +void MethodBuilder::BuildReturn(Value src, bool is_object) { + AddInstruction(Instruction::OpWithArgs( + is_object ? Op::kReturnObject : Op::kReturn, /*destination=*/{}, src)); } void MethodBuilder::BuildConst4(Value target, int value) { @@ -249,6 +256,11 @@ void MethodBuilder::BuildConst4(Value target, int value) { AddInstruction(Instruction::OpWithArgs(Op::kMove, target, Value::Immediate(value))); } +void MethodBuilder::BuildConstString(Value target, const std::string& value) { + const ir::String* const dex_string = dex_->GetOrAddString(value); + AddInstruction(Instruction::OpWithArgs(Op::kMove, target, Value::String(dex_string->orig_index))); +} + void MethodBuilder::EncodeInstructions() { buffer_.clear(); for (const auto& instruction : instructions_) { @@ -259,7 +271,9 @@ void MethodBuilder::EncodeInstructions() { void MethodBuilder::EncodeInstruction(const Instruction& instruction) { switch (instruction.opcode()) { case Instruction::Op::kReturn: - return EncodeReturn(instruction); + return EncodeReturn(instruction, ::art::Instruction::RETURN); + case Instruction::Op::kReturnObject: + return EncodeReturn(instruction, ::art::Instruction::RETURN_OBJECT); case Instruction::Op::kMove: return EncodeMove(instruction); case Instruction::Op::kInvokeVirtual: @@ -271,15 +285,14 @@ void MethodBuilder::EncodeInstruction(const Instruction& instruction) { } } -void MethodBuilder::EncodeReturn(const Instruction& instruction) { - DCHECK_EQ(Instruction::Op::kReturn, instruction.opcode()); +void MethodBuilder::EncodeReturn(const Instruction& instruction, ::art::Instruction::Code opcode) { DCHECK(!instruction.dest().has_value()); if (instruction.args().size() == 0) { buffer_.push_back(art::Instruction::RETURN_VOID); } else { - DCHECK(instruction.args().size() == 1); + DCHECK_EQ(1, instruction.args().size()); size_t source = RegisterValue(instruction.args()[0]); - buffer_.push_back(art::Instruction::RETURN | source << 8); + buffer_.push_back(opcode | source << 8); } } @@ -297,6 +310,12 @@ void MethodBuilder::EncodeMove(const Instruction& instruction) { DCHECK_LT(source.value(), 16); buffer_.push_back(art::Instruction::CONST_4 | (source.value() << 12) | (RegisterValue(*instruction.dest()) << 8)); + } else if (source.is_string()) { + constexpr size_t kMaxRegisters = 256; + DCHECK_LT(RegisterValue(*instruction.dest()), kMaxRegisters); + DCHECK_LT(source.value(), 65536); // make sure we don't need a jumbo string + buffer_.push_back(::art::Instruction::CONST_STRING | (RegisterValue(*instruction.dest()) << 8)); + buffer_.push_back(source.value()); } else { UNIMPLEMENTED(FATAL); } diff --git a/startop/view_compiler/dex_builder.h b/startop/view_compiler/dex_builder.h index 07441518ea32..31414c82e510 100644 --- a/startop/view_compiler/dex_builder.h +++ b/startop/view_compiler/dex_builder.h @@ -110,18 +110,20 @@ class Value { static constexpr Value Local(size_t id) { return Value{id, Kind::kLocalRegister}; } static constexpr Value Parameter(size_t id) { return Value{id, Kind::kParameter}; } static constexpr Value Immediate(size_t value) { return Value{value, Kind::kImmediate}; } + static constexpr Value String(size_t value) { return Value{value, Kind::kString}; } static constexpr Value Label(size_t id) { return Value{id, Kind::kLabel}; } bool is_register() const { return kind_ == Kind::kLocalRegister; } bool is_parameter() const { return kind_ == Kind::kParameter; } bool is_variable() const { return is_register() || is_parameter(); } bool is_immediate() const { return kind_ == Kind::kImmediate; } + bool is_string() const { return kind_ == Kind::kString; } bool is_label() const { return kind_ == Kind::kLabel; } size_t value() const { return value_; } private: - enum class Kind { kLocalRegister, kParameter, kImmediate, kLabel }; + enum class Kind { kLocalRegister, kParameter, kImmediate, kString, kLabel }; const size_t value_; const Kind kind_; @@ -137,7 +139,7 @@ class Instruction { public: // The operation performed by this instruction. These are virtual instructions that do not // correspond exactly to DEX instructions. - enum class Op { kReturn, kMove, kInvokeVirtual, kBindLabel, kBranchEqz }; + enum class Op { kReturn, kReturnObject, kMove, kInvokeVirtual, kBindLabel, kBranchEqz }; //////////////////////// // Named Constructors // @@ -210,16 +212,22 @@ class MethodBuilder { // return-void void BuildReturn(); - void BuildReturn(Value src); + void BuildReturn(Value src, bool is_object = false); // const/4 void BuildConst4(Value target, int value); + void BuildConstString(Value target, const std::string& value); // TODO: add builders for more instructions private: void EncodeInstructions(); void EncodeInstruction(const Instruction& instruction); - void EncodeReturn(const Instruction& instruction); + + // Encodes a return instruction. For instructions with no return value, the opcode field is + // ignored. Otherwise, this specifies which return instruction will be used (return, + // return-object, etc.) + void EncodeReturn(const Instruction& instruction, ::art::Instruction::Code opcode); + void EncodeMove(const Instruction& instruction); void EncodeInvokeVirtual(const Instruction& instruction); void EncodeBranch(art::Instruction::Code op, const Instruction& instruction); diff --git a/startop/view_compiler/dex_builder_test/src/android/startop/test/DexBuilderTest.java b/startop/view_compiler/dex_builder_test/src/android/startop/test/DexBuilderTest.java index 169c63374cb7..2ccdc6d5b4bf 100644 --- a/startop/view_compiler/dex_builder_test/src/android/startop/test/DexBuilderTest.java +++ b/startop/view_compiler/dex_builder_test/src/android/startop/test/DexBuilderTest.java @@ -82,4 +82,38 @@ public class DexBuilderTest { Method method = clazz.getMethod("backwardsBranch"); Assert.assertEquals(2, method.invoke(null)); } + + @Test + public void returnNull() throws Exception { + ClassLoader loader = loadDexFile("simple.dex"); + Class clazz = loader.loadClass("android.startop.test.testcases.SimpleTests"); + Method method = clazz.getMethod("returnNull"); + Assert.assertEquals(null, method.invoke(null)); + } + + @Test + public void makeString() throws Exception { + ClassLoader loader = loadDexFile("simple.dex"); + Class clazz = loader.loadClass("android.startop.test.testcases.SimpleTests"); + Method method = clazz.getMethod("makeString"); + Assert.assertEquals("Hello, World!", method.invoke(null)); + } + + @Test + public void returnStringIfZeroAB() throws Exception { + ClassLoader loader = loadDexFile("simple.dex"); + Class clazz = loader.loadClass("android.startop.test.testcases.SimpleTests"); + Method method = clazz.getMethod("returnStringIfZeroAB", int.class); + Assert.assertEquals("a", method.invoke(null, 0)); + Assert.assertEquals("b", method.invoke(null, 1)); + } + + @Test + public void returnStringIfZeroBA() throws Exception { + ClassLoader loader = loadDexFile("simple.dex"); + Class clazz = loader.loadClass("android.startop.test.testcases.SimpleTests"); + Method method = clazz.getMethod("returnStringIfZeroBA", int.class); + Assert.assertEquals("b", method.invoke(null, 0)); + Assert.assertEquals("a", method.invoke(null, 1)); + } } diff --git a/startop/view_compiler/dex_testcase_generator.cc b/startop/view_compiler/dex_testcase_generator.cc index c521bf2b8ccf..063a0cf6df7e 100644 --- a/startop/view_compiler/dex_testcase_generator.cc +++ b/startop/view_compiler/dex_testcase_generator.cc @@ -138,6 +138,71 @@ void GenerateSimpleTestCases(const string& outdir) { }(backwardsBranch); backwardsBranch.Encode(); + // Test that we can make a null value. Basically: + // + // public static String returnNull() { return null; } + MethodBuilder returnNull{cbuilder.CreateMethod("returnNull", Prototype{string_type})}; + [](MethodBuilder& method) { + Value zero = method.MakeRegister(); + method.BuildConst4(zero, 0); + method.BuildReturn(zero, /*is_object=*/true); + }(returnNull); + returnNull.Encode(); + + // Test that we can make String literals. Basically: + // + // public static String makeString() { return "Hello, World!"; } + MethodBuilder makeString{cbuilder.CreateMethod("makeString", Prototype{string_type})}; + [](MethodBuilder& method) { + Value string = method.MakeRegister(); + method.BuildConstString(string, "Hello, World!"); + method.BuildReturn(string, /*is_object=*/true); + }(makeString); + makeString.Encode(); + + // Make sure strings are sorted correctly. + // + // int returnStringIfZeroAB(int x) { if (x == 0) { return "a"; } else { return "b"; } } + MethodBuilder returnStringIfZeroAB{ + cbuilder.CreateMethod("returnStringIfZeroAB", Prototype{string_type, TypeDescriptor::Int()})}; + [&](MethodBuilder& method) { + Value resultIfZero{method.MakeRegister()}; + Value else_target{method.MakeLabel()}; + method.AddInstruction(Instruction::OpWithArgs( + Instruction::Op::kBranchEqz, /*dest=*/{}, Value::Parameter(0), else_target)); + // else branch + method.BuildConstString(resultIfZero, "b"); + method.AddInstruction( + Instruction::OpWithArgs(Instruction::Op::kReturnObject, /*dest=*/{}, resultIfZero)); + // then branch + method.AddInstruction( + Instruction::OpWithArgs(Instruction::Op::kBindLabel, /*dest=*/{}, else_target)); + method.BuildConstString(resultIfZero, "a"); + method.AddInstruction( + Instruction::OpWithArgs(Instruction::Op::kReturnObject, /*dest=*/{}, resultIfZero)); + method.Encode(); + }(returnStringIfZeroAB); + // int returnStringIfZeroAB(int x) { if (x == 0) { return "b"; } else { return "a"; } } + MethodBuilder returnStringIfZeroBA{ + cbuilder.CreateMethod("returnStringIfZeroBA", Prototype{string_type, TypeDescriptor::Int()})}; + [&](MethodBuilder& method) { + Value resultIfZero{method.MakeRegister()}; + Value else_target{method.MakeLabel()}; + method.AddInstruction(Instruction::OpWithArgs( + Instruction::Op::kBranchEqz, /*dest=*/{}, Value::Parameter(0), else_target)); + // else branch + method.BuildConstString(resultIfZero, "a"); + method.AddInstruction( + Instruction::OpWithArgs(Instruction::Op::kReturnObject, /*dest=*/{}, resultIfZero)); + // then branch + method.AddInstruction( + Instruction::OpWithArgs(Instruction::Op::kBindLabel, /*dest=*/{}, else_target)); + method.BuildConstString(resultIfZero, "b"); + method.AddInstruction( + Instruction::OpWithArgs(Instruction::Op::kReturnObject, /*dest=*/{}, resultIfZero)); + method.Encode(); + }(returnStringIfZeroBA); + slicer::MemView image{dex_file.CreateImage()}; std::ofstream out_file(outdir + "/simple.dex"); out_file.write(image.ptr<const char>(), image.size()); |