blob: 1f3ce6a6616a0478e0d9ed62279658ddf847e476 [file] [log] [blame]
/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef BERBERIS_INTRINSICS_INTRINSICS_ARGS_H_
#define BERBERIS_INTRINSICS_INTRINSICS_ARGS_H_
#include <cstddef>
#include <cstdio>
#include "berberis/base/checks.h"
namespace berberis {
// Helper classes for the EmbedAsmInstruction "construction class".
//
// Constructor of this class takes prescribed arguments from IR insn and
// calls a single assembler [macro]instruction. It creates required scratch
// register allocations and proper MOVs to preserve semantic of the IR insn.
//
// Name of a helper class describes argument of the assembler
// [macro]instruction.
//
// You could find many examples in intrinsics_x86.h file but EmbedAsmInstruction
// is not x86-specific: in theory it should work with any MachineIRBuilder.
//
// Each argument must by of one of the following types:
//
// InArg<N> - argument comes from <N>th source of the IR insn.
// Must be "use" argument of the assembler [macro]instruction.
// Note: please don't use this argument for specific register
// classes (such as "RDX" or "RCX"). If one operation returns
// result, e.g., in "RCX" and another one accepts it in "RCX"
// then register allocator will not be able to satisfy such
// requirements. Use InTmpArg<N> for such instructions.
//
// OutArg<N> - argument comes from <N>th destination of the IR insn.
// Must be "def" or "def_early_clobber" argument of the assembler
// [macro]instruction.
//
// OutTmpArg<N> - argument is copied from temporary register to <N>th
// destination of the IR insn.
// Must be "def" or "def_early_clobber" argument of the
// assembler [macro]instruction.
//
// InOutArg<N, M> - argument is copied from <N>th source of the IR insn to
// <M>th destination of the IR insn, then is passed it to the
// [macro]instruction.
// Must be "use_def" argument of the assembler
// [macro]instruction.
//
// InOutTmpArg<N, M> - argument is copied from <N>th source of the IR insn to
// temporary register, then it's passed to the
// [macro]instruction. Result is copied to the
// <M>th destination of the IR insn.
// Must be "use_def" argument of the assembler
// [macro]instruction.
//
// InTmpArg<N> - argument is copied from <N>th source of the IR insn to the
// temporary register, then is passed to the [macro]instruction.
// Must be "use_def" argument of the assembler
// [macro]instruction.
//
// ImmArg<N, uintXX_t> - argument is 8bit/16bit/32bit/64bit immediate and
// comes as <N>th source of IR insn.
//
// TmpArg - argument is temporary register allocated for the
// [macro]instruction.
//
// TODO(khim): investigate feasibility of adding unconditional copying of
// arguments and results. This way we could remove classes InOutTmpArg/InTmpArg
// and, more importantly, make sure InArg vs InTmpArg mixup will not lead to
// hard to debug errors.
struct ArgInfo {
public:
enum ArgType {
IN_ARG,
IN_TMP_ARG,
OUT_ARG,
OUT_TMP_ARG,
IN_OUT_ARG,
IN_OUT_TMP_ARG,
TMP_ARG,
IMM_ARG
} arg_type;
friend constexpr bool HaveInput(const ArgInfo& arg) {
return arg.arg_type == ArgInfo::IN_ARG ||
arg.arg_type == ArgInfo::IN_TMP_ARG ||
arg.arg_type == ArgInfo::IN_OUT_ARG ||
arg.arg_type == ArgInfo::IN_OUT_TMP_ARG;
}
friend constexpr bool HaveOutput(const ArgInfo& arg) {
return arg.arg_type == ArgInfo::IN_OUT_ARG ||
arg.arg_type == ArgInfo::IN_OUT_TMP_ARG ||
arg.arg_type == OUT_ARG ||
arg.arg_type == OUT_TMP_ARG;
}
friend constexpr bool IsImmediate(const ArgInfo& arg) {
return arg.arg_type == IMM_ARG;
}
friend constexpr bool IsTemporary(const ArgInfo& arg) {
return arg.arg_type == TMP_ARG;
}
const int from = 0;
const int to = 0;
};
template <int N, typename RegisterClass = void, typename Usage = void>
class InArg;
template <int N, typename RegisterClass = void, typename Usage = void>
class OutArg;
template <int N, typename RegisterClass = void, typename Usage = void>
class OutTmpArg;
template <int N, int M, typename RegisterClass = void, typename Usage = void>
class InOutArg;
template <int N, int M, typename RegisterClass = void, typename Usage = void>
class InOutTmpArg;
template <int N, typename RegisterClass = void, typename Usage = void>
class InTmpArg;
template <int N, typename ImmType, typename ImmediateClass = void>
class ImmArg;
template <typename RegisterClass = void, typename Usage = void>
class TmpArg;
template <typename ArgInfo>
class ArgTraits;
template <int N, typename RegisterClassType, typename UsageType>
class ArgTraits<InArg<N, RegisterClassType, UsageType>> {
public:
using Class = RegisterClassType;
using RegisterClass = RegisterClassType;
using Usage = UsageType;
template <typename MachineReg>
using BuilderArg = MachineReg;
static constexpr ArgInfo arg_info{.arg_type = ArgInfo::IN_ARG, .from = N};
};
template <int N, typename RegisterClassType, typename UsageType>
class ArgTraits<OutArg<N, RegisterClassType, UsageType>> {
public:
using Class = RegisterClassType;
using RegisterClass = RegisterClassType;
using Usage = UsageType;
template <typename MachineReg>
using BuilderArg = MachineReg;
static constexpr ArgInfo arg_info{.arg_type = ArgInfo::OUT_ARG, .to = N};
};
template <int N, typename RegisterClassType, typename UsageType>
class ArgTraits<OutTmpArg<N, RegisterClassType, UsageType>> {
public:
using Class = RegisterClassType;
using RegisterClass = RegisterClassType;
using Usage = UsageType;
template <typename MachineReg>
using BuilderArg = MachineReg;
static constexpr ArgInfo arg_info{.arg_type = ArgInfo::OUT_TMP_ARG, .to = N};
};
template <int N, int M, typename RegisterClassType, typename UsageType>
class ArgTraits<InOutArg<N, M, RegisterClassType, UsageType>> {
public:
using Class = RegisterClassType;
using RegisterClass = RegisterClassType;
using Usage = UsageType;
template <typename MachineReg>
using BuilderArg = MachineReg;
static constexpr ArgInfo arg_info{.arg_type = ArgInfo::IN_OUT_ARG, .from = N, .to = M};
};
template <int N, int M, typename RegisterClassType, typename UsageType>
class ArgTraits<InOutTmpArg<N, M, RegisterClassType, UsageType>> {
public:
using Class = RegisterClassType;
using RegisterClass = RegisterClassType;
using Usage = UsageType;
template <typename MachineReg>
using BuilderArg = MachineReg;
static constexpr ArgInfo arg_info{.arg_type = ArgInfo::IN_OUT_TMP_ARG, .from = N, .to = M};
};
template <int N, typename RegisterClassType, typename UsageType>
class ArgTraits<InTmpArg<N, RegisterClassType, UsageType>> {
public:
using Class = RegisterClassType;
using RegisterClass = RegisterClassType;
using Usage = UsageType;
template <typename MachineReg>
using BuilderArg = MachineReg;
static constexpr ArgInfo arg_info{.arg_type = ArgInfo::IN_TMP_ARG, .from = N};
};
template <int N, typename ImmType, typename ImmediateClassType>
class ArgTraits<ImmArg<N, ImmType, ImmediateClassType>> {
public:
using Class = ImmediateClassType;
using ImmediateClass = ImmediateClassType;
template <typename>
using BuilderArg = ImmType;
static constexpr ArgInfo arg_info{.arg_type = ArgInfo::IMM_ARG, .from = N};
};
template <typename RegisterClassType, typename UsageType>
class ArgTraits<TmpArg<RegisterClassType, UsageType>> {
public:
using Class = RegisterClassType;
using RegisterClass = RegisterClassType;
using Usage = UsageType;
template <typename MachineReg>
using BuilderArg = MachineReg;
static constexpr ArgInfo arg_info{.arg_type = ArgInfo::TMP_ARG};
};
// We couldn't use standard "throw std::logic_error(...)" approach here because that code is
// compiled with -fno-exceptions. Thankfully printf(...) produces very similar error messages.
//
// See https://stackoverflow.com/questions/8626055/c11-static-assert-within-constexpr-function
// is you need an explanation for how basic technique works.
template <typename MachineInsn, int arguments_count>
constexpr bool IsCompatible(const ArgInfo* arguments) {
int reg_arguments = 0;
for (size_t argument = 0; argument < arguments_count; ++argument) {
if (arguments[argument].arg_type != ArgInfo::IMM_ARG) {
if ((arguments[argument].arg_type == ArgInfo::IN_ARG) &&
MachineInsn::RegKindAt(reg_arguments).IsDef()) {
fprintf(stderr, "Incorrect use of InArg for argument %d", argument);
return false;
} else if ((arguments[argument].arg_type == ArgInfo::IN_TMP_ARG) &&
!MachineInsn::RegKindAt(reg_arguments).IsDef() &&
!IsFixedRegClass(MachineInsn::RegKindAt(reg_arguments).RegClass())) {
fprintf(stderr, "Inefficient use of InTmpArg for argument %d", argument);
return false;
} else if ((arguments[argument].arg_type == ArgInfo::OUT_ARG) &&
IsFixedRegClass(MachineInsn::RegKindAt(reg_arguments).RegClass())) {
fprintf(stderr, "Incorrect use of OutArg for argument %d", argument);
return false;
} else if ((arguments[argument].arg_type == ArgInfo::OUT_TMP_ARG) &&
!IsFixedRegClass(MachineInsn::RegKindAt(reg_arguments).RegClass())) {
fprintf(stderr, "Inefficient use of OutTmpArg for argument %d", argument);
return false;
} else if ((arguments[argument].arg_type == ArgInfo::IN_OUT_ARG) &&
IsFixedRegClass(MachineInsn::RegKindAt(reg_arguments).RegClass())) {
fprintf(stderr, "Incorrect use of InOutArg for argument %d", argument);
return false;
} else if ((arguments[argument].arg_type == ArgInfo::IN_OUT_TMP_ARG) &&
!IsFixedRegClass(MachineInsn::RegKindAt(reg_arguments).RegClass())) {
fprintf(stderr, "Inefficient use of InOutTmpArg for argument %d", argument);
return false;
}
if (HaveInput(arguments[argument]) &&
!MachineInsn::RegKindAt(reg_arguments).IsInput()) {
fprintf(stderr, "Argument %d does not accept input!", argument);
return false;
} else if (!HaveInput(arguments[argument]) &&
MachineInsn::RegKindAt(reg_arguments).IsInput()) {
fprintf(stderr, "Argument %d requires valid input!", argument);
return false;
}
++reg_arguments;
}
}
if (MachineInsn::NumRegOperands() != reg_arguments) {
fprintf(stderr,
"expected %d arguments, got %d arguments",
MachineInsn::NumRegOperands(),
reg_arguments);
return false;
}
return true;
}
template <typename MachineInsn, typename... Args>
constexpr bool IsCompatible() {
const ArgInfo arguments[] = {ArgTraits<Args>::arg_info...};
// Note: we couldn't pass arguments as an array into IsCompatible by reference
// because this would cause compilation error in case where we have no arguments.
//
// Pass pointer and element count instead.
return IsCompatible<MachineInsn, sizeof...(Args)>(arguments);
}
template <typename MachineIRBuilder, typename Arg>
class ArgGetterSetter;
template <typename Instruction, typename... Args>
class EmbedAsmInstruction {
public:
template <typename MachineIRBuilder, typename IntrinsicInsn>
EmbedAsmInstruction(MachineIRBuilder* builder, const IntrinsicInsn* insn) {
static_assert(IsCompatible<Instruction, Args...>(), "Incompatible intrinsic embedding");
builder->template Gen<Instruction>(ArgGetterSetter<MachineIRBuilder, Args>(builder, insn)...);
}
};
} // namespace berberis
#endif // BERBERIS_INTRINSICS_INTRINSICS_ARGS_H_