From 3092f99ae63f12f5c18d40f21616c98f2b6c62af Mon Sep 17 00:00:00 2001 From: Eric Holk Date: Thu, 25 Jul 2019 15:14:01 -0700 Subject: [PATCH 1/2] [viewcompiler] Add static field get instructions to DexBuilder This allows us to generate code that can read static fields in a class. Once we include several other field operations, we will be able to generate more specialized inflation code in the view compiler. Bug: 111895153 Change-Id: Ia11195b1cea6d5a3ddbc60d972922586a062c853 --- startop/view_compiler/dex_builder.cc | 37 +++++++++++- startop/view_compiler/dex_builder.h | 59 ++++++++++++------- .../view_compiler/dex_builder_test/Android.bp | 1 + .../android/startop/test/DexBuilderTest.java | 13 +++- .../src/android/startop/test/TestClass.java | 23 ++++++++ .../view_compiler/dex_testcase_generator.cc | 15 +++++ 6 files changed, 121 insertions(+), 27 deletions(-) create mode 100644 startop/view_compiler/dex_builder_test/src/android/startop/test/TestClass.java diff --git a/startop/view_compiler/dex_builder.cc b/startop/view_compiler/dex_builder.cc index 6047e8c74e38c..66bc698e21369 100644 --- a/startop/view_compiler/dex_builder.cc +++ b/startop/view_compiler/dex_builder.cc @@ -102,6 +102,9 @@ std::ostream& operator<<(std::ostream& out, const Instruction::Op& opcode) { case Instruction::Op::kCheckCast: out << "kCheckCast"; return out; + case Instruction::Op::kGetStaticField: + out << "kGetStaticField"; + return out; } } @@ -229,6 +232,22 @@ ir::Type* DexBuilder::GetOrAddType(const std::string& descriptor) { return type; } +ir::FieldDecl* DexBuilder::GetOrAddField(TypeDescriptor parent, const std::string& name, + TypeDescriptor type) { + const auto key = std::make_tuple(parent, name); + if (field_decls_by_key_.find(key) != field_decls_by_key_.end()) { + return field_decls_by_key_[key]; + } + + ir::FieldDecl* field = Alloc(); + field->parent = GetOrAddType(parent); + field->name = GetOrAddString(name); + field->type = GetOrAddType(type); + dex_file_->fields_map[field->orig_index] = field; + field_decls_by_key_[key] = field; + return field; +} + ir::Proto* Prototype::Encode(DexBuilder* dex) const { auto* proto = dex->Alloc(); proto->shorty = dex->GetOrAddString(Shorty()); @@ -360,6 +379,8 @@ void MethodBuilder::EncodeInstruction(const Instruction& instruction) { return EncodeNew(instruction); case Instruction::Op::kCheckCast: return EncodeCast(instruction); + case Instruction::Op::kGetStaticField: + return EncodeStaticFieldOp(instruction); } } @@ -428,7 +449,7 @@ void MethodBuilder::EncodeInvoke(const Instruction& instruction, ::art::Instruct // first move all the arguments into contiguous temporary registers. std::array scratch = GetScratchRegisters(); - const auto& prototype = dex_->GetPrototypeByMethodId(instruction.method_id()); + const auto& prototype = dex_->GetPrototypeByMethodId(instruction.index_argument()); CHECK(prototype.has_value()); for (size_t i = 0; i < instruction.args().size(); ++i) { @@ -452,12 +473,12 @@ void MethodBuilder::EncodeInvoke(const Instruction& instruction, ::art::Instruct Encode3rc(InvokeToInvokeRange(opcode), instruction.args().size(), - instruction.method_id(), + instruction.index_argument(), RegisterValue(scratch[0])); } else { Encode35c(opcode, instruction.args().size(), - instruction.method_id(), + instruction.index_argument(), arguments[0], arguments[1], arguments[2], @@ -514,6 +535,16 @@ void MethodBuilder::EncodeCast(const Instruction& instruction) { Encode21c(::art::Instruction::CHECK_CAST, RegisterValue(*instruction.dest()), type.value()); } +void MethodBuilder::EncodeStaticFieldOp(const Instruction& instruction) { + CHECK_EQ(Instruction::Op::kGetStaticField, instruction.opcode()); + CHECK(instruction.dest().has_value()); + CHECK(instruction.dest()->is_variable()); + CHECK_EQ(0, instruction.args().size()); + + Encode21c( + ::art::Instruction::SGET, RegisterValue(*instruction.dest()), instruction.index_argument()); +} + size_t MethodBuilder::RegisterValue(const Value& value) const { if (value.is_register()) { return value.value(); diff --git a/startop/view_compiler/dex_builder.h b/startop/view_compiler/dex_builder.h index 541d80077bd32..a7ccb4a9452b3 100644 --- a/startop/view_compiler/dex_builder.h +++ b/startop/view_compiler/dex_builder.h @@ -153,6 +153,7 @@ class Instruction { kBranchEqz, kBranchNEqz, kCheckCast, + kGetStaticField, kInvokeDirect, kInvokeInterface, kInvokeStatic, @@ -170,12 +171,12 @@ class Instruction { // For instructions with no return value and no arguments. static inline Instruction OpNoArgs(Op opcode) { - return Instruction{opcode, /*method_id*/ 0, /*dest*/ {}}; + return Instruction{opcode, /*index_argument*/ 0, /*dest*/ {}}; } // For most instructions, which take some number of arguments and have an optional return value. template static inline Instruction OpWithArgs(Op opcode, std::optional dest, T... args) { - return Instruction{opcode, /*method_id=*/0, /*result_is_object=*/false, dest, args...}; + return Instruction{opcode, /*index_argument=*/0, /*result_is_object=*/false, dest, args...}; } // A cast instruction. Basically, `(type)val` @@ -186,49 +187,54 @@ class Instruction { // For method calls. template - static inline Instruction InvokeVirtual(size_t method_id, std::optional dest, + static inline Instruction InvokeVirtual(size_t index_argument, std::optional dest, Value this_arg, T... args) { return Instruction{ - Op::kInvokeVirtual, method_id, /*result_is_object=*/false, dest, this_arg, args...}; + Op::kInvokeVirtual, index_argument, /*result_is_object=*/false, dest, this_arg, args...}; } // Returns an object template - static inline Instruction InvokeVirtualObject(size_t method_id, std::optional dest, + static inline Instruction InvokeVirtualObject(size_t index_argument, std::optional dest, Value this_arg, T... args) { return Instruction{ - Op::kInvokeVirtual, method_id, /*result_is_object=*/true, dest, this_arg, args...}; + Op::kInvokeVirtual, index_argument, /*result_is_object=*/true, dest, this_arg, args...}; } // For direct calls (basically, constructors). template - static inline Instruction InvokeDirect(size_t method_id, std::optional dest, + static inline Instruction InvokeDirect(size_t index_argument, std::optional dest, Value this_arg, T... args) { return Instruction{ - Op::kInvokeDirect, method_id, /*result_is_object=*/false, dest, this_arg, args...}; + Op::kInvokeDirect, index_argument, /*result_is_object=*/false, dest, this_arg, args...}; } // Returns an object template - static inline Instruction InvokeDirectObject(size_t method_id, std::optional dest, + static inline Instruction InvokeDirectObject(size_t index_argument, std::optional dest, Value this_arg, T... args) { return Instruction{ - Op::kInvokeDirect, method_id, /*result_is_object=*/true, dest, this_arg, args...}; + Op::kInvokeDirect, index_argument, /*result_is_object=*/true, dest, this_arg, args...}; } // For static calls. template - static inline Instruction InvokeStatic(size_t method_id, std::optional dest, + static inline Instruction InvokeStatic(size_t index_argument, std::optional dest, T... args) { - return Instruction{Op::kInvokeStatic, method_id, /*result_is_object=*/false, dest, args...}; + return Instruction{Op::kInvokeStatic, index_argument, /*result_is_object=*/false, dest, args...}; } // Returns an object template - static inline Instruction InvokeStaticObject(size_t method_id, std::optional dest, + static inline Instruction InvokeStaticObject(size_t index_argument, std::optional dest, T... args) { - return Instruction{Op::kInvokeStatic, method_id, /*result_is_object=*/true, dest, args...}; + return Instruction{Op::kInvokeStatic, index_argument, /*result_is_object=*/true, dest, args...}; } // For static calls. template - static inline Instruction InvokeInterface(size_t method_id, std::optional dest, + static inline Instruction InvokeInterface(size_t index_argument, std::optional dest, T... args) { - return Instruction{Op::kInvokeInterface, method_id, /*result_is_object=*/false, dest, args...}; + return Instruction{Op::kInvokeInterface, index_argument, /*result_is_object=*/false, dest, args...}; + + } + + static inline Instruction GetStaticField(size_t field_id, Value dest) { + return Instruction{Op::kGetStaticField, field_id, dest}; } /////////////// @@ -236,27 +242,27 @@ class Instruction { /////////////// Op opcode() const { return opcode_; } - size_t method_id() const { return method_id_; } + size_t index_argument() const { return index_argument_; } bool result_is_object() const { return result_is_object_; } const std::optional& dest() const { return dest_; } const std::vector& args() const { return args_; } private: - inline Instruction(Op opcode, size_t method_id, std::optional dest) - : opcode_{opcode}, method_id_{method_id}, result_is_object_{false}, dest_{dest}, args_{} {} + inline Instruction(Op opcode, size_t index_argument, std::optional dest) + : opcode_{opcode}, index_argument_{index_argument}, result_is_object_{false}, dest_{dest}, args_{} {} template - inline constexpr Instruction(Op opcode, size_t method_id, bool result_is_object, + inline constexpr Instruction(Op opcode, size_t index_argument, bool result_is_object, std::optional dest, T... args) : opcode_{opcode}, - method_id_{method_id}, + index_argument_{index_argument}, result_is_object_{result_is_object}, dest_{dest}, args_{args...} {} const Op opcode_; // The index of the method to invoke, for kInvokeVirtual and similar opcodes. - const size_t method_id_{0}; + const size_t index_argument_{0}; const bool result_is_object_; const std::optional dest_; const std::vector args_; @@ -319,6 +325,7 @@ class MethodBuilder { void EncodeBranch(art::Instruction::Code op, const Instruction& instruction); void EncodeNew(const Instruction& instruction); void EncodeCast(const Instruction& instruction); + void EncodeStaticFieldOp(const Instruction& instruction); // Low-level instruction format encoding. See // https://source.android.com/devices/tech/dalvik/instruction-formats for documentation of @@ -481,6 +488,11 @@ class DexBuilder { // See the TypeDescriptor class for help generating these. GetOrAddType can be used to declare // imported classes. ir::Type* GetOrAddType(const std::string& descriptor); + inline ir::Type* GetOrAddType(TypeDescriptor descriptor) { + return GetOrAddType(descriptor.descriptor()); + } + + ir::FieldDecl* GetOrAddField(TypeDescriptor parent, const std::string& name, TypeDescriptor type); // Returns the method id for the method, creating it if it has not been created yet. const MethodDeclData& GetOrDeclareMethod(TypeDescriptor type, const std::string& name, @@ -526,6 +538,9 @@ class DexBuilder { // Keep track of already-encoded protos. std::map proto_map_; + + // Keep track of fields that have been declared + std::map, ir::FieldDecl*> field_decls_by_key_; }; template diff --git a/startop/view_compiler/dex_builder_test/Android.bp b/startop/view_compiler/dex_builder_test/Android.bp index ac60e966fe43f..9ad1ca1ef48ef 100644 --- a/startop/view_compiler/dex_builder_test/Android.bp +++ b/startop/view_compiler/dex_builder_test/Android.bp @@ -39,6 +39,7 @@ android_test { srcs: [ "src/android/startop/test/DexBuilderTest.java", "src/android/startop/test/LayoutCompilerTest.java", + "src/android/startop/test/TestClass.java", ], sdk_version: "current", data: [":generate_dex_testcases", ":generate_compiled_layout1", ":generate_compiled_layout2"], 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 42d4161ee81ea..df11bfafd3571 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 @@ -28,10 +28,10 @@ import org.junit.Test; // Adding tests here requires changes in several other places. See README.md in // the view_compiler directory for more information. -public class DexBuilderTest { +public final class DexBuilderTest { static ClassLoader loadDexFile(String filename) throws Exception { return new PathClassLoader("/data/local/tmp/dex-builder-test/" + filename, - ClassLoader.getSystemClassLoader()); + DexBuilderTest.class.getClassLoader()); } public void hello() {} @@ -171,4 +171,13 @@ public class DexBuilderTest { } Assert.assertTrue(castFailed); } + + @Test + public void readStaticField() throws Exception { + ClassLoader loader = loadDexFile("simple.dex"); + Class clazz = loader.loadClass("android.startop.test.testcases.SimpleTests"); + Method method = clazz.getMethod("readStaticField"); + TestClass.staticInteger = 5; + Assert.assertEquals(5, method.invoke(null)); + } } diff --git a/startop/view_compiler/dex_builder_test/src/android/startop/test/TestClass.java b/startop/view_compiler/dex_builder_test/src/android/startop/test/TestClass.java new file mode 100644 index 0000000000000..dd77923060306 --- /dev/null +++ b/startop/view_compiler/dex_builder_test/src/android/startop/test/TestClass.java @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package android.startop.test; + + /** + * A simple class to help test DexBuilder. + */ +public final class TestClass { + public static int staticInteger; + + public int instanceField; +} diff --git a/startop/view_compiler/dex_testcase_generator.cc b/startop/view_compiler/dex_testcase_generator.cc index f62ec5dde85ee..5227f8054765c 100644 --- a/startop/view_compiler/dex_testcase_generator.cc +++ b/startop/view_compiler/dex_testcase_generator.cc @@ -282,6 +282,21 @@ void GenerateSimpleTestCases(const string& outdir) { method.Encode(); }(castObjectToString); + // Read a static field + // integer readStaticField() { return TestClass.staticInteger; } + MethodBuilder readStaticField{ + cbuilder.CreateMethod("readStaticField", Prototype{TypeDescriptor::Int()})}; + [&](MethodBuilder& method) { + const ir::FieldDecl* field = + dex_file.GetOrAddField(TypeDescriptor::FromClassname("android.startop.test.TestClass"), + "staticInteger", + TypeDescriptor::Int()); + Value result{method.MakeRegister()}; + method.AddInstruction(Instruction::GetStaticField(field->orig_index, result)); + method.BuildReturn(result, /*is_object=*/false); + method.Encode(); + }(readStaticField); + slicer::MemView image{dex_file.CreateImage()}; std::ofstream out_file(outdir + "/simple.dex"); out_file.write(image.ptr(), image.size()); From 70445d0d89cb2b730e148cdb92e580585c1ef9c6 Mon Sep 17 00:00:00 2001 From: Eric Holk Date: Fri, 26 Jul 2019 09:37:46 -0700 Subject: [PATCH 2/2] [viewcompiler] Add support for static field put to DexBuilder Bug: 111895153 Change-Id: I12b38fa520790debec545d7d1f6b3522a65ce03b --- startop/view_compiler/dex_builder.cc | 35 +++++++++++++++---- startop/view_compiler/dex_builder.h | 6 ++++ .../android/startop/test/DexBuilderTest.java | 10 ++++++ .../view_compiler/dex_testcase_generator.cc | 16 +++++++++ 4 files changed, 61 insertions(+), 6 deletions(-) diff --git a/startop/view_compiler/dex_builder.cc b/startop/view_compiler/dex_builder.cc index 66bc698e21369..09f9c04d9e2c6 100644 --- a/startop/view_compiler/dex_builder.cc +++ b/startop/view_compiler/dex_builder.cc @@ -105,6 +105,9 @@ std::ostream& operator<<(std::ostream& out, const Instruction::Op& opcode) { case Instruction::Op::kGetStaticField: out << "kGetStaticField"; return out; + case Instruction::Op::kSetStaticField: + out << "kSetStaticField"; + return out; } } @@ -380,6 +383,7 @@ void MethodBuilder::EncodeInstruction(const Instruction& instruction) { case Instruction::Op::kCheckCast: return EncodeCast(instruction); case Instruction::Op::kGetStaticField: + case Instruction::Op::kSetStaticField: return EncodeStaticFieldOp(instruction); } } @@ -536,13 +540,32 @@ void MethodBuilder::EncodeCast(const Instruction& instruction) { } void MethodBuilder::EncodeStaticFieldOp(const Instruction& instruction) { - CHECK_EQ(Instruction::Op::kGetStaticField, instruction.opcode()); - CHECK(instruction.dest().has_value()); - CHECK(instruction.dest()->is_variable()); - CHECK_EQ(0, instruction.args().size()); + switch (instruction.opcode()) { + case Instruction::Op::kGetStaticField: { + CHECK(instruction.dest().has_value()); + CHECK(instruction.dest()->is_variable()); + CHECK_EQ(0, instruction.args().size()); - Encode21c( - ::art::Instruction::SGET, RegisterValue(*instruction.dest()), instruction.index_argument()); + Encode21c(::art::Instruction::SGET, + RegisterValue(*instruction.dest()), + instruction.index_argument()); + break; + } + case Instruction::Op::kSetStaticField: { + CHECK(!instruction.dest().has_value()); + const auto& args = instruction.args(); + CHECK_EQ(1, args.size()); + CHECK(args[0].is_variable()); + + Encode21c(::art::Instruction::SPUT, + RegisterValue(args[0]), + instruction.index_argument()); + break; + } + default: { + LOG(FATAL) << "Unsupported static field operation"; + } + } } size_t MethodBuilder::RegisterValue(const Value& value) const { diff --git a/startop/view_compiler/dex_builder.h b/startop/view_compiler/dex_builder.h index a7ccb4a9452b3..3f9ac43ae5326 100644 --- a/startop/view_compiler/dex_builder.h +++ b/startop/view_compiler/dex_builder.h @@ -163,6 +163,7 @@ class Instruction { kNew, kReturn, kReturnObject, + kSetStaticField }; //////////////////////// @@ -237,6 +238,11 @@ class Instruction { return Instruction{Op::kGetStaticField, field_id, dest}; } + static inline Instruction SetStaticField(size_t field_id, Value value) { + return Instruction{Op::kSetStaticField, field_id, /*result_is_object=*/false, /*dest=*/{}, value}; + } + + /////////////// // Accessors // /////////////// 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 df11bfafd3571..3138e71832261 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 @@ -180,4 +180,14 @@ public final class DexBuilderTest { TestClass.staticInteger = 5; Assert.assertEquals(5, method.invoke(null)); } + + @Test + public void setStaticField() throws Exception { + ClassLoader loader = loadDexFile("simple.dex"); + Class clazz = loader.loadClass("android.startop.test.testcases.SimpleTests"); + Method method = clazz.getMethod("setStaticField"); + TestClass.staticInteger = 5; + method.invoke(null); + Assert.assertEquals(7, TestClass.staticInteger); + } } diff --git a/startop/view_compiler/dex_testcase_generator.cc b/startop/view_compiler/dex_testcase_generator.cc index 5227f8054765c..fee5e722bc55c 100644 --- a/startop/view_compiler/dex_testcase_generator.cc +++ b/startop/view_compiler/dex_testcase_generator.cc @@ -297,6 +297,22 @@ void GenerateSimpleTestCases(const string& outdir) { method.Encode(); }(readStaticField); + // Set a static field + // void setStaticField() { TestClass.staticInteger = 7; } + MethodBuilder setStaticField{ + cbuilder.CreateMethod("setStaticField", Prototype{TypeDescriptor::Void()})}; + [&](MethodBuilder& method) { + const ir::FieldDecl* field = + dex_file.GetOrAddField(TypeDescriptor::FromClassname("android.startop.test.TestClass"), + "staticInteger", + TypeDescriptor::Int()); + Value number{method.MakeRegister()}; + method.BuildConst4(number, 7); + method.AddInstruction(Instruction::SetStaticField(field->orig_index, number)); + method.BuildReturn(); + method.Encode(); + }(setStaticField); + slicer::MemView image{dex_file.CreateImage()}; std::ofstream out_file(outdir + "/simple.dex"); out_file.write(image.ptr(), image.size());