blob: 56839b294891d218749744551358b0e8f063935d [file] [log] [blame]
machenbach@chromium.org528ce022013-09-23 14:09:36 +00001// Copyright 2013 the V8 project authors. All rights reserved.
2// Redistribution and use in source and binary forms, with or without
3// modification, are permitted provided that the following conditions are
4// met:
5//
6// * Redistributions of source code must retain the above copyright
7// notice, this list of conditions and the following disclaimer.
8// * Redistributions in binary form must reproduce the above
9// copyright notice, this list of conditions and the following
10// disclaimer in the documentation and/or other materials provided
11// with the distribution.
12// * Neither the name of Google Inc. nor the names of its
13// contributors may be used to endorse or promote products derived
14// from this software without specific prior written permission.
15//
16// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
28#include "hydrogen-alias-analysis.h"
29#include "hydrogen-load-elimination.h"
30#include "hydrogen-instructions.h"
bmeurer@chromium.org0fdb2a62013-10-21 07:19:36 +000031#include "hydrogen-flow-engine.h"
machenbach@chromium.org528ce022013-09-23 14:09:36 +000032
33namespace v8 {
34namespace internal {
35
bmeurer@chromium.org0fdb2a62013-10-21 07:19:36 +000036#define GLOBAL true
37#define TRACE(x) if (FLAG_trace_load_elimination) PrintF x
38
machenbach@chromium.org528ce022013-09-23 14:09:36 +000039static const int kMaxTrackedFields = 16;
40static const int kMaxTrackedObjects = 5;
41
42// An element in the field approximation list.
43class HFieldApproximation : public ZoneObject {
44 public: // Just a data blob.
45 HValue* object_;
machenbach@chromium.org528ce022013-09-23 14:09:36 +000046 HValue* last_value_;
47 HFieldApproximation* next_;
bmeurer@chromium.org0fdb2a62013-10-21 07:19:36 +000048
49 // Recursively copy the entire linked list of field approximations.
50 HFieldApproximation* Copy(Zone* zone) {
51 if (this == NULL) return NULL;
52 HFieldApproximation* copy = new(zone) HFieldApproximation();
53 copy->object_ = this->object_;
bmeurer@chromium.org0fdb2a62013-10-21 07:19:36 +000054 copy->last_value_ = this->last_value_;
55 copy->next_ = this->next_->Copy(zone);
56 return copy;
57 }
machenbach@chromium.org528ce022013-09-23 14:09:36 +000058};
59
60
61// The main datastructure used during load/store elimination. Each in-object
62// field is tracked separately. For each field, store a list of known field
63// values for known objects.
bmeurer@chromium.org0fdb2a62013-10-21 07:19:36 +000064class HLoadEliminationTable : public ZoneObject {
machenbach@chromium.org528ce022013-09-23 14:09:36 +000065 public:
66 HLoadEliminationTable(Zone* zone, HAliasAnalyzer* aliasing)
67 : zone_(zone), fields_(kMaxTrackedFields, zone), aliasing_(aliasing) { }
68
bmeurer@chromium.org0fdb2a62013-10-21 07:19:36 +000069 // The main processing of instructions.
70 HLoadEliminationTable* Process(HInstruction* instr, Zone* zone) {
71 switch (instr->opcode()) {
72 case HValue::kLoadNamedField: {
73 HLoadNamedField* l = HLoadNamedField::cast(instr);
74 TRACE((" process L%d field %d (o%d)\n",
75 instr->id(),
76 FieldOf(l->access()),
77 l->object()->ActualValue()->id()));
78 HValue* result = load(l);
79 if (result != instr) {
80 // The load can be replaced with a previous load or a value.
81 TRACE((" replace L%d -> v%d\n", instr->id(), result->id()));
82 instr->DeleteAndReplaceWith(result);
83 }
84 break;
85 }
86 case HValue::kStoreNamedField: {
87 HStoreNamedField* s = HStoreNamedField::cast(instr);
88 TRACE((" process S%d field %d (o%d) = v%d\n",
89 instr->id(),
90 FieldOf(s->access()),
91 s->object()->ActualValue()->id(),
92 s->value()->id()));
93 HValue* result = store(s);
94 if (result == NULL) {
95 // The store is redundant. Remove it.
96 TRACE((" remove S%d\n", instr->id()));
97 instr->DeleteAndReplaceWith(NULL);
98 }
99 break;
100 }
101 default: {
102 if (instr->CheckGVNFlag(kChangesInobjectFields)) {
103 TRACE((" kill-all i%d\n", instr->id()));
104 Kill();
105 break;
106 }
107 if (instr->CheckGVNFlag(kChangesMaps)) {
108 TRACE((" kill-maps i%d\n", instr->id()));
109 KillOffset(JSObject::kMapOffset);
110 }
111 if (instr->CheckGVNFlag(kChangesElementsKind)) {
112 TRACE((" kill-elements-kind i%d\n", instr->id()));
113 KillOffset(JSObject::kMapOffset);
114 KillOffset(JSObject::kElementsOffset);
115 }
116 if (instr->CheckGVNFlag(kChangesElementsPointer)) {
117 TRACE((" kill-elements i%d\n", instr->id()));
118 KillOffset(JSObject::kElementsOffset);
119 }
120 if (instr->CheckGVNFlag(kChangesOsrEntries)) {
121 TRACE((" kill-osr i%d\n", instr->id()));
122 Kill();
123 }
124 }
125 // Improvements possible:
126 // - learn from HCheckMaps for field 0
127 // - remove unobservable stores (write-after-write)
128 // - track cells
129 // - track globals
130 // - track roots
131 }
132 return this;
133 }
134
machenbach@chromium.org05150ab2014-01-29 08:13:29 +0000135 // Support for global analysis with HFlowEngine: Copy state to successor
136 // block.
137 HLoadEliminationTable* Copy(HBasicBlock* succ, HBasicBlock* from_block,
138 Zone* zone) {
bmeurer@chromium.org0fdb2a62013-10-21 07:19:36 +0000139 HLoadEliminationTable* copy =
140 new(zone) HLoadEliminationTable(zone, aliasing_);
141 copy->EnsureFields(fields_.length());
142 for (int i = 0; i < fields_.length(); i++) {
143 copy->fields_[i] = fields_[i]->Copy(zone);
144 }
145 if (FLAG_trace_load_elimination) {
146 TRACE((" copy-to B%d\n", succ->block_id()));
147 copy->Print();
148 }
149 return copy;
150 }
151
152 // Support for global analysis with HFlowEngine: Merge this state with
153 // the other incoming state.
machenbach@chromium.org05150ab2014-01-29 08:13:29 +0000154 HLoadEliminationTable* Merge(HBasicBlock* succ, HLoadEliminationTable* that,
155 HBasicBlock* that_block, Zone* zone) {
bmeurer@chromium.org0fdb2a62013-10-21 07:19:36 +0000156 if (that->fields_.length() < fields_.length()) {
157 // Drop fields not in the other table.
158 fields_.Rewind(that->fields_.length());
159 }
160 for (int i = 0; i < fields_.length(); i++) {
161 // Merge the field approximations for like fields.
162 HFieldApproximation* approx = fields_[i];
163 HFieldApproximation* prev = NULL;
164 while (approx != NULL) {
165 // TODO(titzer): Merging is O(N * M); sort?
166 HFieldApproximation* other = that->Find(approx->object_, i);
167 if (other == NULL || !Equal(approx->last_value_, other->last_value_)) {
168 // Kill an entry that doesn't agree with the other value.
169 if (prev != NULL) {
170 prev->next_ = approx->next_;
171 } else {
172 fields_[i] = approx->next_;
173 }
174 approx = approx->next_;
175 continue;
176 }
177 prev = approx;
178 approx = approx->next_;
179 }
180 }
machenbach@chromium.org05150ab2014-01-29 08:13:29 +0000181 if (FLAG_trace_load_elimination) {
182 TRACE((" merge-to B%d\n", succ->block_id()));
183 Print();
184 }
bmeurer@chromium.org0fdb2a62013-10-21 07:19:36 +0000185 return this;
186 }
187
188 friend class HLoadEliminationEffects; // Calls Kill() and others.
189 friend class HLoadEliminationPhase;
190
191 private:
machenbach@chromium.org528ce022013-09-23 14:09:36 +0000192 // Process a load instruction, updating internal table state. If a previous
193 // load or store for this object and field exists, return the new value with
194 // which the load should be replaced. Otherwise, return {instr}.
195 HValue* load(HLoadNamedField* instr) {
machenbach@chromium.org0a730362014-02-05 03:04:56 +0000196 // There must be no loads from non observable in-object properties.
197 ASSERT(!instr->access().IsInobject() ||
198 instr->access().existing_inobject_property());
199
machenbach@chromium.org528ce022013-09-23 14:09:36 +0000200 int field = FieldOf(instr->access());
201 if (field < 0) return instr;
202
203 HValue* object = instr->object()->ActualValue();
204 HFieldApproximation* approx = FindOrCreate(object, field);
205
206 if (approx->last_value_ == NULL) {
207 // Load is not redundant. Fill out a new entry.
machenbach@chromium.org528ce022013-09-23 14:09:36 +0000208 approx->last_value_ = instr;
209 return instr;
machenbach@chromium.org57a54ac2014-01-31 14:01:53 +0000210 } else if (approx->last_value_->block()->EqualToOrDominates(
211 instr->block())) {
machenbach@chromium.org528ce022013-09-23 14:09:36 +0000212 // Eliminate the load. Reuse previously stored value or load instruction.
213 return approx->last_value_;
machenbach@chromium.org57a54ac2014-01-31 14:01:53 +0000214 } else {
215 return instr;
machenbach@chromium.org528ce022013-09-23 14:09:36 +0000216 }
217 }
218
219 // Process a store instruction, updating internal table state. If a previous
220 // store to the same object and field makes this store redundant (e.g. because
221 // the stored values are the same), return NULL indicating that this store
222 // instruction is redundant. Otherwise, return {instr}.
223 HValue* store(HStoreNamedField* instr) {
machenbach@chromium.org0a730362014-02-05 03:04:56 +0000224 if (instr->access().IsInobject() &&
225 !instr->access().existing_inobject_property()) {
226 TRACE((" skipping non existing property initialization store\n"));
machenbach@chromium.org05150ab2014-01-29 08:13:29 +0000227 return instr;
228 }
229
machenbach@chromium.org528ce022013-09-23 14:09:36 +0000230 int field = FieldOf(instr->access());
bmeurer@chromium.org71f9fca2013-10-22 08:00:09 +0000231 if (field < 0) return KillIfMisaligned(instr);
machenbach@chromium.org528ce022013-09-23 14:09:36 +0000232
233 HValue* object = instr->object()->ActualValue();
234 HValue* value = instr->value();
235
machenbach@chromium.org528ce022013-09-23 14:09:36 +0000236 if (instr->has_transition()) {
hpayer@chromium.orgea9b8ba2013-12-20 19:22:39 +0000237 // A transition introduces a new field and alters the map of the object.
238 // Since the field in the object is new, it cannot alias existing entries.
239 // TODO(titzer): introduce a constant for the new map and remember it.
machenbach@chromium.org528ce022013-09-23 14:09:36 +0000240 KillFieldInternal(object, FieldOf(JSObject::kMapOffset), NULL);
hpayer@chromium.orgea9b8ba2013-12-20 19:22:39 +0000241 } else {
242 // Kill non-equivalent may-alias entries.
243 KillFieldInternal(object, field, value);
machenbach@chromium.org528ce022013-09-23 14:09:36 +0000244 }
245 HFieldApproximation* approx = FindOrCreate(object, field);
246
247 if (Equal(approx->last_value_, value)) {
248 // The store is redundant because the field already has this value.
249 return NULL;
250 } else {
251 // The store is not redundant. Update the entry.
machenbach@chromium.org528ce022013-09-23 14:09:36 +0000252 approx->last_value_ = value;
253 return instr;
254 }
255 }
256
257 // Kill everything in this table.
258 void Kill() {
259 fields_.Rewind(0);
260 }
261
262 // Kill all entries matching the given offset.
263 void KillOffset(int offset) {
264 int field = FieldOf(offset);
265 if (field >= 0 && field < fields_.length()) {
266 fields_[field] = NULL;
267 }
268 }
269
bmeurer@chromium.org71f9fca2013-10-22 08:00:09 +0000270 // Kill all entries aliasing the given store.
271 void KillStore(HStoreNamedField* s) {
272 int field = FieldOf(s->access());
273 if (field >= 0) {
274 KillFieldInternal(s->object()->ActualValue(), field, s->value());
275 } else {
276 KillIfMisaligned(s);
277 }
278 }
279
280 // Kill multiple entries in the case of a misaligned store.
281 HValue* KillIfMisaligned(HStoreNamedField* instr) {
282 HObjectAccess access = instr->access();
283 if (access.IsInobject()) {
284 int offset = access.offset();
285 if ((offset % kPointerSize) != 0) {
286 // Kill the field containing the first word of the access.
287 HValue* object = instr->object()->ActualValue();
288 int field = offset / kPointerSize;
289 KillFieldInternal(object, field, NULL);
290
291 // Kill the next field in case of overlap.
machenbach@chromium.org935a7792013-11-12 09:05:18 +0000292 int size = access.representation().size();
bmeurer@chromium.org71f9fca2013-10-22 08:00:09 +0000293 int next_field = (offset + size - 1) / kPointerSize;
294 if (next_field != field) KillFieldInternal(object, next_field, NULL);
295 }
296 }
297 return instr;
298 }
299
bmeurer@chromium.org0fdb2a62013-10-21 07:19:36 +0000300 // Find an entry for the given object and field pair.
301 HFieldApproximation* Find(HValue* object, int field) {
302 // Search for a field approximation for this object.
303 HFieldApproximation* approx = fields_[field];
304 while (approx != NULL) {
305 if (aliasing_->MustAlias(object, approx->object_)) return approx;
306 approx = approx->next_;
machenbach@chromium.org528ce022013-09-23 14:09:36 +0000307 }
bmeurer@chromium.org0fdb2a62013-10-21 07:19:36 +0000308 return NULL;
machenbach@chromium.org528ce022013-09-23 14:09:36 +0000309 }
310
machenbach@chromium.org528ce022013-09-23 14:09:36 +0000311 // Find or create an entry for the given object and field pair.
312 HFieldApproximation* FindOrCreate(HValue* object, int field) {
313 EnsureFields(field + 1);
314
315 // Search for a field approximation for this object.
316 HFieldApproximation* approx = fields_[field];
317 int count = 0;
318 while (approx != NULL) {
319 if (aliasing_->MustAlias(object, approx->object_)) return approx;
320 count++;
321 approx = approx->next_;
322 }
323
324 if (count >= kMaxTrackedObjects) {
325 // Pull the last entry off the end and repurpose it for this object.
326 approx = ReuseLastApproximation(field);
327 } else {
328 // Allocate a new entry.
329 approx = new(zone_) HFieldApproximation();
330 }
331
332 // Insert the entry at the head of the list.
333 approx->object_ = object;
machenbach@chromium.org528ce022013-09-23 14:09:36 +0000334 approx->last_value_ = NULL;
335 approx->next_ = fields_[field];
336 fields_[field] = approx;
337
338 return approx;
339 }
340
341 // Kill all entries for a given field that _may_ alias the given object
342 // and do _not_ have the given value.
343 void KillFieldInternal(HValue* object, int field, HValue* value) {
344 if (field >= fields_.length()) return; // Nothing to do.
345
346 HFieldApproximation* approx = fields_[field];
347 HFieldApproximation* prev = NULL;
348 while (approx != NULL) {
349 if (aliasing_->MayAlias(object, approx->object_)) {
350 if (!Equal(approx->last_value_, value)) {
351 // Kill an aliasing entry that doesn't agree on the value.
352 if (prev != NULL) {
353 prev->next_ = approx->next_;
354 } else {
355 fields_[field] = approx->next_;
356 }
357 approx = approx->next_;
358 continue;
359 }
360 }
361 prev = approx;
362 approx = approx->next_;
363 }
364 }
365
366 bool Equal(HValue* a, HValue* b) {
367 if (a == b) return true;
yangguo@chromium.orgcc536052013-11-29 11:43:20 +0000368 if (a != NULL && b != NULL && a->CheckFlag(HValue::kUseGVN)) {
369 return a->Equals(b);
370 }
machenbach@chromium.org528ce022013-09-23 14:09:36 +0000371 return false;
372 }
373
374 // Remove the last approximation for a field so that it can be reused.
375 // We reuse the last entry because it was the first inserted and is thus
376 // farthest away from the current instruction.
377 HFieldApproximation* ReuseLastApproximation(int field) {
378 HFieldApproximation* approx = fields_[field];
379 ASSERT(approx != NULL);
380
381 HFieldApproximation* prev = NULL;
382 while (approx->next_ != NULL) {
383 prev = approx;
384 approx = approx->next_;
385 }
386 if (prev != NULL) prev->next_ = NULL;
387 return approx;
388 }
389
bmeurer@chromium.org0fdb2a62013-10-21 07:19:36 +0000390 // Compute the field index for the given object access; -1 if not tracked.
391 int FieldOf(HObjectAccess access) {
392 return access.IsInobject() ? FieldOf(access.offset()) : -1;
machenbach@chromium.org528ce022013-09-23 14:09:36 +0000393 }
394
bmeurer@chromium.org0fdb2a62013-10-21 07:19:36 +0000395 // Compute the field index for the given in-object offset; -1 if not tracked.
machenbach@chromium.org528ce022013-09-23 14:09:36 +0000396 int FieldOf(int offset) {
397 if (offset >= kMaxTrackedFields * kPointerSize) return -1;
bmeurer@chromium.org71f9fca2013-10-22 08:00:09 +0000398 // TODO(titzer): track misaligned loads in a separate list?
399 if ((offset % kPointerSize) != 0) return -1; // Ignore misaligned accesses.
machenbach@chromium.org528ce022013-09-23 14:09:36 +0000400 return offset / kPointerSize;
401 }
402
bmeurer@chromium.org0fdb2a62013-10-21 07:19:36 +0000403 // Ensure internal storage for the given number of fields.
404 void EnsureFields(int num_fields) {
405 if (fields_.length() < num_fields) {
406 fields_.AddBlock(NULL, num_fields - fields_.length(), zone_);
407 }
408 }
409
410 // Print this table to stdout.
411 void Print() {
412 for (int i = 0; i < fields_.length(); i++) {
413 PrintF(" field %d: ", i);
414 for (HFieldApproximation* a = fields_[i]; a != NULL; a = a->next_) {
415 PrintF("[o%d =", a->object_->id());
bmeurer@chromium.org0fdb2a62013-10-21 07:19:36 +0000416 if (a->last_value_ != NULL) PrintF(" v%d", a->last_value_->id());
417 PrintF("] ");
418 }
419 PrintF("\n");
420 }
421 }
422
machenbach@chromium.org528ce022013-09-23 14:09:36 +0000423 Zone* zone_;
424 ZoneList<HFieldApproximation*> fields_;
425 HAliasAnalyzer* aliasing_;
426};
427
428
bmeurer@chromium.org0fdb2a62013-10-21 07:19:36 +0000429// Support for HFlowEngine: collect store effects within loops.
430class HLoadEliminationEffects : public ZoneObject {
431 public:
432 explicit HLoadEliminationEffects(Zone* zone)
433 : zone_(zone),
434 maps_stored_(false),
435 fields_stored_(false),
436 elements_stored_(false),
437 stores_(5, zone) { }
438
439 inline bool Disabled() {
440 return false; // Effects are _not_ disabled.
machenbach@chromium.org528ce022013-09-23 14:09:36 +0000441 }
machenbach@chromium.org528ce022013-09-23 14:09:36 +0000442
bmeurer@chromium.org0fdb2a62013-10-21 07:19:36 +0000443 // Process a possibly side-effecting instruction.
444 void Process(HInstruction* instr, Zone* zone) {
machenbach@chromium.org528ce022013-09-23 14:09:36 +0000445 switch (instr->opcode()) {
bmeurer@chromium.org0fdb2a62013-10-21 07:19:36 +0000446 case HValue::kStoreNamedField: {
447 stores_.Add(HStoreNamedField::cast(instr), zone_);
machenbach@chromium.org528ce022013-09-23 14:09:36 +0000448 break;
449 }
bmeurer@chromium.org0fdb2a62013-10-21 07:19:36 +0000450 case HValue::kOsrEntry: {
451 // Kill everything. Loads must not be hoisted past the OSR entry.
452 maps_stored_ = true;
453 fields_stored_ = true;
454 elements_stored_ = true;
machenbach@chromium.org528ce022013-09-23 14:09:36 +0000455 }
456 default: {
bmeurer@chromium.org0fdb2a62013-10-21 07:19:36 +0000457 fields_stored_ |= instr->CheckGVNFlag(kChangesInobjectFields);
458 maps_stored_ |= instr->CheckGVNFlag(kChangesMaps);
459 maps_stored_ |= instr->CheckGVNFlag(kChangesElementsKind);
460 elements_stored_ |= instr->CheckGVNFlag(kChangesElementsKind);
461 elements_stored_ |= instr->CheckGVNFlag(kChangesElementsPointer);
machenbach@chromium.org528ce022013-09-23 14:09:36 +0000462 }
bmeurer@chromium.org0fdb2a62013-10-21 07:19:36 +0000463 }
464 }
465
466 // Apply these effects to the given load elimination table.
467 void Apply(HLoadEliminationTable* table) {
468 if (fields_stored_) {
469 table->Kill();
470 return;
471 }
472 if (maps_stored_) {
473 table->KillOffset(JSObject::kMapOffset);
474 }
475 if (elements_stored_) {
476 table->KillOffset(JSObject::kElementsOffset);
machenbach@chromium.org528ce022013-09-23 14:09:36 +0000477 }
478
bmeurer@chromium.org0fdb2a62013-10-21 07:19:36 +0000479 // Kill non-agreeing fields for each store contained in these effects.
480 for (int i = 0; i < stores_.length(); i++) {
bmeurer@chromium.org71f9fca2013-10-22 08:00:09 +0000481 table->KillStore(stores_[i]);
bmeurer@chromium.org0fdb2a62013-10-21 07:19:36 +0000482 }
483 }
484
485 // Union these effects with the other effects.
486 void Union(HLoadEliminationEffects* that, Zone* zone) {
487 maps_stored_ |= that->maps_stored_;
488 fields_stored_ |= that->fields_stored_;
489 elements_stored_ |= that->elements_stored_;
490 for (int i = 0; i < that->stores_.length(); i++) {
491 stores_.Add(that->stores_[i], zone);
492 }
493 }
494
495 private:
496 Zone* zone_;
497 bool maps_stored_ : 1;
498 bool fields_stored_ : 1;
499 bool elements_stored_ : 1;
500 ZoneList<HStoreNamedField*> stores_;
501};
502
503
504// The main routine of the analysis phase. Use the HFlowEngine for either a
505// local or a global analysis.
506void HLoadEliminationPhase::Run() {
507 HFlowEngine<HLoadEliminationTable, HLoadEliminationEffects>
508 engine(graph(), zone());
509 HAliasAnalyzer aliasing;
510 HLoadEliminationTable* table =
511 new(zone()) HLoadEliminationTable(zone(), &aliasing);
512
513 if (GLOBAL) {
514 // Perform a global analysis.
515 engine.AnalyzeDominatedBlocks(graph()->blocks()->at(0), table);
516 } else {
517 // Perform only local analysis.
518 for (int i = 0; i < graph()->blocks()->length(); i++) {
519 table->Kill();
520 engine.AnalyzeOneBlock(graph()->blocks()->at(i), table);
machenbach@chromium.org528ce022013-09-23 14:09:36 +0000521 }
522 }
523}
524
machenbach@chromium.org528ce022013-09-23 14:09:36 +0000525} } // namespace v8::internal