blob: 032c6ab0c80c93915c22133f24d2040948723191 [file] [log] [blame]
// Copyright 2021 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "src/developer/debug/zxdb/expr/abi_x64.h"
#include <lib/syslog/cpp/macros.h>
#include <algorithm>
#include "src/developer/debug/zxdb/expr/builtin_types.h"
#include "src/developer/debug/zxdb/symbols/base_type.h"
#include "src/developer/debug/zxdb/symbols/collection.h"
#include "src/developer/debug/zxdb/symbols/visit_scopes.h"
namespace zxdb {
namespace {
using debug::RegisterID;
// Notes on returning collections by value
// ---------------------------------------
//
// The x64 ABI Fuchsia uses:
// https://software.intel.com/content/www/us/en/develop/articles/linux-abi.html
//
// The ABI rules for passing and returning collections by value are quite complicated (the LLVM code
// is about 1000 lines). This code doesn't attempt to implement the whole thing, but tries to get
// the common cases and give up for anything complex. Fortunately, this will support the vast
// majority of practical uses.
//
// Fortunately, many of the rules for passing collections don't apply because we know already that
// the collection is passed by value (DWARF tells us). This means that we don't have to worry about
// unaligned values and C++ rules about non-trivial copy constructors. Additionally, we don't
// support x87 floating-point, the C "complex" type, and vectors. This leaves only the POINTER,
// INTEGER, and SSE classes.
//
// POINTER and INTEGER classes are returned in the general-purpose registers, so the only thing we
// have to worry about is finding which things are SSE class. All pass-by-value collections are less
// than 16 bytes except those consisting of SSE vectors which we don't support, so we can also
// assume <= 16 bytes.
//
// The ABI wants to return things in 8-byte chunks. If a chunk is all floating-point, it's returned
// in xmm0, xmm1. If it's integer, pointer, or a combination (possibly including floating-point),
// it's returned in rax, rdx.
//
// Some examples for structure returning:
//
// - {double, int64} -> {xmm0 (8 bytes), rax}
// - {float, int64} -> {xmm0 (4 bytes), rax}
// - {float, float} -> {xmm0[0] (low 4 bytes), xmm0[1] (next higher 4 bytes)}
// - {float, float, int64} -> {xmm0[0], xmm0[1], rax}
// - {float, char} -> {rax (low 4 bytes), rax (5th byte)} (!)
// - {float, char, float} -> {rax (low 4 bytes), rax (5th byte), xmm0[0] (4 bytes)} (!)
//
// For the examples marked with (!) you can see floating-point values getting passed in integer
// registers. This is because the first two values fit into one eightbyte. When comparing the first
// two values according to the ABI parameter classification rule "If one of the classes is INTEGER,
// the result is INTEGER.", the float/char combination is assigned class INTEGER and therefore rax.
//
// For the {float, char, float} case, the second float falls into a different eightbyte, and
// according to the API parameter classification rule "If the size of the aggregate exceeds a single
// eightbyte, each is classified separately." Therefore, the 2nd float and the earlier parameters
// are never compared and keep their separate classes.
// Register classes from the ABI. We do not support SSEUP, X87, X87UP, COMPLEX_X87. The MEMORY
// class isn't handled here because that means it's not passed in registers and we already know
// the answer from the calling convention in DWARF.
enum class RegisterClass {
kEmpty, // Not allocated (NO_CLASS).
kGeneral, // Used for both the POINTER and INTEGER types from the ABI.
kSse // The SSE type (this counts only the low 4 or 8 bytes, not a vector).
};
struct DataMemberInfo {
// Offset from the beginning of the collection of this member. There can be multiple members
// at the same offset in the case of unions.
uint32_t byte_offset;
// This type will be concrete.
fxl::RefPtr<Type> type;
};
// Makes a list of all data members and their locations inside of a collection for the purposes of
// allocating registers for returning it by value.
//
// Returns true on success or false if there are some member types that we don't support for
// computing by-value returns.
bool GetDataMembersForByValueReturning(const fxl::RefPtr<EvalContext>& context,
const Collection* collection,
std::vector<DataMemberInfo>& result) {
VisitResult visit_result = VisitDataMembers(
collection,
[context, &result](bool is_leaf, uint32_t net_byte_offset,
const DataMember* member) -> VisitResult {
if (!is_leaf)
return VisitResult::kContinue; // Intermediate collections, we'll catch members later.
if (member->is_external())
return VisitResult::kContinue; // Static member, doesn't count toward returning.
// Don't support bitfields.
if (member->is_bitfield())
return VisitResult::kAbort;
// Decode the member type.
auto type = context->GetConcreteType(member->type());
if (!type)
return VisitResult::kAbort;
// Save the mapping.
result.push_back({net_byte_offset, std::move(type)});
return VisitResult::kContinue;
});
return visit_result != VisitResult::kAbort;
}
// Performs the merge step from the ABI document to get the allocations for a structure. Returns
// true on success, false means there was an unsupported feature or error.
//
// As per the above algorithm, we should have at most 2 "eightbyte" values. Check each one to
// see if there is anything of type "SSE" (floating point values). Everything else we can assume
// is either a pointer or integer type that goes into a general-purpose register.
bool MergeDataMembersForByValueReturning(const std::vector<DataMemberInfo>& members,
std::vector<RegisterClass>& classes) {
// The current algorithm assumes a maximum of two "eightbytes".
classes.resize(2);
classes[0] = RegisterClass::kEmpty;
classes[1] = RegisterClass::kEmpty;
// The ABI algorithm requires everything be aligned to be passed by value (which we know it is).
// This means that as long as all values are < 8 bytes and not bitfields, nothing will cross this
// boundary.
for (const auto& member : members) {
// Figure out which eightbyte value this member belongs in.
size_t class_index;
if (member.byte_offset < 8) {
class_index = 0;
} else if (member.byte_offset < 16) {
class_index = 1;
} else {
return false; // Value beyond type size.
}
// Simplifying assumption: don't support members greater than 8 bytes. This eliminates "long
// double" which is "x87" class, uint128, SSE vectors, and arrays.
if (member.type->byte_size() > 8)
return false;
if (const BaseType* base_type = member.type->As<BaseType>()) {
if (base_type->base_type() == BaseType::kBaseTypeFloat) {
// Class SSE, but don't overwrite a "general" class (this can happen if there's a uint32
// followed by a 32-bit float). The integer takes precedence.
if (classes[class_index] != RegisterClass::kGeneral)
classes[class_index] = RegisterClass::kSse;
} else {
// All other base types are "general" (pointers or integers). This also overwrites SSE if
// there is a 32-bit float followed by a int32.
classes[class_index] = RegisterClass::kGeneral;
}
} else if (DwarfTagIsPointerOrReference(member.type->tag())) {
// Pointers or reference types. "General" takes precedence over SSE so overwrite.
classes[class_index] = RegisterClass::kGeneral;
} else {
// Any other member types we don't support, give up.
return false;
}
}
return true;
}
// Given a sequence of register classes, allocates it to the registers that would be used. This
// step can not fail.
Abi::CollectionByValueReturn AllocateRegistersForByValueReturning(
uint32_t dest_byte_size, const std::vector<RegisterClass>& classes) {
// Currently expect exactly 2 entries in the list generated by Merge above.
constexpr size_t kMaxRegs = 2;
FX_DCHECK(classes.size() <= kMaxRegs);
// These are the registers to use for each class.
static const debug::RegisterID kGeneralRegs[kMaxRegs] = {debug::RegisterID::kX64_rax,
debug::RegisterID::kX64_rdx};
static const debug::RegisterID kSseRegs[kMaxRegs] = {debug::RegisterID::kX64_xmm0,
debug::RegisterID::kX64_xmm1};
// The next register of each category to use.
int next_general = 0;
int next_sse = 0;
// The number of bytes in the collection left to allocate to registers.
uint32_t remaining_bytes = dest_byte_size;
Abi::CollectionByValueReturn result;
for (size_t i = 0; i < classes.size(); i++) {
switch (classes[i]) {
case RegisterClass::kGeneral:
result.regs.push_back(
{.reg = kGeneralRegs[next_general], .bytes = std::min(remaining_bytes, 8u)});
remaining_bytes -= 8u;
next_general++;
break;
case RegisterClass::kSse:
result.regs.push_back({.reg = kSseRegs[next_sse], .bytes = std::min(remaining_bytes, 8u)});
remaining_bytes -= 8u;
next_sse++;
break;
case RegisterClass::kEmpty:
break;
}
}
return result;
}
} // namespace
bool AbiX64::IsRegisterCalleeSaved(debug::RegisterID reg) const {
switch (reg) {
case debug::RegisterID::kX64_rbx:
case debug::RegisterID::kX64_bh: // All variants of "rbx".
case debug::RegisterID::kX64_bl:
case debug::RegisterID::kX64_bx:
case debug::RegisterID::kX64_ebx:
case debug::RegisterID::kX64_rsp:
case debug::RegisterID::kX64_rbp:
case debug::RegisterID::kX64_r12:
case debug::RegisterID::kX64_r13:
case debug::RegisterID::kX64_r14:
case debug::RegisterID::kX64_r15:
case debug::RegisterID::kX64_rip:
return true;
default:
return false;
}
}
std::optional<debug::RegisterID> AbiX64::GetReturnRegisterForBaseType(const BaseType* base_type) {
switch (base_type->base_type()) {
case BaseType::kBaseTypeFloat:
if (base_type->byte_size() <= 8)
return RegisterID::kX64_xmm0;
// Don't support larger floating-point numbers.
return std::nullopt;
case BaseType::kBaseTypeBoolean:
case BaseType::kBaseTypeSigned:
case BaseType::kBaseTypeSignedChar:
case BaseType::kBaseTypeUnsigned:
case BaseType::kBaseTypeUnsignedChar:
case BaseType::kBaseTypeUTF:
if (base_type->byte_size() <= 8)
return GetReturnRegisterForMachineInt();
// Larger numbers are spread across multiple registers which we don't support yet.
return std::nullopt;
case BaseType::kBaseTypeNone:
case BaseType::kBaseTypeAddress: // Not used in C.
default:
return std::nullopt;
}
}
std::optional<Abi::CollectionReturn> AbiX64::GetCollectionReturnByRefLocation(
const Collection* collection) {
FX_DCHECK(collection->calling_convention() == Collection::kPassByReference);
// Pass-by-reference collections are placed into a location indicated by the caller and that
// location is echoed back upon return in the rax register.
return CollectionReturn{.addr_return_reg = RegisterID::kX64_rax};
}
std::optional<Abi::CollectionByValueReturn> AbiX64::GetCollectionReturnByValueLocation(
const fxl::RefPtr<EvalContext>& eval_context, const Collection* collection) {
if (collection->byte_size() == 0 || collection->byte_size() > 16)
return std::nullopt; // Too big.
// Get all the data members.
std::vector<DataMemberInfo> members;
if (!GetDataMembersForByValueReturning(eval_context, collection, members))
return std::nullopt;
// Merge into classes representing each eightbyte section.
std::vector<RegisterClass> classes;
if (!MergeDataMembersForByValueReturning(members, classes))
return std::nullopt;
return AllocateRegistersForByValueReturning(collection->byte_size(), classes);
}
} // namespace zxdb