LoadElimination.cpp 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206
  1. /*
  2. * Copyright (c) 2022, Leon Albrecht <leon.a@serenityos.com>.
  3. *
  4. * SPDX-License-Identifier: BSD-2-Clause
  5. */
  6. #include <AK/Bitmap.h>
  7. #include <AK/TypeCasts.h>
  8. #include <LibJS/Bytecode/Op.h>
  9. #include <LibJS/Bytecode/PassManager.h>
  10. namespace JS::Bytecode::Passes {
  11. static NonnullOwnPtr<BasicBlock> eliminate_loads(BasicBlock const& block, size_t number_of_registers)
  12. {
  13. auto array_ranges = Bitmap::create(number_of_registers, false).release_value_but_fixme_should_propagate_errors();
  14. for (auto it = InstructionStreamIterator(block.instruction_stream()); !it.at_end(); ++it) {
  15. if ((*it).type() == Instruction::Type::NewArray) {
  16. Op::NewArray const& array_instruction = static_cast<Op::NewArray const&>(*it);
  17. if (size_t element_count = array_instruction.element_count())
  18. array_ranges.set_range<true, false>(array_instruction.start().index(), element_count);
  19. } else if ((*it).type() == Instruction::Type::Call) {
  20. auto const& call_instruction = static_cast<Op::Call const&>(*it);
  21. if (size_t element_count = call_instruction.argument_count())
  22. array_ranges.set_range<true, false>(call_instruction.first_argument().index(), element_count);
  23. }
  24. }
  25. auto new_block = BasicBlock::create(block.name(), block.size());
  26. HashMap<size_t, Register> identifier_table {};
  27. HashMap<u32, Register> register_rerouting_table {};
  28. for (auto it = InstructionStreamIterator(block.instruction_stream()); !it.at_end();) {
  29. using enum Instruction::Type;
  30. // Note: When creating a variable, we technically purge the cache of any
  31. // variables of the same name;
  32. // In practice, we always generate a coinciding SetVariable, which
  33. // does the same
  34. switch ((*it).type()) {
  35. case GetVariable: {
  36. auto const& get_variable = static_cast<Op::GetVariable const&>(*it);
  37. ++it;
  38. auto const& next_instruction = *it;
  39. if (auto reg = identifier_table.find(get_variable.identifier().value()); reg != identifier_table.end()) {
  40. // If we have already seen a variable, we can replace its GetVariable with a simple Load
  41. // knowing that it was already stored in a register
  42. new (new_block->next_slot()) Op::Load(reg->value);
  43. new_block->grow(sizeof(Op::Load));
  44. if (next_instruction.type() == Instruction::Type::Store) {
  45. // If the next instruction is a Store, that is not meant to
  46. // construct an array, we can simply elide that store and reroute
  47. // all further references to the stores destination to the cached
  48. // instance of variable.
  49. // FIXME: We might be able to elide the previous load in the non-array case,
  50. // because we do not yet reuse the accumulator
  51. auto const& store = static_cast<Op::Store const&>(next_instruction);
  52. if (array_ranges.get(store.dst().index())) {
  53. // re-emit the store
  54. new (new_block->next_slot()) Op::Store(store);
  55. new_block->grow(sizeof(Op::Store));
  56. } else {
  57. register_rerouting_table.set(store.dst().index(), reg->value);
  58. }
  59. ++it;
  60. }
  61. continue;
  62. }
  63. // Otherwise we need to emit the GetVariable
  64. new (new_block->next_slot()) Op::GetVariable(get_variable);
  65. new_block->grow(sizeof(Op::GetVariable));
  66. // And if the next instruction is a Store, we can cache it's destination
  67. if (next_instruction.type() == Instruction::Type::Store) {
  68. auto const& store = static_cast<Op::Store const&>(next_instruction);
  69. identifier_table.set(get_variable.identifier().value(), store.dst());
  70. new (new_block->next_slot()) Op::Store(store);
  71. new_block->grow(sizeof(Op::Store));
  72. ++it;
  73. }
  74. continue;
  75. }
  76. case SetVariable: {
  77. // When a variable is set we need to remove it from the cache, because
  78. // we don't have an accurate view on it anymore
  79. // FIXME: If the previous instruction was a `Load $reg`, we could
  80. // update the cache instead
  81. auto const& set_variable = static_cast<Op::SetVariable const&>(*it);
  82. identifier_table.remove(set_variable.identifier().value());
  83. break;
  84. }
  85. case DeleteVariable: {
  86. // When a variable is deleted we need to remove it from the cache, it does not
  87. // exist anymore, although a variable of the same name may exist in upper scopes
  88. auto const& set_variable = static_cast<Op::DeleteVariable const&>(*it);
  89. identifier_table.remove(set_variable.identifier().value());
  90. break;
  91. }
  92. case Store: {
  93. // If we store to a position that we have are rerouting from,
  94. // we need to remove it from the routeing table
  95. // FIXME: This may be redundant due to us assigning to registers only once
  96. auto const& store = static_cast<Op::Store const&>(*it);
  97. register_rerouting_table.remove(store.dst().index());
  98. break;
  99. }
  100. case DeleteById:
  101. case DeleteByValue:
  102. // These can trigger proxies, which call into user code
  103. // So these are treated like calls
  104. case GetByValue:
  105. case GetByValueWithThis:
  106. case GetById:
  107. case GetByIdWithThis:
  108. case PutByValue:
  109. case PutByValueWithThis:
  110. case PutById:
  111. case PutByIdWithThis:
  112. // Attribute accesses (`a.o` or `a[o]`) may result in calls to getters or setters
  113. // or may trigger proxies
  114. // So these are treated like calls
  115. case Call:
  116. case CallWithArgumentArray:
  117. // Calls, especially to local functions and eval, may poison visible and
  118. // cached variables, hence we need to clear the lookup cache after emitting them
  119. // FIXME: In strict mode and with better identifier metrics, we might be able
  120. // to safe some caching with a more fine-grained identifier table
  121. // FIXME: We might be able to save some lookups to objects like `this`
  122. // which should not change their pointer
  123. memcpy(new_block->next_slot(), &*it, (*it).length());
  124. for (auto route : register_rerouting_table)
  125. reinterpret_cast<Instruction*>(new_block->next_slot())->replace_references(Register { route.key }, route.value);
  126. new_block->grow((*it).length());
  127. identifier_table.clear_with_capacity();
  128. ++it;
  129. continue;
  130. case NewBigInt:
  131. // FIXME: This is the only non trivially copyable Instruction,
  132. // so we need to do some extra work here
  133. new (new_block->next_slot()) Op::NewBigInt(static_cast<Op::NewBigInt const&>(*it));
  134. new_block->grow(sizeof(Op::NewBigInt));
  135. ++it;
  136. continue;
  137. default:
  138. break;
  139. }
  140. memcpy(new_block->next_slot(), &*it, (*it).length());
  141. for (auto route : register_rerouting_table) {
  142. // rerouting from key to value
  143. reinterpret_cast<Instruction*>(new_block->next_slot())->replace_references(Register { route.key }, route.value);
  144. }
  145. // because we are replacing the current block, we need to replace references
  146. // to ourselves here
  147. reinterpret_cast<Instruction*>(new_block->next_slot())->replace_references(block, *new_block);
  148. new_block->grow((*it).length());
  149. ++it;
  150. }
  151. return new_block;
  152. }
  153. void EliminateLoads::perform(PassPipelineExecutable& executable)
  154. {
  155. started();
  156. // FIXME: If we walk the CFG instead of the block list, we might be able to
  157. // save some work between blocks
  158. for (auto it = executable.executable.basic_blocks.begin(); it != executable.executable.basic_blocks.end(); ++it) {
  159. auto const& old_block = *it;
  160. auto new_block = eliminate_loads(*old_block, executable.executable.number_of_registers);
  161. // We will replace the old block, with a new one, so we need to replace all references,
  162. // to the old one with the new one
  163. for (auto& block : executable.executable.basic_blocks) {
  164. InstructionStreamIterator it { block->instruction_stream() };
  165. while (!it.at_end()) {
  166. auto& instruction = *it;
  167. ++it;
  168. const_cast<Instruction&>(instruction).replace_references(*old_block, *new_block);
  169. }
  170. }
  171. executable.executable.basic_blocks[it.index()] = move(new_block);
  172. }
  173. finished();
  174. }
  175. }