Flutter Engine
The Flutter Engine
code_patcher_x64.cc
Go to the documentation of this file.
1// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
2// for details. All rights reserved. Use of this source code is governed by a
3// BSD-style license that can be found in the LICENSE file.
4
5#include "vm/globals.h" // Needed here to get TARGET_ARCH_X64.
6#if defined(TARGET_ARCH_X64)
7
8#include "vm/code_patcher.h"
9#include "vm/cpu.h"
10#include "vm/dart_entry.h"
11#include "vm/instructions.h"
12#include "vm/object.h"
13#include "vm/object_store.h"
14#include "vm/raw_object.h"
16
17namespace dart {
18
19// callq [CODE_REG + entry_point_offset (disp8)]
20static const int16_t kCallPatternJIT[] = {
21 0x41, 0xff, 0x54, 0x24, -1,
22};
23
24// callq [TMP + entry_point_offset (disp8)]
25static const int16_t kCallPatternAOT[] = {
26 0x41,
27 0xff,
28 0x53,
29 -1,
30};
31
32static const intptr_t kLoadCodeFromPoolInstructionLength = 3;
33static const intptr_t kLoadCodeFromPoolDisp8PatternLength =
34 kLoadCodeFromPoolInstructionLength + 1;
35static const intptr_t kLoadCodeFromPoolDisp32PatternLength =
36 kLoadCodeFromPoolInstructionLength + 4;
37
38// movq CODE_REG, [PP + disp8]
39static const int16_t
40 kLoadCodeFromPoolDisp8JIT[kLoadCodeFromPoolDisp8PatternLength] = {
41 0x4d,
42 0x8b,
43 0x67,
44 -1,
45};
46
47// movq CODE_REG, [PP + disp32]
48static const int16_t
49 kLoadCodeFromPoolDisp32JIT[kLoadCodeFromPoolDisp32PatternLength] = {
50 0x4d, 0x8b, 0xa7, -1, -1, -1, -1,
51};
52
53// movq TMP, [PP + disp8]
54static const int16_t
55 kLoadCodeFromPoolDisp8AOT[kLoadCodeFromPoolDisp8PatternLength] = {
56 0x4d,
57 0x8b,
58 0x5f,
59 -1,
60};
61
62// movq TMP, [PP + disp32]
63static const int16_t
64 kLoadCodeFromPoolDisp32AOT[kLoadCodeFromPoolDisp32PatternLength] = {
65 0x4d, 0x8b, 0x9f, -1, -1, -1, -1,
66};
67
68static void MatchCallPattern(uword* pc) {
69 const int16_t* call_pattern =
70 FLAG_precompiled_mode ? kCallPatternAOT : kCallPatternJIT;
71 const intptr_t call_pattern_length = FLAG_precompiled_mode
72 ? ARRAY_SIZE(kCallPatternAOT)
73 : ARRAY_SIZE(kCallPatternJIT);
74
75 // callq [reg + entry_point_offset]
76 if (MatchesPattern(*pc, call_pattern, call_pattern_length)) {
77 *pc -= call_pattern_length;
78 } else {
79 FATAL("Expected `call [%s + offs]` at %" Px,
80 FLAG_precompiled_mode ? "TMP" : "CODE_REG", *pc);
81 }
82}
83
84static void MatchCodeLoadFromPool(uword* pc, intptr_t* code_index) {
85 const int16_t* load_code_disp8_pattern = FLAG_precompiled_mode
86 ? kLoadCodeFromPoolDisp8AOT
87 : kLoadCodeFromPoolDisp8JIT;
88 const int16_t* load_code_disp32_pattern = FLAG_precompiled_mode
89 ? kLoadCodeFromPoolDisp32AOT
90 : kLoadCodeFromPoolDisp32JIT;
91
92 if (MatchesPattern(*pc, load_code_disp8_pattern,
93 kLoadCodeFromPoolDisp8PatternLength)) {
94 *pc -= kLoadCodeFromPoolDisp8PatternLength;
95 *code_index =
96 IndexFromPPLoadDisp8(*pc + kLoadCodeFromPoolInstructionLength);
97 } else if (MatchesPattern(*pc, load_code_disp32_pattern,
98 kLoadCodeFromPoolDisp32PatternLength)) {
99 *pc -= kLoadCodeFromPoolDisp32PatternLength;
100 *code_index =
101 IndexFromPPLoadDisp32(*pc + kLoadCodeFromPoolInstructionLength);
102 } else {
103 FATAL("Expected `movq %s, [PP + imm8|imm32]` at %" Px,
104 FLAG_precompiled_mode ? "TMP" : "CODE_REG", *pc);
105 }
106}
107
108class UnoptimizedCall : public ValueObject {
109 public:
110 UnoptimizedCall(uword return_address, const Code& code)
111 : object_pool_(ObjectPool::Handle(code.GetObjectPool())),
112 code_index_(-1),
113 argument_index_(-1) {
114 uword pc = return_address;
115
116 MatchCallPattern(&pc);
117 MatchCodeLoadFromPool(&pc, &code_index_);
118 ASSERT(Object::Handle(object_pool_.ObjectAt(code_index_)).IsCode());
119
120 // movq RBX, [PP + offset]
121 static int16_t load_argument_disp8[] = {
122 0x49, 0x8b, 0x5f, -1, //
123 };
124 static int16_t load_argument_disp32[] = {
125 0x49, 0x8b, 0x9f, -1, -1, -1, -1,
126 };
127 if (MatchesPattern(pc, load_argument_disp8,
128 ARRAY_SIZE(load_argument_disp8))) {
129 pc -= ARRAY_SIZE(load_argument_disp8);
130 argument_index_ = IndexFromPPLoadDisp8(pc + 3);
131 } else if (MatchesPattern(pc, load_argument_disp32,
132 ARRAY_SIZE(load_argument_disp32))) {
133 pc -= ARRAY_SIZE(load_argument_disp32);
134 argument_index_ = IndexFromPPLoadDisp32(pc + 3);
135 } else {
136 FATAL("Failed to decode at %" Px, pc);
137 }
138 }
139
140 intptr_t argument_index() const { return argument_index_; }
141
142 CodePtr target() const {
143 Code& code = Code::Handle();
144 code ^= object_pool_.ObjectAt(code_index_);
145 return code.ptr();
146 }
147
148 void set_target(const Code& target) const {
149 object_pool_.SetObjectAt(code_index_, target);
150 // No need to flush the instruction cache, since the code is not modified.
151 }
152
153 protected:
154 const ObjectPool& object_pool_;
155 intptr_t code_index_;
156 intptr_t argument_index_;
157
158 private:
159 uword start_;
160 DISALLOW_IMPLICIT_CONSTRUCTORS(UnoptimizedCall);
161};
162
163class NativeCall : public UnoptimizedCall {
164 public:
165 NativeCall(uword return_address, const Code& code)
166 : UnoptimizedCall(return_address, code) {}
167
168 NativeFunction native_function() const {
169 return reinterpret_cast<NativeFunction>(
170 object_pool_.RawValueAt(argument_index()));
171 }
172
173 void set_native_function(NativeFunction func) const {
174 object_pool_.SetRawValueAt(argument_index(), reinterpret_cast<uword>(func));
175 }
176
177 private:
179};
180
181class InstanceCall : public UnoptimizedCall {
182 public:
183 InstanceCall(uword return_address, const Code& code)
184 : UnoptimizedCall(return_address, code) {
185#if defined(DEBUG)
186 Object& test_data = Object::Handle(data());
187 ASSERT(test_data.IsArray() || test_data.IsICData() ||
188 test_data.IsMegamorphicCache());
189 if (test_data.IsICData()) {
190 ASSERT(ICData::Cast(test_data).NumArgsTested() > 0);
191 }
192#endif // DEBUG
193 }
194
195 ObjectPtr data() const { return object_pool_.ObjectAt(argument_index()); }
196 void set_data(const Object& data) const {
197 ASSERT(data.IsArray() || data.IsICData() || data.IsMegamorphicCache());
198 object_pool_.SetObjectAt(argument_index(), data);
199 }
200
201 private:
202 DISALLOW_IMPLICIT_CONSTRUCTORS(InstanceCall);
203};
204
205class UnoptimizedStaticCall : public UnoptimizedCall {
206 public:
207 UnoptimizedStaticCall(uword return_address, const Code& caller_code)
208 : UnoptimizedCall(return_address, caller_code) {
209#if defined(DEBUG)
210 ICData& test_ic_data = ICData::Handle();
211 test_ic_data ^= ic_data();
212 ASSERT(test_ic_data.NumArgsTested() >= 0);
213#endif // DEBUG
214 }
215
216 ObjectPtr ic_data() const { return object_pool_.ObjectAt(argument_index()); }
217
218 private:
219 DISALLOW_IMPLICIT_CONSTRUCTORS(UnoptimizedStaticCall);
220};
221
222// The expected pattern of a call where the target is loaded from
223// the object pool.
224class PoolPointerCall : public ValueObject {
225 public:
226 explicit PoolPointerCall(uword return_address, const Code& caller_code)
227 : object_pool_(ObjectPool::Handle(caller_code.GetObjectPool())),
228 code_index_(-1) {
229 uword pc = return_address;
230
231 MatchCallPattern(&pc);
232 MatchCodeLoadFromPool(&pc, &code_index_);
233 ASSERT(Object::Handle(object_pool_.ObjectAt(code_index_)).IsCode());
234 }
235
236 CodePtr Target() const {
237 Code& code = Code::Handle();
238 code ^= object_pool_.ObjectAt(code_index_);
239 return code.ptr();
240 }
241
242 void SetTarget(const Code& target) const {
243 object_pool_.SetObjectAt(code_index_, target);
244 // No need to flush the instruction cache, since the code is not modified.
245 }
246
247 protected:
248 const ObjectPool& object_pool_;
249 intptr_t code_index_;
250
251 private:
252 DISALLOW_IMPLICIT_CONSTRUCTORS(PoolPointerCall);
253};
254
255// Instance call that can switch between a direct monomorphic call, an IC call,
256// and a megamorphic call.
257// load guarded cid load ICData load MegamorphicCache
258// load monomorphic target <-> load ICLookup stub -> load MMLookup stub
259// call target.entry call stub.entry call stub.entry
260class SwitchableCallBase : public ValueObject {
261 public:
262 explicit SwitchableCallBase(const ObjectPool& object_pool)
263 : object_pool_(object_pool), target_index_(-1), data_index_(-1) {}
264
265 intptr_t data_index() const { return data_index_; }
266 intptr_t target_index() const { return target_index_; }
267
268 ObjectPtr data() const { return object_pool_.ObjectAt(data_index()); }
269
270 void SetData(const Object& data) const {
271 ASSERT(!Object::Handle(object_pool_.ObjectAt(data_index())).IsCode());
272 object_pool_.SetObjectAt(data_index(), data);
273 // No need to flush the instruction cache, since the code is not modified.
274 }
275
276 protected:
277 const ObjectPool& object_pool_;
278 intptr_t target_index_;
279 intptr_t data_index_;
280
281 private:
282 DISALLOW_IMPLICIT_CONSTRUCTORS(SwitchableCallBase);
283};
284
285// See [SwitchableCallBase] for a switchable calls in general.
286//
287// The target slot is always a [Code] object: Either the code of the
288// monomorphic function or a stub code.
289class SwitchableCall : public SwitchableCallBase {
290 public:
291 SwitchableCall(uword return_address, const Code& caller_code)
292 : SwitchableCallBase(ObjectPool::Handle(caller_code.GetObjectPool())) {
293 ASSERT(caller_code.ContainsInstructionAt(return_address));
294 uword pc = return_address;
295
296 // callq RCX
297 static int16_t call_pattern[] = {
298 0xff, 0xd1, //
299 };
300 if (MatchesPattern(pc, call_pattern, ARRAY_SIZE(call_pattern))) {
301 pc -= ARRAY_SIZE(call_pattern);
302 } else {
303 FATAL("Failed to decode at %" Px, pc);
304 }
305
306 // movq RBX, [PP + offset]
307 static int16_t load_data_disp8[] = {
308 0x49, 0x8b, 0x5f, -1, //
309 };
310 static int16_t load_data_disp32[] = {
311 0x49, 0x8b, 0x9f, -1, -1, -1, -1,
312 };
313 if (MatchesPattern(pc, load_data_disp8, ARRAY_SIZE(load_data_disp8))) {
314 pc -= ARRAY_SIZE(load_data_disp8);
315 data_index_ = IndexFromPPLoadDisp8(pc + 3);
316 } else if (MatchesPattern(pc, load_data_disp32,
317 ARRAY_SIZE(load_data_disp32))) {
318 pc -= ARRAY_SIZE(load_data_disp32);
319 data_index_ = IndexFromPPLoadDisp32(pc + 3);
320 } else {
321 FATAL("Failed to decode at %" Px, pc);
322 }
323 ASSERT(!Object::Handle(object_pool_.ObjectAt(data_index_)).IsCode());
324
325 // movq rcx, [CODE_REG + entrypoint_offset]
326 static int16_t load_entry_pattern[] = {
327 0x49, 0x8b, 0x4c, 0x24, -1,
328 };
329 if (MatchesPattern(pc, load_entry_pattern,
330 ARRAY_SIZE(load_entry_pattern))) {
331 pc -= ARRAY_SIZE(load_entry_pattern);
332 } else {
333 FATAL("Failed to decode at %" Px, pc);
334 }
335
336 // movq CODE_REG, [PP + offset]
337 static int16_t load_code_disp8[] = {
338 0x4d, 0x8b, 0x67, -1, //
339 };
340 static int16_t load_code_disp32[] = {
341 0x4d, 0x8b, 0xa7, -1, -1, -1, -1,
342 };
343 if (MatchesPattern(pc, load_code_disp8, ARRAY_SIZE(load_code_disp8))) {
344 pc -= ARRAY_SIZE(load_code_disp8);
345 target_index_ = IndexFromPPLoadDisp8(pc + 3);
346 } else if (MatchesPattern(pc, load_code_disp32,
347 ARRAY_SIZE(load_code_disp32))) {
348 pc -= ARRAY_SIZE(load_code_disp32);
349 target_index_ = IndexFromPPLoadDisp32(pc + 3);
350 } else {
351 FATAL("Failed to decode at %" Px, pc);
352 }
353 ASSERT(Object::Handle(object_pool_.ObjectAt(target_index_)).IsCode());
354 }
355
356 void SetTarget(const Code& target) const {
357 ASSERT(Object::Handle(object_pool_.ObjectAt(target_index())).IsCode());
358 object_pool_.SetObjectAt(target_index(), target);
359 // No need to flush the instruction cache, since the code is not modified.
360 }
361
362 uword target_entry() const {
363 return Code::Handle(Code::RawCast(object_pool_.ObjectAt(target_index())))
364 .MonomorphicEntryPoint();
365 }
366};
367
368// See [SwitchableCallBase] for a switchable calls in general.
369//
370// The target slot is always a direct entrypoint address: Either the entry point
371// of the monomorphic function or a stub entry point.
372class BareSwitchableCall : public SwitchableCallBase {
373 public:
374 explicit BareSwitchableCall(uword return_address)
375 : SwitchableCallBase(ObjectPool::Handle(
376 IsolateGroup::Current()->object_store()->global_object_pool())) {
377 uword pc = return_address;
378
379 // callq RCX
380 static int16_t call_pattern[] = {
381 0xff, 0xd1, //
382 };
383 if (MatchesPattern(pc, call_pattern, ARRAY_SIZE(call_pattern))) {
384 pc -= ARRAY_SIZE(call_pattern);
385 } else {
386 FATAL("Failed to decode at %" Px, pc);
387 }
388
389 // movq RBX, [PP + offset]
390 static int16_t load_data_disp8[] = {
391 0x49, 0x8b, 0x5f, -1, //
392 };
393 static int16_t load_data_disp32[] = {
394 0x49, 0x8b, 0x9f, -1, -1, -1, -1,
395 };
396 if (MatchesPattern(pc, load_data_disp8, ARRAY_SIZE(load_data_disp8))) {
397 pc -= ARRAY_SIZE(load_data_disp8);
398 data_index_ = IndexFromPPLoadDisp8(pc + 3);
399 } else if (MatchesPattern(pc, load_data_disp32,
400 ARRAY_SIZE(load_data_disp32))) {
401 pc -= ARRAY_SIZE(load_data_disp32);
402 data_index_ = IndexFromPPLoadDisp32(pc + 3);
403 } else {
404 FATAL("Failed to decode at %" Px, pc);
405 }
406 ASSERT(!Object::Handle(object_pool_.ObjectAt(data_index_)).IsCode());
407
408 // movq RCX, [PP + offset]
409 static int16_t load_code_disp8[] = {
410 0x49, 0x8b, 0x4f, -1, //
411 };
412 static int16_t load_code_disp32[] = {
413 0x49, 0x8b, 0x8f, -1, -1, -1, -1,
414 };
415 if (MatchesPattern(pc, load_code_disp8, ARRAY_SIZE(load_code_disp8))) {
416 pc -= ARRAY_SIZE(load_code_disp8);
417 target_index_ = IndexFromPPLoadDisp8(pc + 3);
418 } else if (MatchesPattern(pc, load_code_disp32,
419 ARRAY_SIZE(load_code_disp32))) {
420 pc -= ARRAY_SIZE(load_code_disp32);
421 target_index_ = IndexFromPPLoadDisp32(pc + 3);
422 } else {
423 FATAL("Failed to decode at %" Px, pc);
424 }
425 ASSERT(object_pool_.TypeAt(target_index_) ==
426 ObjectPool::EntryType::kImmediate);
427 }
428
429 void SetTarget(const Code& target) const {
430 ASSERT(object_pool_.TypeAt(target_index()) ==
431 ObjectPool::EntryType::kImmediate);
432 object_pool_.SetRawValueAt(target_index(), target.MonomorphicEntryPoint());
433 }
434
435 uword target_entry() const { return object_pool_.RawValueAt(target_index()); }
436};
437
438CodePtr CodePatcher::GetStaticCallTargetAt(uword return_address,
439 const Code& code) {
440 ASSERT(code.ContainsInstructionAt(return_address));
441 PoolPointerCall call(return_address, code);
442 return call.Target();
443}
444
445void CodePatcher::PatchStaticCallAt(uword return_address,
446 const Code& code,
447 const Code& new_target) {
448 PatchPoolPointerCallAt(return_address, code, new_target);
449}
450
452 const Code& code,
453 const Code& new_target) {
454 ASSERT(code.ContainsInstructionAt(return_address));
455 PoolPointerCall call(return_address, code);
456 call.SetTarget(new_target);
457}
458
459CodePtr CodePatcher::GetInstanceCallAt(uword return_address,
460 const Code& caller_code,
461 Object* data) {
462 ASSERT(caller_code.ContainsInstructionAt(return_address));
463 InstanceCall call(return_address, caller_code);
464 if (data != nullptr) {
465 *data = call.data();
466 }
467 return call.target();
468}
469
470void CodePatcher::PatchInstanceCallAt(uword return_address,
471 const Code& caller_code,
472 const Object& data,
473 const Code& target) {
474 auto thread = Thread::Current();
475 thread->isolate_group()->RunWithStoppedMutators([&]() {
476 PatchInstanceCallAtWithMutatorsStopped(thread, return_address, caller_code,
477 data, target);
478 });
479}
480
482 Thread* thread,
483 uword return_address,
484 const Code& caller_code,
485 const Object& data,
486 const Code& target) {
487 ASSERT(caller_code.ContainsInstructionAt(return_address));
488 InstanceCall call(return_address, caller_code);
489 call.set_data(data);
490 call.set_target(target);
491}
492
494 UNREACHABLE();
495}
496
497FunctionPtr CodePatcher::GetUnoptimizedStaticCallAt(uword return_address,
498 const Code& caller_code,
499 ICData* ic_data_result) {
500 ASSERT(caller_code.ContainsInstructionAt(return_address));
501 UnoptimizedStaticCall static_call(return_address, caller_code);
502 ICData& ic_data = ICData::Handle();
503 ic_data ^= static_call.ic_data();
504 if (ic_data_result != nullptr) {
505 *ic_data_result = ic_data.ptr();
506 }
507 return ic_data.GetTargetAt(0);
508}
509
510void CodePatcher::PatchSwitchableCallAt(uword return_address,
511 const Code& caller_code,
512 const Object& data,
513 const Code& target) {
514 auto thread = Thread::Current();
515 // Ensure all threads are suspended as we update data and target pair.
516 thread->isolate_group()->RunWithStoppedMutators([&]() {
517 PatchSwitchableCallAtWithMutatorsStopped(thread, return_address,
518 caller_code, data, target);
519 });
520}
521
523 Thread* thread,
524 uword return_address,
525 const Code& caller_code,
526 const Object& data,
527 const Code& target) {
528 if (FLAG_precompiled_mode) {
529 BareSwitchableCall call(return_address);
530 call.SetData(data);
531 call.SetTarget(target);
532 } else {
533 SwitchableCall call(return_address, caller_code);
534 call.SetData(data);
535 call.SetTarget(target);
536 }
537}
538
540 const Code& caller_code) {
541 if (FLAG_precompiled_mode) {
542 BareSwitchableCall call(return_address);
543 return call.target_entry();
544 } else {
545 SwitchableCall call(return_address, caller_code);
546 return call.target_entry();
547 }
548}
549
550ObjectPtr CodePatcher::GetSwitchableCallDataAt(uword return_address,
551 const Code& caller_code) {
552 if (FLAG_precompiled_mode) {
553 BareSwitchableCall call(return_address);
554 return call.data();
555 } else {
556 SwitchableCall call(return_address, caller_code);
557 return call.data();
558 }
559}
560
561void CodePatcher::PatchNativeCallAt(uword return_address,
562 const Code& caller_code,
564 const Code& trampoline) {
566 ASSERT(caller_code.ContainsInstructionAt(return_address));
567 NativeCall call(return_address, caller_code);
568 call.set_target(trampoline);
569 call.set_native_function(target);
570 });
571}
572
573CodePtr CodePatcher::GetNativeCallAt(uword return_address,
574 const Code& caller_code,
576 ASSERT(caller_code.ContainsInstructionAt(return_address));
577 NativeCall call(return_address, caller_code);
578 *target = call.native_function();
579 return call.target();
580}
581
582} // namespace dart
583
584#endif // defined TARGET_ARCH_X64
#define UNREACHABLE()
Definition: assert.h:248
static void PatchInstanceCallAt(uword return_address, const Code &caller_code, const Object &data, const Code &target)
static void PatchPoolPointerCallAt(uword return_address, const Code &code, const Code &new_target)
static CodePtr GetStaticCallTargetAt(uword return_address, const Code &code)
static void PatchSwitchableCallAtWithMutatorsStopped(Thread *thread, uword return_address, const Code &caller_code, const Object &data, const Code &target)
static void PatchInstanceCallAtWithMutatorsStopped(Thread *thread, uword return_address, const Code &caller_code, const Object &data, const Code &target)
static void PatchSwitchableCallAt(uword return_address, const Code &caller_code, const Object &data, const Code &target)
static FunctionPtr GetUnoptimizedStaticCallAt(uword return_address, const Code &code, ICData *ic_data)
static uword GetSwitchableCallTargetEntryAt(uword return_address, const Code &caller_code)
static ObjectPtr GetSwitchableCallDataAt(uword return_address, const Code &caller_code)
static void InsertDeoptimizationCallAt(uword start)
static CodePtr GetInstanceCallAt(uword return_address, const Code &caller_code, Object *data)
static CodePtr GetNativeCallAt(uword return_address, const Code &caller_code, NativeFunction *target)
static void PatchStaticCallAt(uword return_address, const Code &code, const Code &new_target)
static void PatchNativeCallAt(uword return_address, const Code &caller_code, NativeFunction target, const Code &trampoline)
void RunWithStoppedMutators(T single_current_mutator, S otherwise, bool use_force_growth_in_otherwise=false)
Definition: isolate.h:611
static Object & Handle()
Definition: object.h:407
static ObjectPtr RawCast(ObjectPtr obj)
Definition: object.h:325
static Thread * Current()
Definition: thread.h:362
IsolateGroup * isolate_group() const
Definition: thread.h:541
#define ASSERT(E)
#define FATAL(error)
uint32_t * target
Definition: dart_vm.cc:33
uintptr_t uword
Definition: globals.h:501
bool MatchesPattern(uword end, const int16_t *pattern, intptr_t size)
Definition: code_patcher.cc:46
intptr_t IndexFromPPLoadDisp32(uword start)
static int8_t data[kExtLength]
void(* NativeFunction)(NativeArguments *arguments)
intptr_t IndexFromPPLoadDisp8(uword start)
def call(args)
Definition: dom.py:159
#define Px
Definition: globals.h:410
#define DISALLOW_IMPLICIT_CONSTRUCTORS(TypeName)
Definition: globals.h:593
#define ARRAY_SIZE(array)
Definition: globals.h:72