1 // Copyright 2017 the V8 project authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "src/wasm/wasm-code-specialization.h"
6
7 #include "src/assembler-inl.h"
8 #include "src/objects-inl.h"
9 #include "src/source-position-table.h"
10 #include "src/wasm/decoder.h"
11 #include "src/wasm/wasm-module.h"
12 #include "src/wasm/wasm-opcodes.h"
13
14 using namespace v8::internal;
15 using namespace v8::internal::wasm;
16
17 namespace {
18
ExtractDirectCallIndex(wasm::Decoder & decoder,const byte * pc)19 int ExtractDirectCallIndex(wasm::Decoder& decoder, const byte* pc) {
20 DCHECK_EQ(static_cast<int>(kExprCallFunction), static_cast<int>(*pc));
21 decoder.Reset(pc + 1, pc + 6);
22 uint32_t call_idx = decoder.consume_u32v("call index");
23 DCHECK(decoder.ok());
24 DCHECK_GE(kMaxInt, call_idx);
25 return static_cast<int>(call_idx);
26 }
27
AdvanceSourcePositionTableIterator(SourcePositionTableIterator & iterator,size_t offset_l)28 int AdvanceSourcePositionTableIterator(SourcePositionTableIterator& iterator,
29 size_t offset_l) {
30 DCHECK_GE(kMaxInt, offset_l);
31 int offset = static_cast<int>(offset_l);
32 DCHECK(!iterator.done());
33 int byte_pos;
34 do {
35 byte_pos = iterator.source_position().ScriptOffset();
36 iterator.Advance();
37 } while (!iterator.done() && iterator.code_offset() <= offset);
38 return byte_pos;
39 }
40
41 class PatchDirectCallsHelper {
42 public:
PatchDirectCallsHelper(WasmInstanceObject * instance,Code * code)43 PatchDirectCallsHelper(WasmInstanceObject* instance, Code* code)
44 : source_pos_it(code->source_position_table()),
45 decoder(nullptr, nullptr) {
46 FixedArray* deopt_data = code->deoptimization_data();
47 DCHECK_EQ(2, deopt_data->length());
48 WasmCompiledModule* comp_mod = instance->compiled_module();
49 int func_index = Smi::cast(deopt_data->get(1))->value();
50 func_bytes = comp_mod->module_bytes()->GetChars() +
51 comp_mod->module()->functions[func_index].code_start_offset;
52 }
53
54 SourcePositionTableIterator source_pos_it;
55 Decoder decoder;
56 const byte* func_bytes;
57 };
58
59 } // namespace
60
CodeSpecialization(Isolate * isolate,Zone * zone)61 CodeSpecialization::CodeSpecialization(Isolate* isolate, Zone* zone)
62 : objects_to_relocate(isolate->heap(), ZoneAllocationPolicy(zone)) {}
63
~CodeSpecialization()64 CodeSpecialization::~CodeSpecialization() {}
65
RelocateMemoryReferences(Address old_start,uint32_t old_size,Address new_start,uint32_t new_size)66 void CodeSpecialization::RelocateMemoryReferences(Address old_start,
67 uint32_t old_size,
68 Address new_start,
69 uint32_t new_size) {
70 DCHECK(old_mem_start == nullptr && old_mem_size == 0 &&
71 new_mem_start == nullptr && new_mem_size == 0);
72 DCHECK(old_start != new_start || old_size != new_size);
73 old_mem_start = old_start;
74 old_mem_size = old_size;
75 new_mem_start = new_start;
76 new_mem_size = new_size;
77 }
78
RelocateGlobals(Address old_start,Address new_start)79 void CodeSpecialization::RelocateGlobals(Address old_start, Address new_start) {
80 DCHECK(old_globals_start == 0 && new_globals_start == 0);
81 DCHECK(old_start != 0 || new_start != 0);
82 old_globals_start = old_start;
83 new_globals_start = new_start;
84 }
85
PatchTableSize(uint32_t old_size,uint32_t new_size)86 void CodeSpecialization::PatchTableSize(uint32_t old_size, uint32_t new_size) {
87 DCHECK(old_function_table_size == 0 && new_function_table_size == 0);
88 DCHECK(old_size != 0 || new_size != 0);
89 old_function_table_size = old_size;
90 new_function_table_size = new_size;
91 }
92
RelocateDirectCalls(Handle<WasmInstanceObject> instance)93 void CodeSpecialization::RelocateDirectCalls(
94 Handle<WasmInstanceObject> instance) {
95 DCHECK(relocate_direct_calls_instance.is_null());
96 DCHECK(!instance.is_null());
97 relocate_direct_calls_instance = instance;
98 }
99
RelocateObject(Handle<Object> old_obj,Handle<Object> new_obj)100 void CodeSpecialization::RelocateObject(Handle<Object> old_obj,
101 Handle<Object> new_obj) {
102 DCHECK(!old_obj.is_null() && !new_obj.is_null());
103 has_objects_to_relocate = true;
104 objects_to_relocate.Set(*old_obj, new_obj);
105 }
106
ApplyToWholeInstance(WasmInstanceObject * instance,ICacheFlushMode icache_flush_mode)107 bool CodeSpecialization::ApplyToWholeInstance(
108 WasmInstanceObject* instance, ICacheFlushMode icache_flush_mode) {
109 DisallowHeapAllocation no_gc;
110 WasmCompiledModule* compiled_module = instance->compiled_module();
111 FixedArray* code_table = compiled_module->ptr_to_code_table();
112 WasmModule* module = compiled_module->module();
113 std::vector<WasmFunction>* wasm_functions =
114 &compiled_module->module()->functions;
115 DCHECK_EQ(wasm_functions->size() +
116 compiled_module->module()->num_exported_functions,
117 code_table->length());
118
119 bool changed = false;
120 int func_index = module->num_imported_functions;
121
122 // Patch all wasm functions.
123 for (int num_wasm_functions = static_cast<int>(wasm_functions->size());
124 func_index < num_wasm_functions; ++func_index) {
125 Code* wasm_function = Code::cast(code_table->get(func_index));
126 changed |= ApplyToWasmCode(wasm_function, icache_flush_mode);
127 }
128
129 // Patch all exported functions.
130 for (auto exp : module->export_table) {
131 if (exp.kind != kExternalFunction) continue;
132 Code* export_wrapper = Code::cast(code_table->get(func_index));
133 DCHECK_EQ(Code::JS_TO_WASM_FUNCTION, export_wrapper->kind());
134 // There must be exactly one call to WASM_FUNCTION or WASM_TO_JS_FUNCTION.
135 int num_wasm_calls = 0;
136 for (RelocIterator it(export_wrapper,
137 RelocInfo::ModeMask(RelocInfo::CODE_TARGET));
138 !it.done(); it.next()) {
139 DCHECK(RelocInfo::IsCodeTarget(it.rinfo()->rmode()));
140 Code* code = Code::GetCodeFromTargetAddress(it.rinfo()->target_address());
141 // Ignore calls to other builtins like ToNumber.
142 if (code->kind() != Code::WASM_FUNCTION &&
143 code->kind() != Code::WASM_TO_JS_FUNCTION &&
144 code->builtin_index() != Builtins::kIllegal)
145 continue;
146 ++num_wasm_calls;
147 Code* new_code = Code::cast(code_table->get(exp.index));
148 DCHECK(new_code->kind() == Code::WASM_FUNCTION ||
149 new_code->kind() == Code::WASM_TO_JS_FUNCTION);
150 it.rinfo()->set_target_address(new_code->instruction_start(),
151 UPDATE_WRITE_BARRIER, SKIP_ICACHE_FLUSH);
152 changed = true;
153 }
154 DCHECK_EQ(1, num_wasm_calls);
155 func_index++;
156 }
157 DCHECK_EQ(code_table->length(), func_index);
158 return changed;
159 }
160
ApplyToWasmCode(Code * code,ICacheFlushMode icache_flush_mode)161 bool CodeSpecialization::ApplyToWasmCode(Code* code,
162 ICacheFlushMode icache_flush_mode) {
163 DisallowHeapAllocation no_gc;
164 DCHECK_EQ(Code::WASM_FUNCTION, code->kind());
165
166 bool reloc_mem_addr = old_mem_start != new_mem_start;
167 bool reloc_mem_size = old_mem_size != new_mem_size;
168 bool reloc_globals = old_globals_start || new_globals_start;
169 bool patch_table_size = old_function_table_size || new_function_table_size;
170 bool reloc_direct_calls = !relocate_direct_calls_instance.is_null();
171 bool reloc_objects = has_objects_to_relocate;
172
173 int reloc_mode = 0;
174 auto add_mode = [&reloc_mode](bool cond, RelocInfo::Mode mode) {
175 if (cond) reloc_mode |= RelocInfo::ModeMask(mode);
176 };
177 add_mode(reloc_mem_addr, RelocInfo::WASM_MEMORY_REFERENCE);
178 add_mode(reloc_mem_size, RelocInfo::WASM_MEMORY_SIZE_REFERENCE);
179 add_mode(reloc_globals, RelocInfo::WASM_GLOBAL_REFERENCE);
180 add_mode(patch_table_size, RelocInfo::WASM_FUNCTION_TABLE_SIZE_REFERENCE);
181 add_mode(reloc_direct_calls, RelocInfo::CODE_TARGET);
182 add_mode(reloc_objects, RelocInfo::EMBEDDED_OBJECT);
183
184 std::unique_ptr<PatchDirectCallsHelper> patch_direct_calls_helper;
185 bool changed = false;
186
187 for (RelocIterator it(code, reloc_mode); !it.done(); it.next()) {
188 RelocInfo::Mode mode = it.rinfo()->rmode();
189 switch (mode) {
190 case RelocInfo::WASM_MEMORY_REFERENCE:
191 DCHECK(reloc_mem_addr);
192 it.rinfo()->update_wasm_memory_reference(old_mem_start, new_mem_start,
193 icache_flush_mode);
194 changed = true;
195 break;
196 case RelocInfo::WASM_MEMORY_SIZE_REFERENCE:
197 DCHECK(reloc_mem_size);
198 it.rinfo()->update_wasm_memory_size(old_mem_size, new_mem_size,
199 icache_flush_mode);
200 changed = true;
201 break;
202 case RelocInfo::WASM_GLOBAL_REFERENCE:
203 DCHECK(reloc_globals);
204 it.rinfo()->update_wasm_global_reference(
205 old_globals_start, new_globals_start, icache_flush_mode);
206 changed = true;
207 break;
208 case RelocInfo::CODE_TARGET: {
209 DCHECK(reloc_direct_calls);
210 Code* old_code =
211 Code::GetCodeFromTargetAddress(it.rinfo()->target_address());
212 // Skip everything which is not a wasm call (stack checks, traps, ...).
213 if (old_code->kind() != Code::WASM_FUNCTION &&
214 old_code->kind() != Code::WASM_TO_JS_FUNCTION &&
215 old_code->builtin_index() != Builtins::kIllegal)
216 continue;
217 // Iterate simultaneously over the relocation information and the source
218 // position table. For each call in the reloc info, move the source
219 // position iterator forward to that position to find the byte offset of
220 // the respective call. Then extract the call index from the module wire
221 // bytes to find the new compiled function.
222 size_t offset = it.rinfo()->pc() - code->instruction_start();
223 if (!patch_direct_calls_helper) {
224 patch_direct_calls_helper.reset(new PatchDirectCallsHelper(
225 *relocate_direct_calls_instance, code));
226 }
227 int byte_pos = AdvanceSourcePositionTableIterator(
228 patch_direct_calls_helper->source_pos_it, offset);
229 int called_func_index = ExtractDirectCallIndex(
230 patch_direct_calls_helper->decoder,
231 patch_direct_calls_helper->func_bytes + byte_pos);
232 FixedArray* code_table =
233 relocate_direct_calls_instance->compiled_module()
234 ->ptr_to_code_table();
235 Code* new_code = Code::cast(code_table->get(called_func_index));
236 it.rinfo()->set_target_address(new_code->instruction_start(),
237 UPDATE_WRITE_BARRIER, icache_flush_mode);
238 changed = true;
239 } break;
240 case RelocInfo::EMBEDDED_OBJECT: {
241 DCHECK(reloc_objects);
242 Object* old = it.rinfo()->target_object();
243 Handle<Object>* new_obj = objects_to_relocate.Find(old);
244 if (new_obj) {
245 it.rinfo()->set_target_object(**new_obj, UPDATE_WRITE_BARRIER,
246 icache_flush_mode);
247 changed = true;
248 }
249 } break;
250 case RelocInfo::WASM_FUNCTION_TABLE_SIZE_REFERENCE:
251 DCHECK(patch_table_size);
252 it.rinfo()->update_wasm_function_table_size_reference(
253 old_function_table_size, new_function_table_size,
254 icache_flush_mode);
255 changed = true;
256 break;
257 default:
258 UNREACHABLE();
259 }
260 }
261
262 return changed;
263 }
264