Commit e876dab9 authored by Jacob.Bramley@arm.com's avatar Jacob.Bramley@arm.com

ARM64: Fix and improve MacroAssembler::Printf.

  - W-sized values passed to Printf are now handled correctly by the
    simulator. In AAPCS64, int32_t and int64_t are passed in the same
    way, so this didn't affect non-simulator builds.
  - Since Printf now records the type and size of each argument, it is
    possible to mix argument types.
  - It is now possible to print the stack pointer. There is only one
    remaining restriction: The `csp` register cannot be printed unless
    it is the current stack pointer. This is because it is modified by
    BumpSystemStackPointer when the caller-saved registers are
    preserved.

BUG=
R=rmcilroy@chromium.org

Review URL: https://codereview.chromium.org/268353005

git-svn-id: https://v8.googlecode.com/svn/branches/bleeding_edge@21272 ce2b1a6d-e550-0410-aec6-3dcde31c8c00
parent a3a56420
......@@ -109,8 +109,13 @@ inline bool CPURegister::IsNone() const {
inline bool CPURegister::Is(const CPURegister& other) const {
ASSERT(IsValidOrNone() && other.IsValidOrNone());
return (reg_code == other.reg_code) && (reg_size == other.reg_size) &&
(reg_type == other.reg_type);
return Aliases(other) && (reg_size == other.reg_size);
}
inline bool CPURegister::Aliases(const CPURegister& other) const {
ASSERT(IsValidOrNone() && other.IsValidOrNone());
return (reg_code == other.reg_code) && (reg_type == other.reg_type);
}
......@@ -195,16 +200,22 @@ inline void CPURegList::Remove(int code) {
inline Register Register::XRegFromCode(unsigned code) {
// This function returns the zero register when code = 31. The stack pointer
// can not be returned.
ASSERT(code < kNumberOfRegisters);
return Register::Create(code, kXRegSizeInBits);
if (code == kSPRegInternalCode) {
return csp;
} else {
ASSERT(code < kNumberOfRegisters);
return Register::Create(code, kXRegSizeInBits);
}
}
inline Register Register::WRegFromCode(unsigned code) {
ASSERT(code < kNumberOfRegisters);
return Register::Create(code, kWRegSizeInBits);
if (code == kSPRegInternalCode) {
return wcsp;
} else {
ASSERT(code < kNumberOfRegisters);
return Register::Create(code, kWRegSizeInBits);
}
}
......
......@@ -66,6 +66,7 @@ struct CPURegister {
bool IsValidFPRegister() const;
bool IsNone() const;
bool Is(const CPURegister& other) const;
bool Aliases(const CPURegister& other) const;
bool IsZero() const;
bool IsSP() const;
......@@ -561,6 +562,11 @@ class CPURegList {
return size_in_bits / kBitsPerByte;
}
unsigned TotalSizeInBytes() const {
ASSERT(IsValid());
return RegisterSizeInBytes() * Count();
}
private:
RegList list_;
unsigned size_;
......
......@@ -416,24 +416,38 @@ const Instr kImmExceptionIsUnreachable = 0xdebf;
// A pseudo 'printf' instruction. The arguments will be passed to the platform
// printf method.
const Instr kImmExceptionIsPrintf = 0xdeb1;
// Parameters are stored in ARM64 registers as if the printf pseudo-instruction
// was a call to the real printf method:
//
// x0: The format string, then either of:
// Most parameters are stored in ARM64 registers as if the printf
// pseudo-instruction was a call to the real printf method:
// x0: The format string.
// x1-x7: Optional arguments.
// d0-d7: Optional arguments.
//
// Floating-point and integer arguments are passed in separate sets of
// registers in AAPCS64 (even for varargs functions), so it is not possible to
// determine the type of location of each arguments without some information
// about the values that were passed in. This information could be retrieved
// from the printf format string, but the format string is not trivial to
// parse so we encode the relevant information with the HLT instruction.
// - Type
// Either kRegister or kFPRegister, but stored as a uint32_t because there's
// no way to guarantee the size of the CPURegister::RegisterType enum.
const unsigned kPrintfTypeOffset = 1 * kInstructionSize;
const unsigned kPrintfLength = 2 * kInstructionSize;
// Also, the argument layout is described inline in the instructions:
// - arg_count: The number of arguments.
// - arg_pattern: A set of PrintfArgPattern values, packed into two-bit fields.
//
// Floating-point and integer arguments are passed in separate sets of registers
// in AAPCS64 (even for varargs functions), so it is not possible to determine
// the type of each argument without some information about the values that were
// passed in. This information could be retrieved from the printf format string,
// but the format string is not trivial to parse so we encode the relevant
// information with the HLT instruction.
const unsigned kPrintfArgCountOffset = 1 * kInstructionSize;
const unsigned kPrintfArgPatternListOffset = 2 * kInstructionSize;
const unsigned kPrintfLength = 3 * kInstructionSize;
const unsigned kPrintfMaxArgCount = 4;
// The argument pattern is a set of two-bit-fields, each with one of the
// following values:
enum PrintfArgPattern {
kPrintfArgW = 1,
kPrintfArgX = 2,
// There is no kPrintfArgS because floats are always converted to doubles in C
// varargs calls.
kPrintfArgD = 3
};
static const unsigned kPrintfArgPatternBits = 2;
// A pseudo 'debug' instruction.
const Instr kImmExceptionIsDebug = 0xdeb0;
......
This diff is collapsed.
......@@ -1930,12 +1930,13 @@ class MacroAssembler : public Assembler {
// (such as %e, %f or %g) are FPRegisters, and that arguments for integer
// placeholders are Registers.
//
// A maximum of four arguments may be given to any single Printf call. The
// arguments must be of the same type, but they do not need to have the same
// size.
// At the moment it is only possible to print the value of csp if it is the
// current stack pointer. Otherwise, the MacroAssembler will automatically
// update csp on every push (using BumpSystemStackPointer), so determining its
// value is difficult.
//
// The following registers cannot be printed:
// StackPointer(), csp.
// Format placeholders that refer to more than one argument, or to a specific
// argument, are not supported. This includes formats like "%1$d" or "%.*d".
//
// This function automatically preserves caller-saved registers so that
// calling code can use Printf at any point without having to worry about
......@@ -1943,15 +1944,11 @@ class MacroAssembler : public Assembler {
// a problem, preserve the important registers manually and then call
// PrintfNoPreserve. Callee-saved registers are not used by Printf, and are
// implicitly preserved.
//
// This function assumes (and asserts) that the current stack pointer is
// callee-saved, not caller-saved. This is most likely the case anyway, as a
// caller-saved stack pointer doesn't make a lot of sense.
void Printf(const char * format,
const CPURegister& arg0 = NoCPUReg,
const CPURegister& arg1 = NoCPUReg,
const CPURegister& arg2 = NoCPUReg,
const CPURegister& arg3 = NoCPUReg);
CPURegister arg0 = NoCPUReg,
CPURegister arg1 = NoCPUReg,
CPURegister arg2 = NoCPUReg,
CPURegister arg3 = NoCPUReg);
// Like Printf, but don't preserve any caller-saved registers, not even 'lr'.
//
......@@ -2046,8 +2043,10 @@ class MacroAssembler : public Assembler {
// arguments and stack (csp) must be prepared by the caller as for a normal
// AAPCS64 call to 'printf'.
//
// The 'type' argument specifies the type of the optional arguments.
void CallPrintf(CPURegister::RegisterType type = CPURegister::kNoRegister);
// The 'args' argument should point to an array of variable arguments in their
// proper PCS registers (and in calling order). The argument registers can
// have mixed types. The format string (x0) should not be included.
void CallPrintf(int arg_count = 0, const CPURegister * args = NULL);
// Helper for throwing exceptions. Compute a handler address and jump to
// it. See the implementation for register usage.
......
......@@ -3581,43 +3581,7 @@ void Simulator::VisitException(Instruction* instr) {
} else if (instr->ImmException() == kImmExceptionIsRedirectedCall) {
DoRuntimeCall(instr);
} else if (instr->ImmException() == kImmExceptionIsPrintf) {
// Read the argument encoded inline in the instruction stream.
uint32_t type;
memcpy(&type,
pc_->InstructionAtOffset(kPrintfTypeOffset),
sizeof(type));
const char* format = reg<const char*>(0);
// Pass all of the relevant PCS registers onto printf. It doesn't
// matter if we pass too many as the extra ones won't be read.
int result;
fputs(clr_printf, stream_);
if (type == CPURegister::kRegister) {
result = fprintf(stream_, format,
xreg(1), xreg(2), xreg(3), xreg(4),
xreg(5), xreg(6), xreg(7));
} else if (type == CPURegister::kFPRegister) {
result = fprintf(stream_, format,
dreg(0), dreg(1), dreg(2), dreg(3),
dreg(4), dreg(5), dreg(6), dreg(7));
} else {
ASSERT(type == CPURegister::kNoRegister);
result = fprintf(stream_, "%s", format);
}
fputs(clr_normal, stream_);
#ifdef DEBUG
CorruptAllCallerSavedCPURegisters();
#endif
set_xreg(0, result);
// The printf parameters are inlined in the code, so skip them.
set_pc(pc_->InstructionAtOffset(kPrintfLength));
// Set LR as if we'd just called a native printf function.
set_lr(pc());
DoPrintf(instr);
} else if (instr->ImmException() == kImmExceptionIsUnreachable) {
fprintf(stream_, "Hit UNREACHABLE marker at PC=%p.\n",
......@@ -3635,6 +3599,133 @@ void Simulator::VisitException(Instruction* instr) {
}
}
void Simulator::DoPrintf(Instruction* instr) {
ASSERT((instr->Mask(ExceptionMask) == HLT) &&
(instr->ImmException() == kImmExceptionIsPrintf));
// Read the arguments encoded inline in the instruction stream.
uint32_t arg_count;
uint32_t arg_pattern_list;
STATIC_ASSERT(sizeof(*instr) == 1);
memcpy(&arg_count,
instr + kPrintfArgCountOffset,
sizeof(arg_count));
memcpy(&arg_pattern_list,
instr + kPrintfArgPatternListOffset,
sizeof(arg_pattern_list));
ASSERT(arg_count <= kPrintfMaxArgCount);
ASSERT((arg_pattern_list >> (kPrintfArgPatternBits * arg_count)) == 0);
// We need to call the host printf function with a set of arguments defined by
// arg_pattern_list. Because we don't know the types and sizes of the
// arguments, this is very difficult to do in a robust and portable way. To
// work around the problem, we pick apart the format string, and print one
// format placeholder at a time.
// Allocate space for the format string. We take a copy, so we can modify it.
// Leave enough space for one extra character per expected argument (plus the
// '\0' termination).
const char * format_base = reg<const char *>(0);
ASSERT(format_base != NULL);
size_t length = strlen(format_base) + 1;
char * const format = new char[length + arg_count];
// A list of chunks, each with exactly one format placeholder.
const char * chunks[kPrintfMaxArgCount];
// Copy the format string and search for format placeholders.
uint32_t placeholder_count = 0;
char * format_scratch = format;
for (size_t i = 0; i < length; i++) {
if (format_base[i] != '%') {
*format_scratch++ = format_base[i];
} else {
if (format_base[i + 1] == '%') {
// Ignore explicit "%%" sequences.
*format_scratch++ = format_base[i];
if (placeholder_count == 0) {
// The first chunk is passed to printf using "%s", so we need to
// unescape "%%" sequences in this chunk. (Just skip the next '%'.)
i++;
} else {
// Otherwise, pass through "%%" unchanged.
*format_scratch++ = format_base[++i];
}
} else {
CHECK(placeholder_count < arg_count);
// Insert '\0' before placeholders, and store their locations.
*format_scratch++ = '\0';
chunks[placeholder_count++] = format_scratch;
*format_scratch++ = format_base[i];
}
}
}
ASSERT(format_scratch <= (format + length + arg_count));
CHECK(placeholder_count == arg_count);
// Finally, call printf with each chunk, passing the appropriate register
// argument. Normally, printf returns the number of bytes transmitted, so we
// can emulate a single printf call by adding the result from each chunk. If
// any call returns a negative (error) value, though, just return that value.
fprintf(stream_, "%s", clr_printf);
// Because '\0' is inserted before each placeholder, the first string in
// 'format' contains no format placeholders and should be printed literally.
int result = fprintf(stream_, "%s", format);
int pcs_r = 1; // Start at x1. x0 holds the format string.
int pcs_f = 0; // Start at d0.
if (result >= 0) {
for (uint32_t i = 0; i < placeholder_count; i++) {
int part_result = -1;
uint32_t arg_pattern = arg_pattern_list >> (i * kPrintfArgPatternBits);
arg_pattern &= (1 << kPrintfArgPatternBits) - 1;
switch (arg_pattern) {
case kPrintfArgW:
part_result = fprintf(stream_, chunks[i], wreg(pcs_r++));
break;
case kPrintfArgX:
part_result = fprintf(stream_, chunks[i], xreg(pcs_r++));
break;
case kPrintfArgD:
part_result = fprintf(stream_, chunks[i], dreg(pcs_f++));
break;
default: UNREACHABLE();
}
if (part_result < 0) {
// Handle error values.
result = part_result;
break;
}
result += part_result;
}
}
fprintf(stream_, "%s", clr_normal);
#ifdef DEBUG
CorruptAllCallerSavedCPURegisters();
#endif
// Printf returns its result in x0 (just like the C library's printf).
set_xreg(0, result);
// The printf parameters are inlined in the code, so skip them.
set_pc(instr->InstructionAtOffset(kPrintfLength));
// Set LR as if we'd just called a native printf function.
set_lr(pc());
delete[] format;
}
#endif // USE_SIMULATOR
} } // namespace v8::internal
......
......@@ -757,6 +757,9 @@ class Simulator : public DecoderVisitor {
void CorruptAllCallerSavedCPURegisters();
#endif
// Pseudo Printf instruction
void DoPrintf(Instruction* instr);
// Processor state ---------------------------------------
// Output stream.
......
......@@ -9780,7 +9780,7 @@ TEST(cpureglist_utils_empty) {
TEST(printf) {
INIT_V8();
SETUP();
SETUP_SIZE(BUF_SIZE * 2);
START();
char const * test_plain_string = "Printf with no arguments.\n";
......@@ -9821,41 +9821,49 @@ TEST(printf) {
__ Mov(x11, 40);
__ Mov(x12, 500);
// x8 and x9 are used by debug code in part of the macro assembler. However,
// Printf guarantees to preserve them (so we can use Printf in debug code),
// and we need to test that they are properly preserved. The above code
// shouldn't need to use them, but we initialize x8 and x9 last to be on the
// safe side. This test still assumes that none of the code from
// before->Dump() to the end of the test can clobber x8 or x9, so where
// possible we use the Assembler directly to be safe.
__ orr(x8, xzr, 0x8888888888888888);
__ orr(x9, xzr, 0x9999999999999999);
// Check that we don't clobber any registers, except those that we explicitly
// write results into.
// A single character.
__ Mov(w13, 'x');
// Check that we don't clobber any registers.
before.Dump(&masm);
__ Printf(test_plain_string); // NOLINT(runtime/printf)
__ Printf("x0: %" PRId64", x1: 0x%08" PRIx64 "\n", x0, x1);
__ Printf("x0: %" PRId64 ", x1: 0x%08" PRIx64 "\n", x0, x1);
__ Printf("w5: %" PRId32 ", x5: %" PRId64"\n", w5, x5);
__ Printf("d0: %f\n", d0);
__ Printf("Test %%s: %s\n", x2);
__ Printf("w3(uint32): %" PRIu32 "\nw4(int32): %" PRId32 "\n"
"x5(uint64): %" PRIu64 "\nx6(int64): %" PRId64 "\n",
w3, w4, x5, x6);
__ Printf("%%f: %f\n%%g: %g\n%%e: %e\n%%E: %E\n", s1, s2, d3, d4);
__ Printf("0x%08" PRIx32 ", 0x%016" PRIx64 "\n", x28, x28);
__ Printf("0x%" PRIx32 ", 0x%" PRIx64 "\n", w28, x28);
__ Printf("%g\n", d10);
__ Printf("%%%%%s%%%c%%\n", x2, w13);
// Print the stack pointer (csp).
ASSERT(csp.Is(__ StackPointer()));
__ Printf("StackPointer(csp): 0x%016" PRIx64 ", 0x%08" PRIx32 "\n",
__ StackPointer(), __ StackPointer().W());
// Test with a different stack pointer.
const Register old_stack_pointer = __ StackPointer();
__ mov(x29, old_stack_pointer);
__ Mov(x29, old_stack_pointer);
__ SetStackPointer(x29);
__ Printf("old_stack_pointer: 0x%016" PRIx64 "\n", old_stack_pointer);
__ mov(old_stack_pointer, __ StackPointer());
// Print the stack pointer (not csp).
__ Printf("StackPointer(not csp): 0x%016" PRIx64 ", 0x%08" PRIx32 "\n",
__ StackPointer(), __ StackPointer().W());
__ Mov(old_stack_pointer, __ StackPointer());
__ SetStackPointer(old_stack_pointer);
// Test with three arguments.
__ Printf("3=%u, 4=%u, 5=%u\n", x10, x11, x12);
// Mixed argument types.
__ Printf("w3: %" PRIu32 ", s1: %f, x5: %" PRIu64 ", d3: %f\n",
w3, s1, x5, d3);
__ Printf("s1: %f, d3: %f, w3: %" PRId32 ", x5: %" PRId64 "\n",
s1, d3, w3, x5);
END();
RUN();
......@@ -9877,7 +9885,7 @@ TEST(printf_no_preserve) {
char const * test_plain_string = "Printf with no arguments.\n";
char const * test_substring = "'This is a substring.'";
__ PrintfNoPreserve(test_plain_string); // NOLINT(runtime/printf)
__ PrintfNoPreserve(test_plain_string);
__ Mov(x19, x0);
// Test simple integer arguments.
......@@ -9915,7 +9923,7 @@ TEST(printf_no_preserve) {
// Test printing callee-saved registers.
__ Mov(x28, 0x123456789abcdef);
__ PrintfNoPreserve("0x%08" PRIx32 ", 0x%016" PRIx64 "\n", x28, x28);
__ PrintfNoPreserve("0x%" PRIx32 ", 0x%" PRIx64 "\n", w28, x28);
__ Mov(x25, x0);
__ Fmov(d10, 42.0);
......@@ -9926,11 +9934,11 @@ TEST(printf_no_preserve) {
const Register old_stack_pointer = __ StackPointer();
__ Mov(x29, old_stack_pointer);
__ SetStackPointer(x29);
__ PrintfNoPreserve("old_stack_pointer: 0x%016" PRIx64 "\n",
old_stack_pointer);
// Print the stack pointer (not csp).
__ PrintfNoPreserve(
"StackPointer(not csp): 0x%016" PRIx64 ", 0x%08" PRIx32 "\n",
__ StackPointer(), __ StackPointer().W());
__ Mov(x27, x0);
__ Mov(old_stack_pointer, __ StackPointer());
__ SetStackPointer(old_stack_pointer);
......@@ -9941,6 +9949,15 @@ TEST(printf_no_preserve) {
__ PrintfNoPreserve("3=%u, 4=%u, 5=%u\n", x3, x4, x5);
__ Mov(x28, x0);
// Mixed argument types.
__ Mov(w3, 0xffffffff);
__ Fmov(s1, 1.234);
__ Mov(x5, 0xffffffffffffffff);
__ Fmov(d3, 3.456);
__ PrintfNoPreserve("w3: %" PRIu32 ", s1: %f, x5: %" PRIu64 ", d3: %f\n",
w3, s1, x5, d3);
__ Mov(x29, x0);
END();
RUN();
......@@ -9965,16 +9982,18 @@ TEST(printf_no_preserve) {
// %e: 3.456000e+00
// %E: 4.567000E+00
ASSERT_EQUAL_64(13 + 10 + 17 + 17, x24);
// 0x89abcdef, 0x0123456789abcdef
ASSERT_EQUAL_64(31, x25);
// 0x89abcdef, 0x123456789abcdef
ASSERT_EQUAL_64(30, x25);
// 42
ASSERT_EQUAL_64(3, x26);
// old_stack_pointer: 0x00007fb037ae2370
// StackPointer(not csp): 0x00007fb037ae2370, 0x37ae2370
// Note: This is an example value, but the field width is fixed here so the
// string length is still predictable.
ASSERT_EQUAL_64(38, x27);
ASSERT_EQUAL_64(54, x27);
// 3=3, 4=40, 5=500
ASSERT_EQUAL_64(17, x28);
// w3: 4294967295, s1: 1.234000, x5: 18446744073709551615, d3: 3.456000
ASSERT_EQUAL_64(69, x29);
TEARDOWN();
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment