Commit fd306a06 authored by Clemens Hammacher's avatar Clemens Hammacher Committed by Commit Bot

Allow constexpr RegList construction from Registers

Before, the standard way to create a RegList was either:
RegList list = (1 << 0) | (1 << 1) | ...
or
RegList list = rax.bit() | rdx.bit() | ...

The first way allows to make the RegList constexpr, but needs comments
to document which registers you are referring to, and it has no checks
that all bits you set on the RegList actually belong to valid registers.
The second one uses the symbolic names, hence is much more readable and
makes it harder to construct invalid RegLists. It's not constexpr
though, since the {bit()} method on the register types is not constexpr.

This CL adds a constexpr accessor to get the code and bit of a
constexpr Register, and adds a helper method to create a constexpr
RegList like this:
constexpr RegList list = Register::ListOf<rax, rdx, rdi>();

This new method is used in a number of places to test its
applicability. Other uses of the old pattern remain and can be cleaned
up later.

R=tebbi@chromium.org

Change-Id: Ie7b1d6342dc5f316dcfedd0363b3540ad5e7f413
Reviewed-on: https://chromium-review.googlesource.com/728026
Commit-Queue: Clemens Hammacher <clemensh@chromium.org>
Reviewed-by: 's avatarTobias Tebbi <tebbi@chromium.org>
Cr-Commit-Position: refs/heads/master@{#48887}
parent 215a2506
......@@ -1251,6 +1251,10 @@ class HeapObjectRequest {
// and best performance in optimized code.
template <typename SubType, int kAfterLastRegister>
class RegisterBase {
// Internal enum class; used for calling constexpr methods, where we need to
// pass an integral type as template parameter.
enum class RegisterCode : int { kFirst = 0, kAfterLast = kAfterLastRegister };
public:
static constexpr int kCode_no_reg = -1;
static constexpr int kNumRegisters = kAfterLastRegister;
......@@ -1263,12 +1267,34 @@ class RegisterBase {
return SubType{code};
}
constexpr operator RegisterCode() const {
return static_cast<RegisterCode>(reg_code_);
}
template <RegisterCode reg_code>
static constexpr int code() {
static_assert(
reg_code >= RegisterCode::kFirst && reg_code < RegisterCode::kAfterLast,
"must be valid reg");
return static_cast<int>(reg_code);
}
template <RegisterCode reg_code>
static constexpr int bit() {
return 1 << code<reg_code>();
}
static SubType from_code(int code) {
DCHECK_LE(0, code);
DCHECK_GT(kNumRegisters, code);
return SubType{code};
}
template <RegisterCode... reg_codes>
static constexpr RegList ListOf() {
return CombineRegLists(RegisterBase::bit<reg_codes>()...);
}
bool is_valid() const { return reg_code_ != kCode_no_reg; }
int code() const {
......
......@@ -22,8 +22,9 @@ struct make_array_helper;
template <class Function, std::size_t... Indexes>
struct make_array_helper<Function, 0, Indexes...> {
constexpr static auto make_array(Function f)
-> std::array<decltype(f(std::size_t{0})), sizeof...(Indexes) + 1> {
constexpr static std::array<typename std::result_of<Function(size_t)>::type,
sizeof...(Indexes) + 1>
make_array(Function f) {
return {{f(0), f(Indexes)...}};
}
};
......@@ -41,8 +42,8 @@ struct make_array_helper<Function, FirstIndex, Indexes...>
// [](std::size_t i) { return static_cast<int>(2 * i); });
// The resulting array will be constexpr if the passed function is constexpr.
template <std::size_t Size, class Function>
constexpr auto make_array(Function f)
-> std::array<decltype(f(std::size_t{0})), Size> {
constexpr std::array<typename std::result_of<Function(size_t)>::type, Size>
make_array(Function f) {
static_assert(Size > 0, "Can only create non-empty arrays");
return detail::make_array_helper<Function, Size - 1>::make_array(f);
}
......@@ -93,6 +94,40 @@ struct has_output_operator {
static constexpr bool value = sizeof(__check_operator(ptr_t{nullptr})) == 1;
};
namespace detail {
template <typename Func, typename T, typename... Ts>
struct fold_helper {
static_assert(sizeof...(Ts) == 0, "this is the base case");
using result_t = typename std::remove_reference<T>::type;
static constexpr T&& fold(Func func, T&& first) {
return std::forward<T>(first);
}
};
template <typename Func, typename T1, typename T2, typename... Ts>
struct fold_helper<Func, T1, T2, Ts...> {
using folded_t = typename std::result_of<Func(T1, T2)>::type;
using next_fold_helper = fold_helper<Func, folded_t&&, Ts...>;
using result_t = typename next_fold_helper::result_t;
static constexpr result_t fold(Func func, T1&& first, T2&& second,
Ts&&... more) {
return next_fold_helper::fold(
func, func(std::forward<T1>(first), std::forward<T2>(second)),
std::forward<Ts>(more)...);
}
};
} // namespace detail
// Fold all arguments from left to right with a given function.
template <typename Func, typename... Ts>
constexpr auto fold(Func func, Ts&&... more) ->
typename detail::fold_helper<Func, Ts...>::result_t {
return detail::fold_helper<Func, Ts...>::fold(func,
std::forward<Ts>(more)...);
}
} // namespace base
} // namespace v8
......
......@@ -2485,7 +2485,7 @@ void Builtins::Generate_WasmCompileLazy(MacroAssembler* masm) {
// Save all parameter registers (see wasm-linkage.cc). They might be
// overwritten in the runtime call below. We don't have any callee-saved
// registers in wasm, so no need to store anything else.
const RegList gp_regs = r0.bit() | r1.bit() | r2.bit() | r3.bit();
constexpr RegList gp_regs = Register::ListOf<r0, r1, r2, r3>();
constexpr DwVfpRegister lowest_fp_reg = d0;
constexpr DwVfpRegister highest_fp_reg = d7;
......
......@@ -2688,10 +2688,10 @@ void Builtins::Generate_WasmCompileLazy(MacroAssembler* masm) {
// Save all parameter registers (see wasm-linkage.cc). They might be
// overwritten in the runtime call below. We don't have any callee-saved
// registers in wasm, so no need to store anything else.
const RegList gp_regs = x0.bit() | x1.bit() | x2.bit() | x3.bit() |
x4.bit() | x5.bit() | x6.bit() | x7.bit();
const RegList fp_regs = d0.bit() | d1.bit() | d2.bit() | d3.bit() |
d4.bit() | d5.bit() | d6.bit() | d7.bit();
constexpr RegList gp_regs =
Register::ListOf<x0, x1, x2, x3, x4, x5, x6, x7>();
constexpr RegList fp_regs =
Register::ListOf<d0, d1, d2, d3, d4, d5, d6, d7>();
__ PushXRegList(gp_regs);
__ PushDRegList(fp_regs);
......
......@@ -2528,9 +2528,9 @@ void Builtins::Generate_WasmCompileLazy(MacroAssembler* masm) {
// Save all parameter registers (see wasm-linkage.cc). They might be
// overwritten in the runtime call below. We don't have any callee-saved
// registers in wasm, so no need to store anything else.
const RegList gp_regs = a0.bit() | a1.bit() | a2.bit() | a3.bit();
const RegList fp_regs = f2.bit() | f4.bit() | f6.bit() | f8.bit() |
f10.bit() | f12.bit() | f14.bit();
constexpr RegList gp_regs = Register::ListOf<a0, a1, a2, a3>();
constexpr RegList fp_regs =
DoubleRegister::ListOf<f2, f4, f6, f8, f10, f12, f14>();
__ MultiPush(gp_regs);
__ MultiPushFPU(fp_regs);
......
......@@ -2552,10 +2552,10 @@ void Builtins::Generate_WasmCompileLazy(MacroAssembler* masm) {
// Save all parameter registers (see wasm-linkage.cc). They might be
// overwritten in the runtime call below. We don't have any callee-saved
// registers in wasm, so no need to store anything else.
const RegList gp_regs = a0.bit() | a1.bit() | a2.bit() | a3.bit() |
a4.bit() | a5.bit() | a6.bit() | a7.bit();
const RegList fp_regs = f2.bit() | f4.bit() | f6.bit() | f8.bit() |
f10.bit() | f12.bit() | f14.bit();
constexpr RegList gp_regs =
Register::ListOf<a0, a1, a2, a3, a4, a5, a6, a7>();
constexpr RegList fp_regs =
DoubleRegister::ListOf<f2, f4, f6, f8, f10, f12, f14>();
__ MultiPush(gp_regs);
__ MultiPushFPU(fp_regs);
......
......@@ -2560,10 +2560,10 @@ void Builtins::Generate_WasmCompileLazy(MacroAssembler* masm) {
// Save all parameter registers (see wasm-linkage.cc). They might be
// overwritten in the runtime call below. We don't have any callee-saved
// registers in wasm, so no need to store anything else.
const RegList gp_regs = r3.bit() | r4.bit() | r5.bit() | r6.bit() |
r7.bit() | r8.bit() | r9.bit() | r10.bit();
const RegList fp_regs = d1.bit() | d2.bit() | d3.bit() | d4.bit() |
d5.bit() | d6.bit() | d7.bit() | d8.bit();
constexpr RegList gp_regs =
Register::ListOf<r3, r4, r5, r6, r7, r8, r9, r10>();
constexpr RegList fp_regs =
DoubleRegister::ListOf<d1, d2, d3, d4, d5, d6, d7, d8>();
__ MultiPush(gp_regs);
__ MultiPushDoubles(fp_regs);
......
......@@ -2557,12 +2557,11 @@ void Builtins::Generate_WasmCompileLazy(MacroAssembler* masm) {
// Save all parameter registers (see wasm-linkage.cc). They might be
// overwritten in the runtime call below. We don't have any callee-saved
// registers in wasm, so no need to store anything else.
const RegList gp_regs =
r2.bit() | r3.bit() | r4.bit() | r5.bit() | r6.bit();
constexpr RegList gp_regs = Register::ListOf<r2, r3, r4, r5, r6>();
#if V8_TARGET_ARCH_S390X
const RegList fp_regs = d0.bit() | d2.bit() | d4.bit() | d6.bit();
constexpr RegList fp_regs = DoubleRegister::ListOf<d0, d2, d4, d6>();
#else
const RegList fp_regs = d0.bit() | d2.bit();
constexpr RegList fp_regs = DoubleRegister::ListOf<d0, d2>();
#endif
__ MultiPush(gp_regs);
__ MultiPushDoubles(fp_regs);
......
......@@ -87,22 +87,6 @@ namespace internal {
V(xmm6) \
V(xmm7)
// Note that the bit values must match those used in actual instruction encoding
const int kNumRegs = 8;
// Caller-saved registers
const RegList kJSCallerSaved =
1 << 0 | // eax
1 << 1 | // ecx
1 << 2 | // edx
1 << 3 | // ebx - used as a caller-saved register in JavaScript code
1 << 7; // edi - callee function
const int kNumJSCallerSaved = 5;
// Number of registers for which space is reserved in safepoints.
const int kNumSafepointRegisters = 8;
enum RegisterCode {
#define REGISTER_CODE(R) kRegCode_##R,
GENERAL_REGISTERS(REGISTER_CODE)
......@@ -156,6 +140,21 @@ DOUBLE_REGISTERS(DEFINE_REGISTER)
#undef DEFINE_REGISTER
constexpr DoubleRegister no_double_reg = DoubleRegister::no_reg();
// Note that the bit values must match those used in actual instruction encoding
constexpr int kNumRegs = 8;
// Caller-saved registers
constexpr RegList kJSCallerSaved =
Register::ListOf<eax, ecx, edx,
ebx, // used as a caller-saved register in JavaScript code
edi // callee function
>();
constexpr int kNumJSCallerSaved = 5;
// Number of registers for which space is reserved in safepoints.
constexpr int kNumSafepointRegisters = 8;
enum Condition {
// any value < 0 is considered no_condition
no_condition = -1,
......
......@@ -8,6 +8,7 @@
#include <cstdint>
#include "src/base/bits.h"
#include "src/base/template-utils.h"
namespace v8 {
namespace internal {
......@@ -20,7 +21,25 @@ typedef uint32_t RegList;
#endif
// Get the number of registers in a given register list.
inline int NumRegs(RegList list) { return base::bits::CountPopulation(list); }
constexpr int NumRegs(RegList list) {
return base::bits::CountPopulation(list);
}
// Combine two RegLists by building the union of the contained registers.
// Implemented as a Functor to pass it to base::fold even on gcc < 5 (see
// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=52892).
// TODO(clemensh): Remove this once we require gcc >= 5.0.
struct CombineRegListsFunctor {
constexpr RegList operator()(RegList list1, RegList list2) const {
return list1 | list2;
}
};
// Combine several RegLists by building the union of the contained registers.
template <typename... RegLists>
constexpr RegList CombineRegLists(RegLists... lists) {
return base::fold(CombineRegListsFunctor{}, 0, lists...);
}
} // namespace internal
} // namespace v8
......
......@@ -84,19 +84,6 @@ namespace internal {
// The length of pushq(rbp), movp(rbp, rsp), Push(rsi) and Push(rdi).
constexpr int kNoCodeAgeSequenceLength = kPointerSize == kInt64Size ? 6 : 17;
const int kNumRegs = 16;
const RegList kJSCallerSaved =
1 << 0 | // rax
1 << 1 | // rcx
1 << 2 | // rdx
1 << 3 | // rbx - used as a caller-saved register in JavaScript code
1 << 7; // rdi - callee function
const int kNumJSCallerSaved = 5;
// Number of registers for which space is reserved in safepoints.
const int kNumSafepointRegisters = 16;
enum RegisterCode {
#define REGISTER_CODE(R) kRegCode_##R,
GENERAL_REGISTERS(REGISTER_CODE)
......@@ -129,6 +116,19 @@ GENERAL_REGISTERS(DECLARE_REGISTER)
#undef DECLARE_REGISTER
constexpr Register no_reg = Register::no_reg();
constexpr int kNumRegs = 16;
constexpr RegList kJSCallerSaved =
Register::ListOf<rax, rcx, rdx,
rbx, // used as a caller-saved register in JavaScript code
rdi // callee function
>();
constexpr int kNumJSCallerSaved = 5;
// Number of registers for which space is reserved in safepoints.
constexpr int kNumSafepointRegisters = 16;
#ifdef _WIN64
// Windows calling convention
constexpr Register arg_reg_1 = rcx;
......
......@@ -101,6 +101,63 @@ static_assert(has_output_operator<TestClass2>::value,
static_assert(!has_output_operator<const TestClass2>::value,
"const TestClass2 can not be output");
//////////////////////////////
// Test fold.
//////////////////////////////
struct FoldAllSameType {
constexpr uint32_t operator()(uint32_t a, uint32_t b) const { return a | b; }
};
static_assert(base::fold(FoldAllSameType{}, 3, 6) == 7, "check fold");
// Test that it works if implicit conversion is needed for one of the
// parameters.
static_assert(base::fold(FoldAllSameType{}, uint8_t{1}, 256) == 257,
"check correct type inference");
// Test a single parameter.
static_assert(base::fold(FoldAllSameType{}, 25) == 25,
"check folding a single argument");
TEST(TemplateUtilsTest, FoldDifferentType) {
auto fn = [](std::string str, char c) {
str.push_back(c);
return str;
};
CHECK_EQ(base::fold(fn, std::string("foo"), 'b', 'a', 'r'), "foobar");
}
TEST(TemplateUtilsTest, FoldMoveOnlyType) {
auto fn = [](std::unique_ptr<std::string> str, char c) {
str->push_back(c);
return str;
};
std::unique_ptr<std::string> str = base::make_unique<std::string>("foo");
std::unique_ptr<std::string> folded =
base::fold(fn, std::move(str), 'b', 'a', 'r');
CHECK_NULL(str);
CHECK_NOT_NULL(folded);
CHECK_EQ(*folded, "foobar");
}
struct TemplatizedFoldFunctor {
template <typename T, typename... Tup>
std::tuple<Tup..., typename std::decay<T>::type> operator()(
std::tuple<Tup...> tup, T&& val) {
return std::tuple_cat(std::move(tup),
std::make_tuple(std::forward<T>(val)));
}
};
TEST(TemplateUtilsTest, FoldToTuple) {
auto input = std::make_tuple(char{'x'}, int{4}, double{3.2},
std::unique_ptr<uint8_t>{}, std::string{"foo"});
auto result =
base::fold(TemplatizedFoldFunctor{}, std::make_tuple(),
std::get<0>(input), std::get<1>(input), std::get<2>(input),
std::unique_ptr<uint8_t>{}, std::get<4>(input));
static_assert(std::is_same<decltype(result), decltype(input)>::value,
"the resulting tuple should have the same type as the input");
DCHECK_EQ(input, result);
}
} // namespace template_utils_unittest
} // namespace base
} // namespace v8
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