Loading startop/view_compiler/dex_builder.cc +62 −2 Original line number Diff line number Diff line Loading @@ -17,7 +17,6 @@ #include "dex_builder.h" #include "dex/descriptors_names.h" #include "dex/dex_instruction.h" #include <fstream> #include <memory> Loading Loading @@ -56,6 +55,12 @@ std::ostream& operator<<(std::ostream& out, const Instruction::Op& opcode) { case Instruction::Op::kInvokeVirtual: out << "kInvokeVirtual"; return out; case Instruction::Op::kBindLabel: out << "kBindLabel"; return out; case Instruction::Op::kBranchEqz: out << "kBranchEqz"; return out; } } Loading Loading @@ -224,6 +229,11 @@ ir::EncodedMethod* MethodBuilder::Encode() { Value MethodBuilder::MakeRegister() { return Value::Local(num_registers_++); } Value MethodBuilder::MakeLabel() { labels_.push_back({}); return Value::Label(labels_.size() - 1); } void MethodBuilder::AddInstruction(Instruction instruction) { instructions_.push_back(instruction); } Loading Loading @@ -254,6 +264,10 @@ void MethodBuilder::EncodeInstruction(const Instruction& instruction) { return EncodeMove(instruction); case Instruction::Op::kInvokeVirtual: return EncodeInvokeVirtual(instruction); case Instruction::Op::kBindLabel: return BindLabel(instruction.args()[0]); case Instruction::Op::kBranchEqz: return EncodeBranch(art::Instruction::IF_EQZ, instruction); } } Loading Loading @@ -307,7 +321,22 @@ void MethodBuilder::EncodeInvokeVirtual(const Instruction& instruction) { } } size_t MethodBuilder::RegisterValue(Value value) const { // Encodes a conditional branch that tests a single argument. void MethodBuilder::EncodeBranch(art::Instruction::Code op, const Instruction& instruction) { const auto& args = instruction.args(); const auto& test_value = args[0]; const auto& branch_target = args[1]; CHECK_EQ(2, args.size()); CHECK(test_value.is_variable()); CHECK(branch_target.is_label()); size_t instruction_offset = buffer_.size(); buffer_.push_back(op | (RegisterValue(test_value) << 8)); size_t field_offset = buffer_.size(); buffer_.push_back(LabelValue(branch_target, instruction_offset, field_offset)); } size_t MethodBuilder::RegisterValue(const Value& value) const { if (value.is_register()) { return value.value(); } else if (value.is_parameter()) { Loading @@ -317,6 +346,37 @@ size_t MethodBuilder::RegisterValue(Value value) const { return 0; } void MethodBuilder::BindLabel(const Value& label_id) { CHECK(label_id.is_label()); LabelData& label = labels_[label_id.value()]; CHECK(!label.bound_address.has_value()); label.bound_address = buffer_.size(); // patch any forward references to this label. for (const auto& ref : label.references) { buffer_[ref.field_offset] = *label.bound_address - ref.instruction_offset; } // No point keeping these around anymore. label.references.clear(); } ::dex::u2 MethodBuilder::LabelValue(const Value& label_id, size_t instruction_offset, size_t field_offset) { CHECK(label_id.is_label()); LabelData& label = labels_[label_id.value()]; // Short-circuit if the label is already bound. if (label.bound_address.has_value()) { return *label.bound_address - instruction_offset; } // Otherwise, save a reference to where we need to back-patch later. label.references.push_front(LabelReference{instruction_offset, field_offset}); return 0; } const MethodDeclData& DexBuilder::GetOrDeclareMethod(TypeDescriptor type, const std::string& name, Prototype prototype) { MethodDeclData& entry = method_id_map_[{type, name, prototype}]; Loading startop/view_compiler/dex_builder.h +34 −3 Original line number Diff line number Diff line Loading @@ -16,12 +16,14 @@ #ifndef DEX_BUILDER_H_ #define DEX_BUILDER_H_ #include <forward_list> #include <map> #include <optional> #include <string> #include <unordered_map> #include <vector> #include "dex/dex_instruction.h" #include "slicer/dex_ir.h" #include "slicer/writer.h" Loading Loading @@ -108,15 +110,18 @@ 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 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_label() const { return kind_ == Kind::kLabel; } size_t value() const { return value_; } private: enum class Kind { kLocalRegister, kParameter, kImmediate }; enum class Kind { kLocalRegister, kParameter, kImmediate, kLabel }; const size_t value_; const Kind kind_; Loading @@ -132,7 +137,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 }; enum class Op { kReturn, kMove, kInvokeVirtual, kBindLabel, kBranchEqz }; //////////////////////// // Named Constructors // Loading Loading @@ -195,6 +200,8 @@ class MethodBuilder { // it's up to the caller to reuse registers as appropriate. Value MakeRegister(); Value MakeLabel(); ///////////////////////////////// // Instruction builder methods // ///////////////////////////////// Loading @@ -215,9 +222,18 @@ class MethodBuilder { void EncodeReturn(const Instruction& instruction); void EncodeMove(const Instruction& instruction); void EncodeInvokeVirtual(const Instruction& instruction); void EncodeBranch(art::Instruction::Code op, const Instruction& instruction); // Converts a register or parameter to its DEX register number. size_t RegisterValue(Value value) const; size_t RegisterValue(const Value& value) const; // Sets a label's address to the current position in the instruction buffer. If there are any // forward references to the label, this function will back-patch them. void BindLabel(const Value& label); // Returns the offset of the label relative to the given instruction offset. If the label is not // bound, a reference will be saved and it will automatically be patched when the label is bound. ::dex::u2 LabelValue(const Value& label, size_t instruction_offset, size_t field_offset); DexBuilder* dex_; ir::Class* class_; Loading @@ -231,6 +247,21 @@ class MethodBuilder { // How many registers we've allocated size_t num_registers_{0}; // Stores information needed to back-patch a label once it is bound. We need to know the start of // the instruction that refers to the label, and the offset to where the actual label value should // go. struct LabelReference { size_t instruction_offset; size_t field_offset; }; struct LabelData { std::optional<size_t> bound_address; std::forward_list<LabelReference> references; }; std::vector<LabelData> labels_; }; // A helper to build class definitions. Loading startop/view_compiler/dex_builder_test/src/android/startop/test/DexBuilderTest.java +17 −0 Original line number Diff line number Diff line Loading @@ -65,4 +65,21 @@ public class DexBuilderTest { Method method = clazz.getMethod("returnStringLength", String.class); Assert.assertEquals(13, method.invoke(null, "Hello, World!")); } @Test public void returnIfZero() throws Exception { ClassLoader loader = loadDexFile("simple.dex"); Class clazz = loader.loadClass("android.startop.test.testcases.SimpleTests"); Method method = clazz.getMethod("returnIfZero", int.class); Assert.assertEquals(5, method.invoke(null, 0)); Assert.assertEquals(3, method.invoke(null, 17)); } @Test public void backwardsBranch() throws Exception { ClassLoader loader = loadDexFile("simple.dex"); Class clazz = loader.loadClass("android.startop.test.testcases.SimpleTests"); Method method = clazz.getMethod("backwardsBranch"); Assert.assertEquals(2, method.invoke(null)); } } startop/view_compiler/dex_testcase_generator.cc +75 −7 Original line number Diff line number Diff line Loading @@ -46,9 +46,11 @@ void GenerateSimpleTestCases(const string& outdir) { // int return5() { return 5; } auto return5{cbuilder.CreateMethod("return5", Prototype{TypeDescriptor::Int()})}; { Value r{return5.MakeRegister()}; return5.BuildConst4(r, 5); return5.BuildReturn(r); } return5.Encode(); // // int returnParam(int x) { return x; } Loading @@ -64,12 +66,78 @@ void GenerateSimpleTestCases(const string& outdir) { auto returnStringLength{ cbuilder.CreateMethod("returnStringLength", Prototype{TypeDescriptor::Int(), string_type})}; { Value result = returnStringLength.MakeRegister(); returnStringLength.AddInstruction( Instruction::InvokeVirtual(string_length.id, result, Value::Parameter(0))); returnStringLength.BuildReturn(result); } returnStringLength.Encode(); // int returnIfZero(int x) { if (x == 0) { return 5; } else { return 3; } } MethodBuilder returnIfZero{cbuilder.CreateMethod( "returnIfZero", Prototype{TypeDescriptor::Int(), TypeDescriptor::Int()})}; { Value resultIfZero{returnIfZero.MakeRegister()}; Value else_target{returnIfZero.MakeLabel()}; returnIfZero.AddInstruction(Instruction::OpWithArgs( Instruction::Op::kBranchEqz, /*dest=*/{}, Value::Parameter(0), else_target)); // else branch returnIfZero.BuildConst4(resultIfZero, 3); returnIfZero.AddInstruction( Instruction::OpWithArgs(Instruction::Op::kReturn, /*dest=*/{}, resultIfZero)); // then branch returnIfZero.AddInstruction( Instruction::OpWithArgs(Instruction::Op::kBindLabel, /*dest=*/{}, else_target)); returnIfZero.BuildConst4(resultIfZero, 5); returnIfZero.AddInstruction( Instruction::OpWithArgs(Instruction::Op::kReturn, /*dest=*/{}, resultIfZero)); } returnIfZero.Encode(); // Make sure backwards branches work too. // // Pseudo code for test: // { // zero = 0; // result = 1; // if (zero == 0) goto B; // A: // return result; // B: // result = 2; // if (zero == 0) goto A; // result = 3; // return result; // } // If it runs correctly, this test should return 2. MethodBuilder backwardsBranch{ cbuilder.CreateMethod("backwardsBranch", Prototype{TypeDescriptor::Int()})}; [](MethodBuilder& method) { Value zero = method.MakeRegister(); Value result = method.MakeRegister(); Value labelA = method.MakeLabel(); Value labelB = method.MakeLabel(); method.BuildConst4(zero, 0); method.BuildConst4(result, 1); method.AddInstruction( Instruction::OpWithArgs(Instruction::Op::kBranchEqz, /*dest=*/{}, zero, labelB)); method.AddInstruction( Instruction::OpWithArgs(Instruction::Op::kBindLabel, /*dest=*/{}, labelA)); method.BuildReturn(result); method.AddInstruction( Instruction::OpWithArgs(Instruction::Op::kBindLabel, /*dest=*/{}, labelB)); method.BuildConst4(result, 2); method.AddInstruction( Instruction::OpWithArgs(Instruction::Op::kBranchEqz, /*dest=*/{}, zero, labelA)); method.BuildConst4(result, 3); method.BuildReturn(result); }(backwardsBranch); backwardsBranch.Encode(); slicer::MemView image{dex_file.CreateImage()}; std::ofstream out_file(outdir + "/simple.dex"); out_file.write(image.ptr<const char>(), image.size()); Loading Loading
startop/view_compiler/dex_builder.cc +62 −2 Original line number Diff line number Diff line Loading @@ -17,7 +17,6 @@ #include "dex_builder.h" #include "dex/descriptors_names.h" #include "dex/dex_instruction.h" #include <fstream> #include <memory> Loading Loading @@ -56,6 +55,12 @@ std::ostream& operator<<(std::ostream& out, const Instruction::Op& opcode) { case Instruction::Op::kInvokeVirtual: out << "kInvokeVirtual"; return out; case Instruction::Op::kBindLabel: out << "kBindLabel"; return out; case Instruction::Op::kBranchEqz: out << "kBranchEqz"; return out; } } Loading Loading @@ -224,6 +229,11 @@ ir::EncodedMethod* MethodBuilder::Encode() { Value MethodBuilder::MakeRegister() { return Value::Local(num_registers_++); } Value MethodBuilder::MakeLabel() { labels_.push_back({}); return Value::Label(labels_.size() - 1); } void MethodBuilder::AddInstruction(Instruction instruction) { instructions_.push_back(instruction); } Loading Loading @@ -254,6 +264,10 @@ void MethodBuilder::EncodeInstruction(const Instruction& instruction) { return EncodeMove(instruction); case Instruction::Op::kInvokeVirtual: return EncodeInvokeVirtual(instruction); case Instruction::Op::kBindLabel: return BindLabel(instruction.args()[0]); case Instruction::Op::kBranchEqz: return EncodeBranch(art::Instruction::IF_EQZ, instruction); } } Loading Loading @@ -307,7 +321,22 @@ void MethodBuilder::EncodeInvokeVirtual(const Instruction& instruction) { } } size_t MethodBuilder::RegisterValue(Value value) const { // Encodes a conditional branch that tests a single argument. void MethodBuilder::EncodeBranch(art::Instruction::Code op, const Instruction& instruction) { const auto& args = instruction.args(); const auto& test_value = args[0]; const auto& branch_target = args[1]; CHECK_EQ(2, args.size()); CHECK(test_value.is_variable()); CHECK(branch_target.is_label()); size_t instruction_offset = buffer_.size(); buffer_.push_back(op | (RegisterValue(test_value) << 8)); size_t field_offset = buffer_.size(); buffer_.push_back(LabelValue(branch_target, instruction_offset, field_offset)); } size_t MethodBuilder::RegisterValue(const Value& value) const { if (value.is_register()) { return value.value(); } else if (value.is_parameter()) { Loading @@ -317,6 +346,37 @@ size_t MethodBuilder::RegisterValue(Value value) const { return 0; } void MethodBuilder::BindLabel(const Value& label_id) { CHECK(label_id.is_label()); LabelData& label = labels_[label_id.value()]; CHECK(!label.bound_address.has_value()); label.bound_address = buffer_.size(); // patch any forward references to this label. for (const auto& ref : label.references) { buffer_[ref.field_offset] = *label.bound_address - ref.instruction_offset; } // No point keeping these around anymore. label.references.clear(); } ::dex::u2 MethodBuilder::LabelValue(const Value& label_id, size_t instruction_offset, size_t field_offset) { CHECK(label_id.is_label()); LabelData& label = labels_[label_id.value()]; // Short-circuit if the label is already bound. if (label.bound_address.has_value()) { return *label.bound_address - instruction_offset; } // Otherwise, save a reference to where we need to back-patch later. label.references.push_front(LabelReference{instruction_offset, field_offset}); return 0; } const MethodDeclData& DexBuilder::GetOrDeclareMethod(TypeDescriptor type, const std::string& name, Prototype prototype) { MethodDeclData& entry = method_id_map_[{type, name, prototype}]; Loading
startop/view_compiler/dex_builder.h +34 −3 Original line number Diff line number Diff line Loading @@ -16,12 +16,14 @@ #ifndef DEX_BUILDER_H_ #define DEX_BUILDER_H_ #include <forward_list> #include <map> #include <optional> #include <string> #include <unordered_map> #include <vector> #include "dex/dex_instruction.h" #include "slicer/dex_ir.h" #include "slicer/writer.h" Loading Loading @@ -108,15 +110,18 @@ 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 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_label() const { return kind_ == Kind::kLabel; } size_t value() const { return value_; } private: enum class Kind { kLocalRegister, kParameter, kImmediate }; enum class Kind { kLocalRegister, kParameter, kImmediate, kLabel }; const size_t value_; const Kind kind_; Loading @@ -132,7 +137,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 }; enum class Op { kReturn, kMove, kInvokeVirtual, kBindLabel, kBranchEqz }; //////////////////////// // Named Constructors // Loading Loading @@ -195,6 +200,8 @@ class MethodBuilder { // it's up to the caller to reuse registers as appropriate. Value MakeRegister(); Value MakeLabel(); ///////////////////////////////// // Instruction builder methods // ///////////////////////////////// Loading @@ -215,9 +222,18 @@ class MethodBuilder { void EncodeReturn(const Instruction& instruction); void EncodeMove(const Instruction& instruction); void EncodeInvokeVirtual(const Instruction& instruction); void EncodeBranch(art::Instruction::Code op, const Instruction& instruction); // Converts a register or parameter to its DEX register number. size_t RegisterValue(Value value) const; size_t RegisterValue(const Value& value) const; // Sets a label's address to the current position in the instruction buffer. If there are any // forward references to the label, this function will back-patch them. void BindLabel(const Value& label); // Returns the offset of the label relative to the given instruction offset. If the label is not // bound, a reference will be saved and it will automatically be patched when the label is bound. ::dex::u2 LabelValue(const Value& label, size_t instruction_offset, size_t field_offset); DexBuilder* dex_; ir::Class* class_; Loading @@ -231,6 +247,21 @@ class MethodBuilder { // How many registers we've allocated size_t num_registers_{0}; // Stores information needed to back-patch a label once it is bound. We need to know the start of // the instruction that refers to the label, and the offset to where the actual label value should // go. struct LabelReference { size_t instruction_offset; size_t field_offset; }; struct LabelData { std::optional<size_t> bound_address; std::forward_list<LabelReference> references; }; std::vector<LabelData> labels_; }; // A helper to build class definitions. Loading
startop/view_compiler/dex_builder_test/src/android/startop/test/DexBuilderTest.java +17 −0 Original line number Diff line number Diff line Loading @@ -65,4 +65,21 @@ public class DexBuilderTest { Method method = clazz.getMethod("returnStringLength", String.class); Assert.assertEquals(13, method.invoke(null, "Hello, World!")); } @Test public void returnIfZero() throws Exception { ClassLoader loader = loadDexFile("simple.dex"); Class clazz = loader.loadClass("android.startop.test.testcases.SimpleTests"); Method method = clazz.getMethod("returnIfZero", int.class); Assert.assertEquals(5, method.invoke(null, 0)); Assert.assertEquals(3, method.invoke(null, 17)); } @Test public void backwardsBranch() throws Exception { ClassLoader loader = loadDexFile("simple.dex"); Class clazz = loader.loadClass("android.startop.test.testcases.SimpleTests"); Method method = clazz.getMethod("backwardsBranch"); Assert.assertEquals(2, method.invoke(null)); } }
startop/view_compiler/dex_testcase_generator.cc +75 −7 Original line number Diff line number Diff line Loading @@ -46,9 +46,11 @@ void GenerateSimpleTestCases(const string& outdir) { // int return5() { return 5; } auto return5{cbuilder.CreateMethod("return5", Prototype{TypeDescriptor::Int()})}; { Value r{return5.MakeRegister()}; return5.BuildConst4(r, 5); return5.BuildReturn(r); } return5.Encode(); // // int returnParam(int x) { return x; } Loading @@ -64,12 +66,78 @@ void GenerateSimpleTestCases(const string& outdir) { auto returnStringLength{ cbuilder.CreateMethod("returnStringLength", Prototype{TypeDescriptor::Int(), string_type})}; { Value result = returnStringLength.MakeRegister(); returnStringLength.AddInstruction( Instruction::InvokeVirtual(string_length.id, result, Value::Parameter(0))); returnStringLength.BuildReturn(result); } returnStringLength.Encode(); // int returnIfZero(int x) { if (x == 0) { return 5; } else { return 3; } } MethodBuilder returnIfZero{cbuilder.CreateMethod( "returnIfZero", Prototype{TypeDescriptor::Int(), TypeDescriptor::Int()})}; { Value resultIfZero{returnIfZero.MakeRegister()}; Value else_target{returnIfZero.MakeLabel()}; returnIfZero.AddInstruction(Instruction::OpWithArgs( Instruction::Op::kBranchEqz, /*dest=*/{}, Value::Parameter(0), else_target)); // else branch returnIfZero.BuildConst4(resultIfZero, 3); returnIfZero.AddInstruction( Instruction::OpWithArgs(Instruction::Op::kReturn, /*dest=*/{}, resultIfZero)); // then branch returnIfZero.AddInstruction( Instruction::OpWithArgs(Instruction::Op::kBindLabel, /*dest=*/{}, else_target)); returnIfZero.BuildConst4(resultIfZero, 5); returnIfZero.AddInstruction( Instruction::OpWithArgs(Instruction::Op::kReturn, /*dest=*/{}, resultIfZero)); } returnIfZero.Encode(); // Make sure backwards branches work too. // // Pseudo code for test: // { // zero = 0; // result = 1; // if (zero == 0) goto B; // A: // return result; // B: // result = 2; // if (zero == 0) goto A; // result = 3; // return result; // } // If it runs correctly, this test should return 2. MethodBuilder backwardsBranch{ cbuilder.CreateMethod("backwardsBranch", Prototype{TypeDescriptor::Int()})}; [](MethodBuilder& method) { Value zero = method.MakeRegister(); Value result = method.MakeRegister(); Value labelA = method.MakeLabel(); Value labelB = method.MakeLabel(); method.BuildConst4(zero, 0); method.BuildConst4(result, 1); method.AddInstruction( Instruction::OpWithArgs(Instruction::Op::kBranchEqz, /*dest=*/{}, zero, labelB)); method.AddInstruction( Instruction::OpWithArgs(Instruction::Op::kBindLabel, /*dest=*/{}, labelA)); method.BuildReturn(result); method.AddInstruction( Instruction::OpWithArgs(Instruction::Op::kBindLabel, /*dest=*/{}, labelB)); method.BuildConst4(result, 2); method.AddInstruction( Instruction::OpWithArgs(Instruction::Op::kBranchEqz, /*dest=*/{}, zero, labelA)); method.BuildConst4(result, 3); method.BuildReturn(result); }(backwardsBranch); backwardsBranch.Encode(); slicer::MemView image{dex_file.CreateImage()}; std::ofstream out_file(outdir + "/simple.dex"); out_file.write(image.ptr<const char>(), image.size()); Loading