diff options
7 files changed, 277 insertions, 49 deletions
diff --git a/startop/view_compiler/dex_builder.cc b/startop/view_compiler/dex_builder.cc index a78f7d53d135..4c1a0dc7f749 100644 --- a/startop/view_compiler/dex_builder.cc +++ b/startop/view_compiler/dex_builder.cc @@ -21,8 +21,6 @@ #include <fstream> #include <memory> -#define DCHECK_NOT_NULL(p) DCHECK((p) != nullptr) - namespace startop { namespace dex { @@ -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"}; }; @@ -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) { @@ -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; @@ -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) {} @@ -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()); @@ -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))); } @@ -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); @@ -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); } @@ -373,22 +409,61 @@ 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; + } } - Encode35c(opcode, - instruction.args().size(), - instruction.method_id(), - arguments[0], - arguments[1], - arguments[2], - arguments[3], - arguments[4]); + 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(), + arguments[0], + arguments[1], + arguments[2], + arguments[3], + arguments[4]); + } // If there is a return value, add a move-result instruction if (instruction.dest().has_value()) { @@ -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()); } @@ -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; } @@ -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; @@ -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) { diff --git a/startop/view_compiler/dex_builder.h b/startop/view_compiler/dex_builder.h index 757d863461f0..541d80077bd3 100644 --- a/startop/view_compiler/dex_builder.h +++ b/startop/view_compiler/dex_builder.h @@ -16,6 +16,7 @@ #ifndef DEX_BUILDER_H_ #define DEX_BUILDER_H_ +#include <array> #include <forward_list> #include <map> #include <optional> @@ -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: @@ -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_); @@ -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} {} }; @@ -151,6 +158,7 @@ class Instruction { kInvokeStatic, kInvokeVirtual, kMove, + kMoveObject, kNew, kReturn, kReturnObject, @@ -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); } @@ -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; @@ -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}; @@ -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. diff --git a/startop/view_compiler/dex_builder_test.cc b/startop/view_compiler/dex_builder_test.cc index 61c86b4091b3..90c256f271cf 100644 --- a/startop/view_compiler/dex_builder_test.cc +++ b/startop/view_compiler/dex_builder_test.cc @@ -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)); +} diff --git a/startop/view_compiler/dex_builder_test/Android.bp b/startop/view_compiler/dex_builder_test/Android.bp index d4f38ed148c9..ac60e966fe43 100644 --- a/startop/view_compiler/dex_builder_test/Android.bp +++ b/startop/view_compiler/dex_builder_test/Android.bp @@ -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"], @@ -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: [ @@ -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", diff --git a/startop/view_compiler/dex_builder_test/AndroidTest.xml b/startop/view_compiler/dex_builder_test/AndroidTest.xml index 68d8fdc444d8..92e2a718bcce 100644 --- a/startop/view_compiler/dex_builder_test/AndroidTest.xml +++ b/startop/view_compiler/dex_builder_test/AndroidTest.xml @@ -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" > diff --git a/startop/view_compiler/dex_builder_test/res/layout/layout2.xml b/startop/view_compiler/dex_builder_test/res/layout/layout2.xml new file mode 100644 index 000000000000..b092e1c20311 --- /dev/null +++ b/startop/view_compiler/dex_builder_test/res/layout/layout2.xml @@ -0,0 +1,45 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent"> + + <TableRow + android:layout_width="match_parent" + android:layout_height="match_parent" > + + <Button + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="Button" /> + + <TableRow + android:layout_width="match_parent" + android:layout_height="match_parent" > + + <Button + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="Button" /> + <TableRow + android:layout_width="match_parent" + android:layout_height="match_parent" > + + <Button + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="Button" /> + + <TableRow + android:layout_width="match_parent" + android:layout_height="match_parent" > + + <Button + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="Button" /> + </TableRow> + + </TableRow> + </TableRow> + </TableRow> +</LinearLayout> diff --git a/startop/view_compiler/dex_builder_test/src/android/startop/test/LayoutCompilerTest.java b/startop/view_compiler/dex_builder_test/src/android/startop/test/LayoutCompilerTest.java index ce3ce8328559..a3b1b6c11ac3 100644 --- a/startop/view_compiler/dex_builder_test/src/android/startop/test/LayoutCompilerTest.java +++ b/startop/view_compiler/dex_builder_test/src/android/startop/test/LayoutCompilerTest.java @@ -36,11 +36,20 @@ public class LayoutCompilerTest { } @Test - public void loadAndInflaterLayout1() throws Exception { + public void loadAndInflateLayout1() throws Exception { ClassLoader dex_file = loadDexFile("layout1.dex"); Class compiled_view = dex_file.loadClass("android.startop.test.CompiledView"); Method layout1 = compiled_view.getMethod("layout1", Context.class, int.class); Context context = InstrumentationRegistry.getTargetContext(); layout1.invoke(null, context, R.layout.layout1); } + + @Test + public void loadAndInflateLayout2() throws Exception { + ClassLoader dex_file = loadDexFile("layout2.dex"); + Class compiled_view = dex_file.loadClass("android.startop.test.CompiledView"); + Method layout1 = compiled_view.getMethod("layout2", Context.class, int.class); + Context context = InstrumentationRegistry.getTargetContext(); + layout1.invoke(null, context, R.layout.layout1); + } } |