• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2021 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 #ifndef V8_BASELINE_MIPS_BASELINE_ASSEMBLER_MIPS_INL_H_
6 #define V8_BASELINE_MIPS_BASELINE_ASSEMBLER_MIPS_INL_H_
7 
8 #include "src/baseline/baseline-assembler.h"
9 #include "src/codegen/interface-descriptors.h"
10 #include "src/codegen/mips/assembler-mips-inl.h"
11 
12 namespace v8 {
13 namespace internal {
14 namespace baseline {
15 
16 class BaselineAssembler::ScratchRegisterScope {
17  public:
ScratchRegisterScope(BaselineAssembler * assembler)18   explicit ScratchRegisterScope(BaselineAssembler* assembler)
19       : assembler_(assembler),
20         prev_scope_(assembler->scratch_register_scope_),
21         wrapped_scope_(assembler->masm()) {
22     if (!assembler_->scratch_register_scope_) {
23       // If we haven't opened a scratch scope yet, for the first one add a
24       // couple of extra registers.
25       wrapped_scope_.Include({t4, t5, t6, t7});
26     }
27     assembler_->scratch_register_scope_ = this;
28   }
~ScratchRegisterScope()29   ~ScratchRegisterScope() { assembler_->scratch_register_scope_ = prev_scope_; }
30 
AcquireScratch()31   Register AcquireScratch() { return wrapped_scope_.Acquire(); }
32 
33  private:
34   BaselineAssembler* assembler_;
35   ScratchRegisterScope* prev_scope_;
36   UseScratchRegisterScope wrapped_scope_;
37 };
38 
39 enum class Condition : uint32_t {
40   kEqual = eq,
41   kNotEqual = ne,
42 
43   kLessThan = lt,
44   kGreaterThan = gt,
45   kLessThanEqual = le,
46   kGreaterThanEqual = ge,
47 
48   kUnsignedLessThan = Uless,
49   kUnsignedGreaterThan = Ugreater,
50   kUnsignedLessThanEqual = Uless_equal,
51   kUnsignedGreaterThanEqual = Ugreater_equal,
52 
53   kOverflow = overflow,
54   kNoOverflow = no_overflow,
55 
56   kZero = eq,
57   kNotZero = ne,
58 };
59 
AsMasmCondition(Condition cond)60 inline internal::Condition AsMasmCondition(Condition cond) {
61   // This is important for arm, where the internal::Condition where each value
62   // represents an encoded bit field value.
63   STATIC_ASSERT(sizeof(internal::Condition) == sizeof(Condition));
64   return static_cast<internal::Condition>(cond);
65 }
66 
67 namespace detail {
68 
69 #ifdef DEBUG
Clobbers(Register target,MemOperand op)70 inline bool Clobbers(Register target, MemOperand op) {
71   return op.is_reg() && op.rm() == target;
72 }
73 #endif
74 
75 }  // namespace detail
76 
77 #define __ masm_->
78 
RegisterFrameOperand(interpreter::Register interpreter_register)79 MemOperand BaselineAssembler::RegisterFrameOperand(
80     interpreter::Register interpreter_register) {
81   return MemOperand(fp, interpreter_register.ToOperand() * kSystemPointerSize);
82 }
RegisterFrameAddress(interpreter::Register interpreter_register,Register rscratch)83 void BaselineAssembler::RegisterFrameAddress(
84     interpreter::Register interpreter_register, Register rscratch) {
85   return __ Addu(rscratch, fp,
86                  interpreter_register.ToOperand() * kSystemPointerSize);
87 }
FeedbackVectorOperand()88 MemOperand BaselineAssembler::FeedbackVectorOperand() {
89   return MemOperand(fp, BaselineFrameConstants::kFeedbackVectorFromFp);
90 }
91 
Bind(Label * label)92 void BaselineAssembler::Bind(Label* label) { __ bind(label); }
93 
BindWithoutJumpTarget(Label * label)94 void BaselineAssembler::BindWithoutJumpTarget(Label* label) { __ bind(label); }
95 
JumpTarget()96 void BaselineAssembler::JumpTarget() {
97   // NOP.
98 }
Jump(Label * target,Label::Distance distance)99 void BaselineAssembler::Jump(Label* target, Label::Distance distance) {
100   __ Branch(target);
101 }
JumpIfRoot(Register value,RootIndex index,Label * target,Label::Distance)102 void BaselineAssembler::JumpIfRoot(Register value, RootIndex index,
103                                    Label* target, Label::Distance) {
104   __ JumpIfRoot(value, index, target);
105 }
JumpIfNotRoot(Register value,RootIndex index,Label * target,Label::Distance)106 void BaselineAssembler::JumpIfNotRoot(Register value, RootIndex index,
107                                       Label* target, Label::Distance) {
108   __ JumpIfNotRoot(value, index, target);
109 }
JumpIfSmi(Register value,Label * target,Label::Distance)110 void BaselineAssembler::JumpIfSmi(Register value, Label* target,
111                                   Label::Distance) {
112   __ JumpIfSmi(value, target);
113 }
JumpIfNotSmi(Register value,Label * target,Label::Distance)114 void BaselineAssembler::JumpIfNotSmi(Register value, Label* target,
115                                      Label::Distance) {
116   __ JumpIfNotSmi(value, target);
117 }
JumpIfImmediate(Condition cc,Register left,int right,Label * target,Label::Distance distance)118 void BaselineAssembler::JumpIfImmediate(Condition cc, Register left, int right,
119                                         Label* target,
120                                         Label::Distance distance) {
121   JumpIf(cc, left, Operand(right), target, distance);
122 }
123 
CallBuiltin(Builtin builtin)124 void BaselineAssembler::CallBuiltin(Builtin builtin) {
125   ASM_CODE_COMMENT_STRING(masm_,
126                           __ CommentForOffHeapTrampoline("call", builtin));
127   Register temp = t9;
128   __ LoadEntryFromBuiltin(builtin, temp);
129   __ Call(temp);
130 }
131 
TailCallBuiltin(Builtin builtin)132 void BaselineAssembler::TailCallBuiltin(Builtin builtin) {
133   ASM_CODE_COMMENT_STRING(masm_,
134                           __ CommentForOffHeapTrampoline("tail call", builtin));
135   Register temp = t9;
136   __ LoadEntryFromBuiltin(builtin, temp);
137   __ Jump(temp);
138 }
139 
TestAndBranch(Register value,int mask,Condition cc,Label * target,Label::Distance)140 void BaselineAssembler::TestAndBranch(Register value, int mask, Condition cc,
141                                       Label* target, Label::Distance) {
142   ScratchRegisterScope temps(this);
143   Register scratch = temps.AcquireScratch();
144   __ And(scratch, value, Operand(mask));
145   __ Branch(target, AsMasmCondition(cc), scratch, Operand(zero_reg));
146 }
147 
JumpIf(Condition cc,Register lhs,const Operand & rhs,Label * target,Label::Distance)148 void BaselineAssembler::JumpIf(Condition cc, Register lhs, const Operand& rhs,
149                                Label* target, Label::Distance) {
150   __ Branch(target, AsMasmCondition(cc), lhs, Operand(rhs));
151 }
JumpIfObjectType(Condition cc,Register object,InstanceType instance_type,Register map,Label * target,Label::Distance)152 void BaselineAssembler::JumpIfObjectType(Condition cc, Register object,
153                                          InstanceType instance_type,
154                                          Register map, Label* target,
155                                          Label::Distance) {
156   ScratchRegisterScope temps(this);
157   Register type = temps.AcquireScratch();
158   __ GetObjectType(object, map, type);
159   __ Branch(target, AsMasmCondition(cc), type, Operand(instance_type));
160 }
JumpIfInstanceType(Condition cc,Register map,InstanceType instance_type,Label * target,Label::Distance)161 void BaselineAssembler::JumpIfInstanceType(Condition cc, Register map,
162                                            InstanceType instance_type,
163                                            Label* target, Label::Distance) {
164   ScratchRegisterScope temps(this);
165   Register type = temps.AcquireScratch();
166   if (FLAG_debug_code) {
167     __ AssertNotSmi(map);
168     __ GetObjectType(map, type, type);
169     __ Assert(eq, AbortReason::kUnexpectedValue, type, Operand(MAP_TYPE));
170   }
171   __ Lw(type, FieldMemOperand(map, Map::kInstanceTypeOffset));
172   __ Branch(target, AsMasmCondition(cc), type, Operand(instance_type));
173 }
JumpIfPointer(Condition cc,Register value,MemOperand operand,Label * target,Label::Distance)174 void BaselineAssembler::JumpIfPointer(Condition cc, Register value,
175                                       MemOperand operand, Label* target,
176                                       Label::Distance) {
177   ScratchRegisterScope temps(this);
178   Register scratch = temps.AcquireScratch();
179   __ Lw(scratch, operand);
180   __ Branch(target, AsMasmCondition(cc), value, Operand(scratch));
181 }
JumpIfSmi(Condition cc,Register value,Smi smi,Label * target,Label::Distance)182 void BaselineAssembler::JumpIfSmi(Condition cc, Register value, Smi smi,
183                                   Label* target, Label::Distance) {
184   ScratchRegisterScope temps(this);
185   Register scratch = temps.AcquireScratch();
186   __ li(scratch, Operand(smi));
187   __ SmiUntag(scratch);
188   __ Branch(target, AsMasmCondition(cc), value, Operand(scratch));
189 }
JumpIfSmi(Condition cc,Register lhs,Register rhs,Label * target,Label::Distance)190 void BaselineAssembler::JumpIfSmi(Condition cc, Register lhs, Register rhs,
191                                   Label* target, Label::Distance) {
192   __ AssertSmi(lhs);
193   __ AssertSmi(rhs);
194   __ Branch(target, AsMasmCondition(cc), lhs, Operand(rhs));
195 }
JumpIfTagged(Condition cc,Register value,MemOperand operand,Label * target,Label::Distance)196 void BaselineAssembler::JumpIfTagged(Condition cc, Register value,
197                                      MemOperand operand, Label* target,
198                                      Label::Distance) {
199   ScratchRegisterScope temps(this);
200   Register scratch = temps.AcquireScratch();
201   __ Lw(scratch, operand);
202   __ Branch(target, AsMasmCondition(cc), value, Operand(scratch));
203 }
JumpIfTagged(Condition cc,MemOperand operand,Register value,Label * target,Label::Distance)204 void BaselineAssembler::JumpIfTagged(Condition cc, MemOperand operand,
205                                      Register value, Label* target,
206                                      Label::Distance) {
207   ScratchRegisterScope temps(this);
208   Register scratch = temps.AcquireScratch();
209   __ Lw(scratch, operand);
210   __ Branch(target, AsMasmCondition(cc), scratch, Operand(value));
211 }
JumpIfByte(Condition cc,Register value,int32_t byte,Label * target,Label::Distance)212 void BaselineAssembler::JumpIfByte(Condition cc, Register value, int32_t byte,
213                                    Label* target, Label::Distance) {
214   __ Branch(target, AsMasmCondition(cc), value, Operand(byte));
215 }
216 
Move(interpreter::Register output,Register source)217 void BaselineAssembler::Move(interpreter::Register output, Register source) {
218   Move(RegisterFrameOperand(output), source);
219 }
Move(Register output,TaggedIndex value)220 void BaselineAssembler::Move(Register output, TaggedIndex value) {
221   __ li(output, Operand(value.ptr()));
222 }
Move(MemOperand output,Register source)223 void BaselineAssembler::Move(MemOperand output, Register source) {
224   __ Sw(source, output);
225 }
Move(Register output,ExternalReference reference)226 void BaselineAssembler::Move(Register output, ExternalReference reference) {
227   __ li(output, Operand(reference));
228 }
Move(Register output,Handle<HeapObject> value)229 void BaselineAssembler::Move(Register output, Handle<HeapObject> value) {
230   __ li(output, Operand(value));
231 }
Move(Register output,int32_t value)232 void BaselineAssembler::Move(Register output, int32_t value) {
233   __ li(output, Operand(value));
234 }
MoveMaybeSmi(Register output,Register source)235 void BaselineAssembler::MoveMaybeSmi(Register output, Register source) {
236   __ Move(output, source);
237 }
MoveSmi(Register output,Register source)238 void BaselineAssembler::MoveSmi(Register output, Register source) {
239   __ Move(output, source);
240 }
241 
242 namespace detail {
243 
244 template <typename Arg>
ToRegister(BaselineAssembler * basm,BaselineAssembler::ScratchRegisterScope * scope,Arg arg)245 inline Register ToRegister(BaselineAssembler* basm,
246                            BaselineAssembler::ScratchRegisterScope* scope,
247                            Arg arg) {
248   Register reg = scope->AcquireScratch();
249   basm->Move(reg, arg);
250   return reg;
251 }
ToRegister(BaselineAssembler * basm,BaselineAssembler::ScratchRegisterScope * scope,Register reg)252 inline Register ToRegister(BaselineAssembler* basm,
253                            BaselineAssembler::ScratchRegisterScope* scope,
254                            Register reg) {
255   return reg;
256 }
257 
258 template <typename... Args>
259 struct PushAllHelper;
260 template <>
261 struct PushAllHelper<> {
262   static int Push(BaselineAssembler* basm) { return 0; }
263   static int PushReverse(BaselineAssembler* basm) { return 0; }
264 };
265 // TODO(ishell): try to pack sequence of pushes into one instruction by
266 // looking at regiser codes. For example, Push(r1, r2, r5, r0, r3, r4)
267 // could be generated as two pushes: Push(r1, r2, r5) and Push(r0, r3, r4).
268 template <typename Arg>
269 struct PushAllHelper<Arg> {
270   static int Push(BaselineAssembler* basm, Arg arg) {
271     BaselineAssembler::ScratchRegisterScope scope(basm);
272     basm->masm()->Push(ToRegister(basm, &scope, arg));
273     return 1;
274   }
275   static int PushReverse(BaselineAssembler* basm, Arg arg) {
276     return Push(basm, arg);
277   }
278 };
279 // TODO(ishell): try to pack sequence of pushes into one instruction by
280 // looking at regiser codes. For example, Push(r1, r2, r5, r0, r3, r4)
281 // could be generated as two pushes: Push(r1, r2, r5) and Push(r0, r3, r4).
282 template <typename Arg, typename... Args>
283 struct PushAllHelper<Arg, Args...> {
284   static int Push(BaselineAssembler* basm, Arg arg, Args... args) {
285     PushAllHelper<Arg>::Push(basm, arg);
286     return 1 + PushAllHelper<Args...>::Push(basm, args...);
287   }
288   static int PushReverse(BaselineAssembler* basm, Arg arg, Args... args) {
289     int nargs = PushAllHelper<Args...>::PushReverse(basm, args...);
290     PushAllHelper<Arg>::Push(basm, arg);
291     return nargs + 1;
292   }
293 };
294 template <>
295 struct PushAllHelper<interpreter::RegisterList> {
296   static int Push(BaselineAssembler* basm, interpreter::RegisterList list) {
297     for (int reg_index = 0; reg_index < list.register_count(); ++reg_index) {
298       PushAllHelper<interpreter::Register>::Push(basm, list[reg_index]);
299     }
300     return list.register_count();
301   }
302   static int PushReverse(BaselineAssembler* basm,
303                          interpreter::RegisterList list) {
304     for (int reg_index = list.register_count() - 1; reg_index >= 0;
305          --reg_index) {
306       PushAllHelper<interpreter::Register>::Push(basm, list[reg_index]);
307     }
308     return list.register_count();
309   }
310 };
311 
312 template <typename... T>
313 struct PopAllHelper;
314 template <>
315 struct PopAllHelper<> {
316   static void Pop(BaselineAssembler* basm) {}
317 };
318 // TODO(ishell): try to pack sequence of pops into one instruction by
319 // looking at regiser codes. For example, Pop(r1, r2, r5, r0, r3, r4)
320 // could be generated as two pops: Pop(r1, r2, r5) and Pop(r0, r3, r4).
321 template <>
322 struct PopAllHelper<Register> {
323   static void Pop(BaselineAssembler* basm, Register reg) {
324     basm->masm()->Pop(reg);
325   }
326 };
327 template <typename... T>
328 struct PopAllHelper<Register, T...> {
329   static void Pop(BaselineAssembler* basm, Register reg, T... tail) {
330     PopAllHelper<Register>::Pop(basm, reg);
331     PopAllHelper<T...>::Pop(basm, tail...);
332   }
333 };
334 
335 }  // namespace detail
336 
337 template <typename... T>
338 int BaselineAssembler::Push(T... vals) {
339   return detail::PushAllHelper<T...>::Push(this, vals...);
340 }
341 
342 template <typename... T>
343 void BaselineAssembler::PushReverse(T... vals) {
344   detail::PushAllHelper<T...>::PushReverse(this, vals...);
345 }
346 
347 template <typename... T>
348 void BaselineAssembler::Pop(T... registers) {
349   detail::PopAllHelper<T...>::Pop(this, registers...);
350 }
351 
352 void BaselineAssembler::LoadTaggedPointerField(Register output, Register source,
353                                                int offset) {
354   __ Lw(output, FieldMemOperand(source, offset));
355 }
356 void BaselineAssembler::LoadTaggedSignedField(Register output, Register source,
357                                               int offset) {
358   __ Lw(output, FieldMemOperand(source, offset));
359 }
360 void BaselineAssembler::LoadTaggedAnyField(Register output, Register source,
361                                            int offset) {
362   __ Lw(output, FieldMemOperand(source, offset));
363 }
364 void BaselineAssembler::LoadWord16FieldZeroExtend(Register output,
365                                                   Register source, int offset) {
366   __ lhu(output, FieldMemOperand(source, offset));
367 }
368 void BaselineAssembler::LoadWord8Field(Register output, Register source,
369                                        int offset) {
370   __ lb(output, FieldMemOperand(source, offset));
371 }
372 void BaselineAssembler::StoreTaggedSignedField(Register target, int offset,
373                                                Smi value) {
374   ASM_CODE_COMMENT(masm_);
375   ScratchRegisterScope temps(this);
376   Register scratch = temps.AcquireScratch();
377   __ li(scratch, Operand(value));
378   __ Sw(scratch, FieldMemOperand(target, offset));
379 }
380 void BaselineAssembler::StoreTaggedFieldWithWriteBarrier(Register target,
381                                                          int offset,
382                                                          Register value) {
383   ASM_CODE_COMMENT(masm_);
384   __ Sw(value, FieldMemOperand(target, offset));
385   ScratchRegisterScope temps(this);
386   Register scratch = temps.AcquireScratch();
387   __ RecordWriteField(target, offset, value, scratch, kRAHasNotBeenSaved,
388                       SaveFPRegsMode::kIgnore);
389 }
390 void BaselineAssembler::StoreTaggedFieldNoWriteBarrier(Register target,
391                                                        int offset,
392                                                        Register value) {
393   __ Sw(value, FieldMemOperand(target, offset));
394 }
395 
396 void BaselineAssembler::AddToInterruptBudgetAndJumpIfNotExceeded(
397     int32_t weight, Label* skip_interrupt_label) {
398   ASM_CODE_COMMENT(masm_);
399   ScratchRegisterScope scratch_scope(this);
400   Register feedback_cell = scratch_scope.AcquireScratch();
401   LoadFunction(feedback_cell);
402   LoadTaggedPointerField(feedback_cell, feedback_cell,
403                          JSFunction::kFeedbackCellOffset);
404 
405   Register interrupt_budget = scratch_scope.AcquireScratch();
406   __ Lw(interrupt_budget,
407         FieldMemOperand(feedback_cell, FeedbackCell::kInterruptBudgetOffset));
408   __ Addu(interrupt_budget, interrupt_budget, weight);
409   __ Sw(interrupt_budget,
410         FieldMemOperand(feedback_cell, FeedbackCell::kInterruptBudgetOffset));
411   if (skip_interrupt_label) {
412     DCHECK_LT(weight, 0);
413     __ Branch(skip_interrupt_label, ge, interrupt_budget, Operand(zero_reg));
414   }
415 }
416 void BaselineAssembler::AddToInterruptBudgetAndJumpIfNotExceeded(
417     Register weight, Label* skip_interrupt_label) {
418   ASM_CODE_COMMENT(masm_);
419   ScratchRegisterScope scratch_scope(this);
420   Register feedback_cell = scratch_scope.AcquireScratch();
421   LoadFunction(feedback_cell);
422   LoadTaggedPointerField(feedback_cell, feedback_cell,
423                          JSFunction::kFeedbackCellOffset);
424 
425   Register interrupt_budget = scratch_scope.AcquireScratch();
426   __ Lw(interrupt_budget,
427         FieldMemOperand(feedback_cell, FeedbackCell::kInterruptBudgetOffset));
428   __ Addu(interrupt_budget, interrupt_budget, weight);
429   __ Sw(interrupt_budget,
430         FieldMemOperand(feedback_cell, FeedbackCell::kInterruptBudgetOffset));
431   if (skip_interrupt_label)
432     __ Branch(skip_interrupt_label, ge, interrupt_budget, Operand(zero_reg));
433 }
434 
435 void BaselineAssembler::AddSmi(Register lhs, Smi rhs) {
436   __ Addu(lhs, lhs, Operand(rhs));
437 }
438 
439 void BaselineAssembler::Word32And(Register output, Register lhs, int rhs) {
440   __ And(output, lhs, Operand(rhs));
441 }
442 
443 void BaselineAssembler::Switch(Register reg, int case_value_base,
444                                Label** labels, int num_labels) {
445   ASM_CODE_COMMENT(masm_);
446   Label fallthrough;
447   if (case_value_base != 0) {
448     __ Subu(reg, reg, Operand(case_value_base));
449   }
450 
451   __ Branch(&fallthrough, AsMasmCondition(Condition::kUnsignedGreaterThanEqual),
452             reg, Operand(num_labels));
453 
454   __ GenerateSwitchTable(reg, num_labels,
455                          [labels](size_t i) { return labels[i]; });
456 
457   __ bind(&fallthrough);
458 }
459 
460 #undef __
461 
462 #define __ basm.
463 
464 void BaselineAssembler::EmitReturn(MacroAssembler* masm) {
465   ASM_CODE_COMMENT(masm);
466   BaselineAssembler basm(masm);
467 
468   Register weight = BaselineLeaveFrameDescriptor::WeightRegister();
469   Register params_size = BaselineLeaveFrameDescriptor::ParamsSizeRegister();
470 
471   {
472     ASM_CODE_COMMENT_STRING(masm, "Update Interrupt Budget");
473 
474     Label skip_interrupt_label;
475     __ AddToInterruptBudgetAndJumpIfNotExceeded(weight, &skip_interrupt_label);
476     __ masm()->SmiTag(params_size);
477     __ masm()->Push(params_size, kInterpreterAccumulatorRegister);
478 
479     __ LoadContext(kContextRegister);
480     __ LoadFunction(kJSFunctionRegister);
481     __ masm()->Push(kJSFunctionRegister);
482     __ CallRuntime(Runtime::kBytecodeBudgetInterrupt, 1);
483 
484     __ masm()->Pop(params_size, kInterpreterAccumulatorRegister);
485     __ masm()->SmiUntag(params_size);
486 
487   __ Bind(&skip_interrupt_label);
488   }
489 
490   BaselineAssembler::ScratchRegisterScope temps(&basm);
491   Register actual_params_size = temps.AcquireScratch();
492   // Compute the size of the actual parameters + receiver (in bytes).
493   __ Move(actual_params_size,
494           MemOperand(fp, StandardFrameConstants::kArgCOffset));
495 
496   // If actual is bigger than formal, then we should use it to free up the stack
497   // arguments.
498   Label corrected_args_count;
499   __ masm()->Branch(&corrected_args_count, ge, params_size,
500                     Operand(actual_params_size));
501   __ masm()->Move(params_size, actual_params_size);
502   __ Bind(&corrected_args_count);
503 
504   // Leave the frame (also dropping the register file).
505   __ masm()->LeaveFrame(StackFrame::BASELINE);
506 
507   // Drop receiver + arguments.
508   __ masm()->DropArguments(params_size, TurboAssembler::kCountIsInteger,
509                            TurboAssembler::kCountIncludesReceiver);
510 
511   __ masm()->Ret();
512 }
513 
514 #undef __
515 
516 inline void EnsureAccumulatorPreservedScope::AssertEqualToAccumulator(
517     Register reg) {
518   assembler_->masm()->Assert(eq, AbortReason::kUnexpectedValue, reg,
519                              Operand(kInterpreterAccumulatorRegister));
520 }
521 
522 }  // namespace baseline
523 }  // namespace internal
524 }  // namespace v8
525 
526 #endif  // V8_BASELINE_MIPS_BASELINE_ASSEMBLER_MIPS_INL_H_
527