Loading startop/view_compiler/dex_builder.cc +121 −37 Original line number Diff line number Diff line Loading @@ -21,8 +21,6 @@ #include <fstream> #include <memory> #define DCHECK_NOT_NULL(p) DCHECK((p) != nullptr) namespace startop { namespace dex { Loading @@ -32,6 +30,8 @@ using std::string; using ::dex::kAccPublic; using Op = Instruction::Op; using Opcode = ::art::Instruction::Code; const TypeDescriptor TypeDescriptor::Int() { return TypeDescriptor{"I"}; }; const TypeDescriptor TypeDescriptor::Void() { return TypeDescriptor{"V"}; }; Loading @@ -42,6 +42,23 @@ constexpr uint8_t kDexFileMagic[]{0x64, 0x65, 0x78, 0x0a, 0x30, 0x33, 0x38, 0x00 // Strings lengths can be 32 bits long, but encoded as LEB128 this can take up to five bytes. constexpr size_t kMaxEncodedStringLength{5}; // Converts invoke-* to invoke-*/range constexpr Opcode InvokeToInvokeRange(Opcode opcode) { switch (opcode) { case ::art::Instruction::INVOKE_VIRTUAL: return ::art::Instruction::INVOKE_VIRTUAL_RANGE; case ::art::Instruction::INVOKE_DIRECT: return ::art::Instruction::INVOKE_DIRECT_RANGE; case ::art::Instruction::INVOKE_STATIC: return ::art::Instruction::INVOKE_STATIC_RANGE; case ::art::Instruction::INVOKE_INTERFACE: return ::art::Instruction::INVOKE_INTERFACE_RANGE; default: LOG(FATAL) << opcode << " is not a recognized invoke opcode."; UNREACHABLE(); } } } // namespace std::ostream& operator<<(std::ostream& out, const Instruction::Op& opcode) { Loading @@ -55,6 +72,9 @@ std::ostream& operator<<(std::ostream& out, const Instruction::Op& opcode) { case Instruction::Op::kMove: out << "kMove"; return out; case Instruction::Op::kMoveObject: out << "kMoveObject"; return out; case Instruction::Op::kInvokeVirtual: out << "kInvokeVirtual"; return out; Loading Loading @@ -233,6 +253,11 @@ std::string Prototype::Shorty() const { return shorty; } const TypeDescriptor& Prototype::ArgType(size_t index) const { CHECK_LT(index, param_types_.size()); return param_types_[index]; } ClassBuilder::ClassBuilder(DexBuilder* parent, const std::string& name, ir::Class* class_def) : parent_(parent), type_descriptor_{TypeDescriptor::FromClassname(name)}, class_(class_def) {} Loading @@ -257,10 +282,10 @@ ir::EncodedMethod* MethodBuilder::Encode() { method->access_flags = kAccPublic | ::dex::kAccStatic; auto* code = dex_->Alloc<ir::Code>(); DCHECK_NOT_NULL(decl_->prototype); CHECK(decl_->prototype != nullptr); size_t const num_args = decl_->prototype->param_types != nullptr ? decl_->prototype->param_types->types.size() : 0; code->registers = num_registers_ + num_args; code->registers = num_registers_ + num_args + kMaxScratchRegisters; code->ins_count = num_args; EncodeInstructions(); code->instructions = slicer::ArrayView<const ::dex::u2>(buffer_.data(), buffer_.size()); Loading Loading @@ -292,7 +317,7 @@ void MethodBuilder::BuildReturn(Value src, bool is_object) { } void MethodBuilder::BuildConst4(Value target, int value) { DCHECK_LT(value, 16); CHECK_LT(value, 16); AddInstruction(Instruction::OpWithArgs(Op::kMove, target, Value::Immediate(value))); } Loading @@ -315,6 +340,7 @@ void MethodBuilder::EncodeInstruction(const Instruction& instruction) { case Instruction::Op::kReturnObject: return EncodeReturn(instruction, ::art::Instruction::RETURN_OBJECT); case Instruction::Op::kMove: case Instruction::Op::kMoveObject: return EncodeMove(instruction); case Instruction::Op::kInvokeVirtual: return EncodeInvoke(instruction, art::Instruction::INVOKE_VIRTUAL); Loading @@ -338,33 +364,43 @@ void MethodBuilder::EncodeInstruction(const Instruction& instruction) { } void MethodBuilder::EncodeReturn(const Instruction& instruction, ::art::Instruction::Code opcode) { DCHECK(!instruction.dest().has_value()); CHECK(!instruction.dest().has_value()); if (instruction.args().size() == 0) { Encode10x(art::Instruction::RETURN_VOID); } else { DCHECK_EQ(1, instruction.args().size()); CHECK_EQ(1, instruction.args().size()); size_t source = RegisterValue(instruction.args()[0]); Encode11x(opcode, source); } } void MethodBuilder::EncodeMove(const Instruction& instruction) { DCHECK_EQ(Instruction::Op::kMove, instruction.opcode()); DCHECK(instruction.dest().has_value()); DCHECK(instruction.dest()->is_register() || instruction.dest()->is_parameter()); DCHECK_EQ(1, instruction.args().size()); CHECK(Instruction::Op::kMove == instruction.opcode() || Instruction::Op::kMoveObject == instruction.opcode()); CHECK(instruction.dest().has_value()); CHECK(instruction.dest()->is_variable()); CHECK_EQ(1, instruction.args().size()); const Value& source = instruction.args()[0]; if (source.is_immediate()) { // TODO: support more registers DCHECK_LT(RegisterValue(*instruction.dest()), 16); CHECK_LT(RegisterValue(*instruction.dest()), 16); Encode11n(art::Instruction::CONST_4, RegisterValue(*instruction.dest()), source.value()); } 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 CHECK_LT(RegisterValue(*instruction.dest()), kMaxRegisters); CHECK_LT(source.value(), 65536); // make sure we don't need a jumbo string Encode21c(::art::Instruction::CONST_STRING, RegisterValue(*instruction.dest()), source.value()); } else if (source.is_variable()) { // For the moment, we only use this when we need to reshuffle registers for // an invoke instruction, meaning we are too big for the 4-bit version. // We'll err on the side of caution and always generate the 16-bit form of // the instruction. Opcode opcode = instruction.opcode() == Instruction::Op::kMove ? ::art::Instruction::MOVE_16 : ::art::Instruction::MOVE_OBJECT_16; Encode32x(opcode, RegisterValue(*instruction.dest()), RegisterValue(source)); } else { UNIMPLEMENTED(FATAL); } Loading @@ -373,14 +409,52 @@ void MethodBuilder::EncodeMove(const Instruction& instruction) { void MethodBuilder::EncodeInvoke(const Instruction& instruction, ::art::Instruction::Code opcode) { constexpr size_t kMaxArgs = 5; // Currently, we only support up to 5 arguments. CHECK_LE(instruction.args().size(), kMaxArgs); uint8_t arguments[kMaxArgs]{}; bool has_long_args = false; for (size_t i = 0; i < instruction.args().size(); ++i) { CHECK(instruction.args()[i].is_variable()); arguments[i] = RegisterValue(instruction.args()[i]); if (!IsShortRegister(arguments[i])) { has_long_args = true; } } if (has_long_args) { // Some of the registers don't fit in the four bit short form of the invoke // instruction, so we need to do an invoke/range. To do this, we need to // first move all the arguments into contiguous temporary registers. std::array<Value, kMaxArgs> scratch{GetScratchRegisters<kMaxArgs>()}; const auto& prototype = dex_->GetPrototypeByMethodId(instruction.method_id()); CHECK(prototype.has_value()); for (size_t i = 0; i < instruction.args().size(); ++i) { Instruction::Op move_op; if (opcode == ::art::Instruction::INVOKE_VIRTUAL || opcode == ::art::Instruction::INVOKE_DIRECT) { // In this case, there is an implicit `this` argument, which is always an object. if (i == 0) { move_op = Instruction::Op::kMoveObject; } else { move_op = prototype->ArgType(i - 1).is_object() ? Instruction::Op::kMoveObject : Instruction::Op::kMove; } } else { move_op = prototype->ArgType(i).is_object() ? Instruction::Op::kMoveObject : Instruction::Op::kMove; } EncodeMove(Instruction::OpWithArgs(move_op, scratch[i], instruction.args()[i])); } Encode3rc(InvokeToInvokeRange(opcode), instruction.args().size(), instruction.method_id(), RegisterValue(scratch[0])); } else { Encode35c(opcode, instruction.args().size(), instruction.method_id(), Loading @@ -389,6 +463,7 @@ void MethodBuilder::EncodeInvoke(const Instruction& instruction, ::art::Instruct arguments[2], arguments[3], arguments[4]); } // If there is a return value, add a move-result instruction if (instruction.dest().has_value()) { Loading Loading @@ -416,26 +491,26 @@ void MethodBuilder::EncodeBranch(art::Instruction::Code op, const Instruction& i } void MethodBuilder::EncodeNew(const Instruction& instruction) { DCHECK_EQ(Instruction::Op::kNew, instruction.opcode()); DCHECK(instruction.dest().has_value()); DCHECK(instruction.dest()->is_variable()); DCHECK_EQ(1, instruction.args().size()); CHECK_EQ(Instruction::Op::kNew, instruction.opcode()); CHECK(instruction.dest().has_value()); CHECK(instruction.dest()->is_variable()); CHECK_EQ(1, instruction.args().size()); const Value& type = instruction.args()[0]; DCHECK_LT(RegisterValue(*instruction.dest()), 256); DCHECK(type.is_type()); CHECK_LT(RegisterValue(*instruction.dest()), 256); CHECK(type.is_type()); Encode21c(::art::Instruction::NEW_INSTANCE, RegisterValue(*instruction.dest()), type.value()); } void MethodBuilder::EncodeCast(const Instruction& instruction) { DCHECK_EQ(Instruction::Op::kCheckCast, instruction.opcode()); DCHECK(instruction.dest().has_value()); DCHECK(instruction.dest()->is_variable()); DCHECK_EQ(1, instruction.args().size()); CHECK_EQ(Instruction::Op::kCheckCast, instruction.opcode()); CHECK(instruction.dest().has_value()); CHECK(instruction.dest()->is_variable()); CHECK_EQ(1, instruction.args().size()); const Value& type = instruction.args()[0]; DCHECK_LT(RegisterValue(*instruction.dest()), 256); DCHECK(type.is_type()); CHECK_LT(RegisterValue(*instruction.dest()), 256); CHECK(type.is_type()); Encode21c(::art::Instruction::CHECK_CAST, RegisterValue(*instruction.dest()), type.value()); } Loading @@ -443,9 +518,9 @@ size_t MethodBuilder::RegisterValue(const Value& value) const { if (value.is_register()) { return value.value(); } else if (value.is_parameter()) { return value.value() + num_registers_; return value.value() + num_registers_ + kMaxScratchRegisters; } DCHECK(false && "Must be either a parameter or a register"); CHECK(false && "Must be either a parameter or a register"); return 0; } Loading Loading @@ -498,7 +573,7 @@ const MethodDeclData& DexBuilder::GetOrDeclareMethod(TypeDescriptor type, const // update the index -> ir node map (see tools/dexter/slicer/dex_ir_builder.cc) auto new_index = dex_file_->methods_indexes.AllocateIndex(); auto& ir_node = dex_file_->methods_map[new_index]; SLICER_CHECK(ir_node == nullptr); CHECK(ir_node == nullptr); ir_node = decl; decl->orig_index = decl->index = new_index; Loading @@ -508,6 +583,15 @@ const MethodDeclData& DexBuilder::GetOrDeclareMethod(TypeDescriptor type, const return entry; } std::optional<const Prototype> DexBuilder::GetPrototypeByMethodId(size_t method_id) const { for (const auto& entry : method_id_map_) { if (entry.second.id == method_id) { return entry.first.prototype; } } return {}; } ir::Proto* DexBuilder::GetOrEncodeProto(Prototype prototype) { ir::Proto*& ir_proto = proto_map_[prototype]; if (ir_proto == nullptr) { Loading startop/view_compiler/dex_builder.h +50 −9 Original line number Diff line number Diff line Loading @@ -16,6 +16,7 @@ #ifndef DEX_BUILDER_H_ #define DEX_BUILDER_H_ #include <array> #include <forward_list> #include <map> #include <optional> Loading Loading @@ -70,6 +71,8 @@ class TypeDescriptor { // Return the shorty descriptor, such as I or L std::string short_descriptor() const { return descriptor().substr(0, 1); } bool is_object() const { return short_descriptor() == "L"; } bool operator<(const TypeDescriptor& rhs) const { return descriptor_ < rhs.descriptor_; } private: Loading @@ -92,6 +95,8 @@ class Prototype { // Get the shorty descriptor, such as VII for (Int, Int) -> Void std::string Shorty() const; const TypeDescriptor& ArgType(size_t index) const; bool operator<(const Prototype& rhs) const { return std::make_tuple(return_type_, param_types_) < std::make_tuple(rhs.return_type_, rhs.param_types_); Loading Loading @@ -124,11 +129,13 @@ class Value { size_t value() const { return value_; } constexpr Value() : value_{0}, kind_{Kind::kInvalid} {} private: enum class Kind { kLocalRegister, kParameter, kImmediate, kString, kLabel, kType }; enum class Kind { kInvalid, kLocalRegister, kParameter, kImmediate, kString, kLabel, kType }; const size_t value_; const Kind kind_; size_t value_; Kind kind_; constexpr Value(size_t value, Kind kind) : value_{value}, kind_{kind} {} }; Loading @@ -151,6 +158,7 @@ class Instruction { kInvokeStatic, kInvokeVirtual, kMove, kMoveObject, kNew, kReturn, kReturnObject, Loading @@ -172,7 +180,7 @@ class Instruction { // A cast instruction. Basically, `(type)val` static inline Instruction Cast(Value val, Value type) { DCHECK(type.is_type()); CHECK(type.is_type()); return OpWithArgs(Op::kCheckCast, val, type); } Loading Loading @@ -343,21 +351,48 @@ class MethodBuilder { buffer_.push_back(b); } inline void Encode32x(art::Instruction::Code opcode, uint16_t a, uint16_t b) { buffer_.push_back(opcode); buffer_.push_back(a); buffer_.push_back(b); } inline void Encode35c(art::Instruction::Code opcode, size_t a, uint16_t b, uint8_t c, uint8_t d, uint8_t e, uint8_t f, uint8_t g) { // a|g|op|bbbb|f|e|d|c CHECK_LE(a, 5); CHECK_LT(c, 16); CHECK_LT(d, 16); CHECK_LT(e, 16); CHECK_LT(f, 16); CHECK_LT(g, 16); CHECK(IsShortRegister(c)); CHECK(IsShortRegister(d)); CHECK(IsShortRegister(e)); CHECK(IsShortRegister(f)); CHECK(IsShortRegister(g)); buffer_.push_back((a << 12) | (g << 8) | opcode); buffer_.push_back(b); buffer_.push_back((f << 12) | (e << 8) | (d << 4) | c); } inline void Encode3rc(art::Instruction::Code opcode, size_t a, uint16_t b, uint16_t c) { CHECK_LE(a, 255); buffer_.push_back((a << 8) | opcode); buffer_.push_back(b); buffer_.push_back(c); } static constexpr bool IsShortRegister(size_t register_value) { return register_value < 16; } // Returns an array of num_regs scratch registers. These are guaranteed to be // contiguous, so they are suitable for the invoke-*/range instructions. template <int num_regs> std::array<Value, num_regs> GetScratchRegisters() const { static_assert(num_regs <= kMaxScratchRegisters); std::array<Value, num_regs> regs; for (size_t i = 0; i < num_regs; ++i) { regs[i] = std::move(Value::Local(num_registers_ + i)); } return regs; } // Converts a register or parameter to its DEX register number. size_t RegisterValue(const Value& value) const; Loading @@ -379,6 +414,10 @@ class MethodBuilder { // A buffer to hold instructions that have been encoded. std::vector<::dex::u2> buffer_; // We create some scratch registers for when we have to shuffle registers // around to make legal DEX code. static constexpr size_t kMaxScratchRegisters = 5; // How many registers we've allocated size_t num_registers_{0}; Loading Loading @@ -447,6 +486,8 @@ class DexBuilder { const MethodDeclData& GetOrDeclareMethod(TypeDescriptor type, const std::string& name, Prototype prototype); std::optional<const Prototype> GetPrototypeByMethodId(size_t method_id) const; private: // Looks up the ir::Proto* corresponding to this given prototype, or creates one if it does not // exist. Loading startop/view_compiler/dex_builder_test.cc +38 −0 Original line number Diff line number Diff line Loading @@ -140,3 +140,41 @@ TEST(DexBuilderTest, VerifyDexCallStringLength) { EXPECT_TRUE(EncodeAndVerify(&dex_file)); } // Write out and verify a DEX file that corresponds to: // // package dextest; // public class DexTest { // public static int foo(String s) { return s.length(); } // } TEST(DexBuilderTest, VerifyDexCallManyRegisters) { DexBuilder dex_file; auto cbuilder{dex_file.MakeClass("dextest.DexTest")}; MethodBuilder method{cbuilder.CreateMethod( "foo", Prototype{TypeDescriptor::Int()})}; Value result = method.MakeRegister(); // Make a bunch of registers for (size_t i = 0; i < 25; ++i) { method.MakeRegister(); } // Now load a string literal into a register Value string_val = method.MakeRegister(); method.BuildConstString(string_val, "foo"); MethodDeclData string_length = dex_file.GetOrDeclareMethod(TypeDescriptor::FromClassname("java.lang.String"), "length", Prototype{TypeDescriptor::Int()}); method.AddInstruction(Instruction::InvokeVirtual(string_length.id, result, string_val)); method.BuildReturn(result); method.Encode(); EXPECT_TRUE(EncodeAndVerify(&dex_file)); } startop/view_compiler/dex_builder_test/Android.bp +12 −2 Original line number Diff line number Diff line Loading @@ -15,7 +15,7 @@ // genrule { name: "generate_compiled_layout", name: "generate_compiled_layout1", tools: [":viewcompiler"], cmd: "$(location :viewcompiler) $(in) --dex --out $(out) --package android.startop.test", srcs: ["res/layout/layout1.xml"], Loading @@ -24,6 +24,16 @@ genrule { ], } genrule { name: "generate_compiled_layout2", tools: [":viewcompiler"], cmd: "$(location :viewcompiler) $(in) --dex --out $(out) --package android.startop.test", srcs: ["res/layout/layout2.xml"], out: [ "layout2.dex", ], } android_test { name: "dex-builder-test", srcs: [ Loading @@ -31,7 +41,7 @@ android_test { "src/android/startop/test/LayoutCompilerTest.java", ], sdk_version: "current", data: [":generate_dex_testcases", ":generate_compiled_layout"], data: [":generate_dex_testcases", ":generate_compiled_layout1", ":generate_compiled_layout2"], static_libs: [ "android-support-test", "guava", Loading startop/view_compiler/dex_builder_test/AndroidTest.xml +1 −0 Original line number Diff line number Diff line Loading @@ -26,6 +26,7 @@ <option name="push" value="trivial.dex->/data/local/tmp/dex-builder-test/trivial.dex" /> <option name="push" value="simple.dex->/data/local/tmp/dex-builder-test/simple.dex" /> <option name="push" value="layout1.dex->/data/local/tmp/dex-builder-test/layout1.dex" /> <option name="push" value="layout2.dex->/data/local/tmp/dex-builder-test/layout2.dex" /> </target_preparer> <test class="com.android.tradefed.testtype.AndroidJUnitTest" > Loading Loading
startop/view_compiler/dex_builder.cc +121 −37 Original line number Diff line number Diff line Loading @@ -21,8 +21,6 @@ #include <fstream> #include <memory> #define DCHECK_NOT_NULL(p) DCHECK((p) != nullptr) namespace startop { namespace dex { Loading @@ -32,6 +30,8 @@ using std::string; using ::dex::kAccPublic; using Op = Instruction::Op; using Opcode = ::art::Instruction::Code; const TypeDescriptor TypeDescriptor::Int() { return TypeDescriptor{"I"}; }; const TypeDescriptor TypeDescriptor::Void() { return TypeDescriptor{"V"}; }; Loading @@ -42,6 +42,23 @@ constexpr uint8_t kDexFileMagic[]{0x64, 0x65, 0x78, 0x0a, 0x30, 0x33, 0x38, 0x00 // Strings lengths can be 32 bits long, but encoded as LEB128 this can take up to five bytes. constexpr size_t kMaxEncodedStringLength{5}; // Converts invoke-* to invoke-*/range constexpr Opcode InvokeToInvokeRange(Opcode opcode) { switch (opcode) { case ::art::Instruction::INVOKE_VIRTUAL: return ::art::Instruction::INVOKE_VIRTUAL_RANGE; case ::art::Instruction::INVOKE_DIRECT: return ::art::Instruction::INVOKE_DIRECT_RANGE; case ::art::Instruction::INVOKE_STATIC: return ::art::Instruction::INVOKE_STATIC_RANGE; case ::art::Instruction::INVOKE_INTERFACE: return ::art::Instruction::INVOKE_INTERFACE_RANGE; default: LOG(FATAL) << opcode << " is not a recognized invoke opcode."; UNREACHABLE(); } } } // namespace std::ostream& operator<<(std::ostream& out, const Instruction::Op& opcode) { Loading @@ -55,6 +72,9 @@ std::ostream& operator<<(std::ostream& out, const Instruction::Op& opcode) { case Instruction::Op::kMove: out << "kMove"; return out; case Instruction::Op::kMoveObject: out << "kMoveObject"; return out; case Instruction::Op::kInvokeVirtual: out << "kInvokeVirtual"; return out; Loading Loading @@ -233,6 +253,11 @@ std::string Prototype::Shorty() const { return shorty; } const TypeDescriptor& Prototype::ArgType(size_t index) const { CHECK_LT(index, param_types_.size()); return param_types_[index]; } ClassBuilder::ClassBuilder(DexBuilder* parent, const std::string& name, ir::Class* class_def) : parent_(parent), type_descriptor_{TypeDescriptor::FromClassname(name)}, class_(class_def) {} Loading @@ -257,10 +282,10 @@ ir::EncodedMethod* MethodBuilder::Encode() { method->access_flags = kAccPublic | ::dex::kAccStatic; auto* code = dex_->Alloc<ir::Code>(); DCHECK_NOT_NULL(decl_->prototype); CHECK(decl_->prototype != nullptr); size_t const num_args = decl_->prototype->param_types != nullptr ? decl_->prototype->param_types->types.size() : 0; code->registers = num_registers_ + num_args; code->registers = num_registers_ + num_args + kMaxScratchRegisters; code->ins_count = num_args; EncodeInstructions(); code->instructions = slicer::ArrayView<const ::dex::u2>(buffer_.data(), buffer_.size()); Loading Loading @@ -292,7 +317,7 @@ void MethodBuilder::BuildReturn(Value src, bool is_object) { } void MethodBuilder::BuildConst4(Value target, int value) { DCHECK_LT(value, 16); CHECK_LT(value, 16); AddInstruction(Instruction::OpWithArgs(Op::kMove, target, Value::Immediate(value))); } Loading @@ -315,6 +340,7 @@ void MethodBuilder::EncodeInstruction(const Instruction& instruction) { case Instruction::Op::kReturnObject: return EncodeReturn(instruction, ::art::Instruction::RETURN_OBJECT); case Instruction::Op::kMove: case Instruction::Op::kMoveObject: return EncodeMove(instruction); case Instruction::Op::kInvokeVirtual: return EncodeInvoke(instruction, art::Instruction::INVOKE_VIRTUAL); Loading @@ -338,33 +364,43 @@ void MethodBuilder::EncodeInstruction(const Instruction& instruction) { } void MethodBuilder::EncodeReturn(const Instruction& instruction, ::art::Instruction::Code opcode) { DCHECK(!instruction.dest().has_value()); CHECK(!instruction.dest().has_value()); if (instruction.args().size() == 0) { Encode10x(art::Instruction::RETURN_VOID); } else { DCHECK_EQ(1, instruction.args().size()); CHECK_EQ(1, instruction.args().size()); size_t source = RegisterValue(instruction.args()[0]); Encode11x(opcode, source); } } void MethodBuilder::EncodeMove(const Instruction& instruction) { DCHECK_EQ(Instruction::Op::kMove, instruction.opcode()); DCHECK(instruction.dest().has_value()); DCHECK(instruction.dest()->is_register() || instruction.dest()->is_parameter()); DCHECK_EQ(1, instruction.args().size()); CHECK(Instruction::Op::kMove == instruction.opcode() || Instruction::Op::kMoveObject == instruction.opcode()); CHECK(instruction.dest().has_value()); CHECK(instruction.dest()->is_variable()); CHECK_EQ(1, instruction.args().size()); const Value& source = instruction.args()[0]; if (source.is_immediate()) { // TODO: support more registers DCHECK_LT(RegisterValue(*instruction.dest()), 16); CHECK_LT(RegisterValue(*instruction.dest()), 16); Encode11n(art::Instruction::CONST_4, RegisterValue(*instruction.dest()), source.value()); } 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 CHECK_LT(RegisterValue(*instruction.dest()), kMaxRegisters); CHECK_LT(source.value(), 65536); // make sure we don't need a jumbo string Encode21c(::art::Instruction::CONST_STRING, RegisterValue(*instruction.dest()), source.value()); } else if (source.is_variable()) { // For the moment, we only use this when we need to reshuffle registers for // an invoke instruction, meaning we are too big for the 4-bit version. // We'll err on the side of caution and always generate the 16-bit form of // the instruction. Opcode opcode = instruction.opcode() == Instruction::Op::kMove ? ::art::Instruction::MOVE_16 : ::art::Instruction::MOVE_OBJECT_16; Encode32x(opcode, RegisterValue(*instruction.dest()), RegisterValue(source)); } else { UNIMPLEMENTED(FATAL); } Loading @@ -373,14 +409,52 @@ void MethodBuilder::EncodeMove(const Instruction& instruction) { void MethodBuilder::EncodeInvoke(const Instruction& instruction, ::art::Instruction::Code opcode) { constexpr size_t kMaxArgs = 5; // Currently, we only support up to 5 arguments. CHECK_LE(instruction.args().size(), kMaxArgs); uint8_t arguments[kMaxArgs]{}; bool has_long_args = false; for (size_t i = 0; i < instruction.args().size(); ++i) { CHECK(instruction.args()[i].is_variable()); arguments[i] = RegisterValue(instruction.args()[i]); if (!IsShortRegister(arguments[i])) { has_long_args = true; } } if (has_long_args) { // Some of the registers don't fit in the four bit short form of the invoke // instruction, so we need to do an invoke/range. To do this, we need to // first move all the arguments into contiguous temporary registers. std::array<Value, kMaxArgs> scratch{GetScratchRegisters<kMaxArgs>()}; const auto& prototype = dex_->GetPrototypeByMethodId(instruction.method_id()); CHECK(prototype.has_value()); for (size_t i = 0; i < instruction.args().size(); ++i) { Instruction::Op move_op; if (opcode == ::art::Instruction::INVOKE_VIRTUAL || opcode == ::art::Instruction::INVOKE_DIRECT) { // In this case, there is an implicit `this` argument, which is always an object. if (i == 0) { move_op = Instruction::Op::kMoveObject; } else { move_op = prototype->ArgType(i - 1).is_object() ? Instruction::Op::kMoveObject : Instruction::Op::kMove; } } else { move_op = prototype->ArgType(i).is_object() ? Instruction::Op::kMoveObject : Instruction::Op::kMove; } EncodeMove(Instruction::OpWithArgs(move_op, scratch[i], instruction.args()[i])); } Encode3rc(InvokeToInvokeRange(opcode), instruction.args().size(), instruction.method_id(), RegisterValue(scratch[0])); } else { Encode35c(opcode, instruction.args().size(), instruction.method_id(), Loading @@ -389,6 +463,7 @@ void MethodBuilder::EncodeInvoke(const Instruction& instruction, ::art::Instruct arguments[2], arguments[3], arguments[4]); } // If there is a return value, add a move-result instruction if (instruction.dest().has_value()) { Loading Loading @@ -416,26 +491,26 @@ void MethodBuilder::EncodeBranch(art::Instruction::Code op, const Instruction& i } void MethodBuilder::EncodeNew(const Instruction& instruction) { DCHECK_EQ(Instruction::Op::kNew, instruction.opcode()); DCHECK(instruction.dest().has_value()); DCHECK(instruction.dest()->is_variable()); DCHECK_EQ(1, instruction.args().size()); CHECK_EQ(Instruction::Op::kNew, instruction.opcode()); CHECK(instruction.dest().has_value()); CHECK(instruction.dest()->is_variable()); CHECK_EQ(1, instruction.args().size()); const Value& type = instruction.args()[0]; DCHECK_LT(RegisterValue(*instruction.dest()), 256); DCHECK(type.is_type()); CHECK_LT(RegisterValue(*instruction.dest()), 256); CHECK(type.is_type()); Encode21c(::art::Instruction::NEW_INSTANCE, RegisterValue(*instruction.dest()), type.value()); } void MethodBuilder::EncodeCast(const Instruction& instruction) { DCHECK_EQ(Instruction::Op::kCheckCast, instruction.opcode()); DCHECK(instruction.dest().has_value()); DCHECK(instruction.dest()->is_variable()); DCHECK_EQ(1, instruction.args().size()); CHECK_EQ(Instruction::Op::kCheckCast, instruction.opcode()); CHECK(instruction.dest().has_value()); CHECK(instruction.dest()->is_variable()); CHECK_EQ(1, instruction.args().size()); const Value& type = instruction.args()[0]; DCHECK_LT(RegisterValue(*instruction.dest()), 256); DCHECK(type.is_type()); CHECK_LT(RegisterValue(*instruction.dest()), 256); CHECK(type.is_type()); Encode21c(::art::Instruction::CHECK_CAST, RegisterValue(*instruction.dest()), type.value()); } Loading @@ -443,9 +518,9 @@ size_t MethodBuilder::RegisterValue(const Value& value) const { if (value.is_register()) { return value.value(); } else if (value.is_parameter()) { return value.value() + num_registers_; return value.value() + num_registers_ + kMaxScratchRegisters; } DCHECK(false && "Must be either a parameter or a register"); CHECK(false && "Must be either a parameter or a register"); return 0; } Loading Loading @@ -498,7 +573,7 @@ const MethodDeclData& DexBuilder::GetOrDeclareMethod(TypeDescriptor type, const // update the index -> ir node map (see tools/dexter/slicer/dex_ir_builder.cc) auto new_index = dex_file_->methods_indexes.AllocateIndex(); auto& ir_node = dex_file_->methods_map[new_index]; SLICER_CHECK(ir_node == nullptr); CHECK(ir_node == nullptr); ir_node = decl; decl->orig_index = decl->index = new_index; Loading @@ -508,6 +583,15 @@ const MethodDeclData& DexBuilder::GetOrDeclareMethod(TypeDescriptor type, const return entry; } std::optional<const Prototype> DexBuilder::GetPrototypeByMethodId(size_t method_id) const { for (const auto& entry : method_id_map_) { if (entry.second.id == method_id) { return entry.first.prototype; } } return {}; } ir::Proto* DexBuilder::GetOrEncodeProto(Prototype prototype) { ir::Proto*& ir_proto = proto_map_[prototype]; if (ir_proto == nullptr) { Loading
startop/view_compiler/dex_builder.h +50 −9 Original line number Diff line number Diff line Loading @@ -16,6 +16,7 @@ #ifndef DEX_BUILDER_H_ #define DEX_BUILDER_H_ #include <array> #include <forward_list> #include <map> #include <optional> Loading Loading @@ -70,6 +71,8 @@ class TypeDescriptor { // Return the shorty descriptor, such as I or L std::string short_descriptor() const { return descriptor().substr(0, 1); } bool is_object() const { return short_descriptor() == "L"; } bool operator<(const TypeDescriptor& rhs) const { return descriptor_ < rhs.descriptor_; } private: Loading @@ -92,6 +95,8 @@ class Prototype { // Get the shorty descriptor, such as VII for (Int, Int) -> Void std::string Shorty() const; const TypeDescriptor& ArgType(size_t index) const; bool operator<(const Prototype& rhs) const { return std::make_tuple(return_type_, param_types_) < std::make_tuple(rhs.return_type_, rhs.param_types_); Loading Loading @@ -124,11 +129,13 @@ class Value { size_t value() const { return value_; } constexpr Value() : value_{0}, kind_{Kind::kInvalid} {} private: enum class Kind { kLocalRegister, kParameter, kImmediate, kString, kLabel, kType }; enum class Kind { kInvalid, kLocalRegister, kParameter, kImmediate, kString, kLabel, kType }; const size_t value_; const Kind kind_; size_t value_; Kind kind_; constexpr Value(size_t value, Kind kind) : value_{value}, kind_{kind} {} }; Loading @@ -151,6 +158,7 @@ class Instruction { kInvokeStatic, kInvokeVirtual, kMove, kMoveObject, kNew, kReturn, kReturnObject, Loading @@ -172,7 +180,7 @@ class Instruction { // A cast instruction. Basically, `(type)val` static inline Instruction Cast(Value val, Value type) { DCHECK(type.is_type()); CHECK(type.is_type()); return OpWithArgs(Op::kCheckCast, val, type); } Loading Loading @@ -343,21 +351,48 @@ class MethodBuilder { buffer_.push_back(b); } inline void Encode32x(art::Instruction::Code opcode, uint16_t a, uint16_t b) { buffer_.push_back(opcode); buffer_.push_back(a); buffer_.push_back(b); } inline void Encode35c(art::Instruction::Code opcode, size_t a, uint16_t b, uint8_t c, uint8_t d, uint8_t e, uint8_t f, uint8_t g) { // a|g|op|bbbb|f|e|d|c CHECK_LE(a, 5); CHECK_LT(c, 16); CHECK_LT(d, 16); CHECK_LT(e, 16); CHECK_LT(f, 16); CHECK_LT(g, 16); CHECK(IsShortRegister(c)); CHECK(IsShortRegister(d)); CHECK(IsShortRegister(e)); CHECK(IsShortRegister(f)); CHECK(IsShortRegister(g)); buffer_.push_back((a << 12) | (g << 8) | opcode); buffer_.push_back(b); buffer_.push_back((f << 12) | (e << 8) | (d << 4) | c); } inline void Encode3rc(art::Instruction::Code opcode, size_t a, uint16_t b, uint16_t c) { CHECK_LE(a, 255); buffer_.push_back((a << 8) | opcode); buffer_.push_back(b); buffer_.push_back(c); } static constexpr bool IsShortRegister(size_t register_value) { return register_value < 16; } // Returns an array of num_regs scratch registers. These are guaranteed to be // contiguous, so they are suitable for the invoke-*/range instructions. template <int num_regs> std::array<Value, num_regs> GetScratchRegisters() const { static_assert(num_regs <= kMaxScratchRegisters); std::array<Value, num_regs> regs; for (size_t i = 0; i < num_regs; ++i) { regs[i] = std::move(Value::Local(num_registers_ + i)); } return regs; } // Converts a register or parameter to its DEX register number. size_t RegisterValue(const Value& value) const; Loading @@ -379,6 +414,10 @@ class MethodBuilder { // A buffer to hold instructions that have been encoded. std::vector<::dex::u2> buffer_; // We create some scratch registers for when we have to shuffle registers // around to make legal DEX code. static constexpr size_t kMaxScratchRegisters = 5; // How many registers we've allocated size_t num_registers_{0}; Loading Loading @@ -447,6 +486,8 @@ class DexBuilder { const MethodDeclData& GetOrDeclareMethod(TypeDescriptor type, const std::string& name, Prototype prototype); std::optional<const Prototype> GetPrototypeByMethodId(size_t method_id) const; private: // Looks up the ir::Proto* corresponding to this given prototype, or creates one if it does not // exist. Loading
startop/view_compiler/dex_builder_test.cc +38 −0 Original line number Diff line number Diff line Loading @@ -140,3 +140,41 @@ TEST(DexBuilderTest, VerifyDexCallStringLength) { EXPECT_TRUE(EncodeAndVerify(&dex_file)); } // Write out and verify a DEX file that corresponds to: // // package dextest; // public class DexTest { // public static int foo(String s) { return s.length(); } // } TEST(DexBuilderTest, VerifyDexCallManyRegisters) { DexBuilder dex_file; auto cbuilder{dex_file.MakeClass("dextest.DexTest")}; MethodBuilder method{cbuilder.CreateMethod( "foo", Prototype{TypeDescriptor::Int()})}; Value result = method.MakeRegister(); // Make a bunch of registers for (size_t i = 0; i < 25; ++i) { method.MakeRegister(); } // Now load a string literal into a register Value string_val = method.MakeRegister(); method.BuildConstString(string_val, "foo"); MethodDeclData string_length = dex_file.GetOrDeclareMethod(TypeDescriptor::FromClassname("java.lang.String"), "length", Prototype{TypeDescriptor::Int()}); method.AddInstruction(Instruction::InvokeVirtual(string_length.id, result, string_val)); method.BuildReturn(result); method.Encode(); EXPECT_TRUE(EncodeAndVerify(&dex_file)); }
startop/view_compiler/dex_builder_test/Android.bp +12 −2 Original line number Diff line number Diff line Loading @@ -15,7 +15,7 @@ // genrule { name: "generate_compiled_layout", name: "generate_compiled_layout1", tools: [":viewcompiler"], cmd: "$(location :viewcompiler) $(in) --dex --out $(out) --package android.startop.test", srcs: ["res/layout/layout1.xml"], Loading @@ -24,6 +24,16 @@ genrule { ], } genrule { name: "generate_compiled_layout2", tools: [":viewcompiler"], cmd: "$(location :viewcompiler) $(in) --dex --out $(out) --package android.startop.test", srcs: ["res/layout/layout2.xml"], out: [ "layout2.dex", ], } android_test { name: "dex-builder-test", srcs: [ Loading @@ -31,7 +41,7 @@ android_test { "src/android/startop/test/LayoutCompilerTest.java", ], sdk_version: "current", data: [":generate_dex_testcases", ":generate_compiled_layout"], data: [":generate_dex_testcases", ":generate_compiled_layout1", ":generate_compiled_layout2"], static_libs: [ "android-support-test", "guava", Loading
startop/view_compiler/dex_builder_test/AndroidTest.xml +1 −0 Original line number Diff line number Diff line Loading @@ -26,6 +26,7 @@ <option name="push" value="trivial.dex->/data/local/tmp/dex-builder-test/trivial.dex" /> <option name="push" value="simple.dex->/data/local/tmp/dex-builder-test/simple.dex" /> <option name="push" value="layout1.dex->/data/local/tmp/dex-builder-test/layout1.dex" /> <option name="push" value="layout2.dex->/data/local/tmp/dex-builder-test/layout2.dex" /> </target_preparer> <test class="com.android.tradefed.testtype.AndroidJUnitTest" > Loading