blob: a02af4dbe57d6598fe2efef182adc36d20841d2e [file] [log] [blame]
/*
* Copyright (C) 2024 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#pragma once
#include <JavaScriptCore/JITOperationValidation.h>
#include <JavaScriptCore/ThrowScope.h>
#include <type_traits>
#include <wtf/FunctionTraits.h>
#include <wtf/StdLibExtras.h>
namespace JSC {
// This is wrapped in a lambda since it's used by static_asserts thus needs to be an expression.
// Additionally, it should not participate in SFINAE: substitution failure IS an error when used in
// a type name below.
template<typename T>
constexpr bool assertNotOperationSignature = ([] {
static_assert(!std::is_function_v<typename std::remove_pointer_t<T>>, "This should take a return type, not an operator signature.");
return true;
})();
template<typename T>
concept canMakeExceptionOperationResult =
std::is_standard_layout_v<T>
&& std::is_trivial_v<T>
#if CPU(ARM64) || CPU(ARM_THUMB2)
&& !std::is_floating_point_v<T> // The ARM64 ABI says that this should be returned in x0 instead of d0. Seems unlikely it's worth it to do the extra fmov.
#endif
&& sizeof(T) <= sizeof(CPURegister);
#if USE(JSVALUE64)
template<typename T>
struct ExceptionOperationResult {
using ResultType = T;
static_assert(assertNotOperationSignature<T>);
T value;
Exception* exception;
};
template<>
struct ExceptionOperationResult<void> {
using ResultType = void;
Exception* exception;
};
template<typename OperationType>
concept OperationHasResult = FunctionTraits<OperationType>::hasResult && !std::is_same_v<typename FunctionTraits<OperationType>::ResultType, ExceptionOperationResult<void>>;
// Note we need this because e.g. `requires (!OperationHasResult<int>)` will pass the requirement as the SFINAE in `OperationHasResult<int>`
// means the concept returns false and the `!` makes it true.
template<typename OperationType>
concept OperationIsVoid = !FunctionTraits<OperationType>::hasResult || std::is_same_v<typename FunctionTraits<OperationType>::ResultType, ExceptionOperationResult<void>>;
template<typename T>
struct IsExceptionOperationResult : std::false_type { };
template<typename T>
struct IsExceptionOperationResult<ExceptionOperationResult<T>> : std::true_type { };
template<typename T>
concept isExceptionOperationResult = IsExceptionOperationResult<T>::value;
// Note: We always return an exception in a register if the operation's return type is void on all platforms (assuming it throws).
template<typename T>
using OperationReturnType = std::conditional_t<(std::is_same_v<T, void> || canMakeExceptionOperationResult<T>), ExceptionOperationResult<T>, T>;
// This class exists so we can cast ExceptionOperationImplicitResult<From> to ExceptionOperationResult<To> implicitly while ExceptionOperationResult remains POD.
// FIXME: we could use __atribute__((trivial_abi)) when we no longer support MSVC.
template<typename From>
struct ExceptionOperationImplicitResult {
template<typename To>
requires std::is_convertible_v<From, To>
ALWAYS_INLINE operator ExceptionOperationResult<To>()
{
// For whatever reason aggregate initialization doesn't seem to work here.
ExceptionOperationResult<To> result;
result.value = value;
result.exception = exception;
return result;
}
template<typename To>
ALWAYS_INLINE operator To()
{
return value;
}
From value;
Exception* exception;
};
template<>
struct ExceptionOperationImplicitResult<void> {
ALWAYS_INLINE operator ExceptionOperationResult<void>()
{
ExceptionOperationResult<void> result;
result.exception = exception;
return result;
}
Exception* exception;
};
// We can't have a constructor since that would make ExceptionOperationResult non-pod and thus not returned in registers.
template<typename Scope, typename T>
requires canMakeExceptionOperationResult<T>
ALWAYS_INLINE ExceptionOperationImplicitResult<T> makeOperationResult(Scope& scope, T result)
{
return { result, scope.exception() };
}
template<typename Scope>
ALWAYS_INLINE ExceptionOperationImplicitResult<void> makeOperationResult(Scope& scope)
{
return { scope.exception() };
}
template<typename Scope, typename T>
requires (!canMakeExceptionOperationResult<T>)
ALWAYS_INLINE T makeOperationResult(Scope& scope, T result)
{
RELEASE_AND_RETURN(scope, result);
}
#else // USE(JSVALUE64)
enum class ExceptionOperationResultTag : uint64_t { };
struct ExceptionOperationResultVoid {
Exception* exception;
};
template<typename T>
using ExceptionOperationResult = std::conditional_t<(std::is_same_v<T, void>), ExceptionOperationResultVoid, ExceptionOperationResultTag>;
template<typename OperationType>
concept OperationHasResult = FunctionTraits<OperationType>::hasResult && !std::is_same_v<typename FunctionTraits<OperationType>::ResultType, ExceptionOperationResult<void>>;
// Note we need this because e.g. `requires (!OperationHasResult<int>)` will pass the requirement as the SFINAE in `OperationHasResult<int>`
// means the concept returns false and the `!` makes it true.
template<typename OperationType>
concept OperationIsVoid = !FunctionTraits<OperationType>::hasResult || std::is_same_v<typename FunctionTraits<OperationType>::ResultType, ExceptionOperationResult<void>>;
template<typename T>
struct IsExceptionOperationResult : std::false_type { };
template<>
struct IsExceptionOperationResult<ExceptionOperationResultTag> : std::true_type { };
template<>
struct IsExceptionOperationResult<ExceptionOperationResultVoid> : std::true_type { };
template<typename T>
concept isExceptionOperationResult = IsExceptionOperationResult<T>::value;
// Note: We always return an exception in a register if the operation's return type is void on all platforms (assuming it throws).
template<typename T>
using OperationReturnType = std::conditional_t<(assertNotOperationSignature<T> && (std::is_same_v<T, void> || canMakeExceptionOperationResult<T>)), ExceptionOperationResult<T>, T>;
static ALWAYS_INLINE ExceptionOperationResultTag asExceptionResultBase(uint32_t value, Exception* exception)
{
uint32_t v = std::bit_cast<uint32_t>(value);
return std::bit_cast<ExceptionOperationResultTag>(
static_cast<long long>(v)
| (static_cast<long long>(reinterpret_cast<uint32_t>(exception)) << 32));
}
template<typename T>
requires (sizeof(T) == sizeof(uint32_t))
static ALWAYS_INLINE ExceptionOperationResultTag asExceptionResult(T value, Exception* exception)
{
uint32_t v = std::bit_cast<uint32_t>(value);
return asExceptionResultBase(v, exception);
}
template<typename T>
requires (sizeof(T) < sizeof(uint32_t))
static ALWAYS_INLINE ExceptionOperationResultTag asExceptionResult(T value, Exception* exception)
{
uint32_t v = static_cast<uint32_t>(value);
return asExceptionResultBase(v, exception);
}
template<typename From>
struct ExceptionOperationImplicitResult {
ALWAYS_INLINE operator ExceptionOperationResultTag()
{
return asExceptionResult(value, exception);
}
template<typename To>
ALWAYS_INLINE operator To()
{
return static_cast<To>(value);
}
From value;
Exception* exception;
};
template<>
struct ExceptionOperationImplicitResult<void> {
ALWAYS_INLINE operator ExceptionOperationResultVoid()
{
return { exception };
}
Exception* exception;
};
template<typename Scope, typename T>
requires canMakeExceptionOperationResult<T>
ALWAYS_INLINE ExceptionOperationImplicitResult<T> makeOperationResult(Scope& scope, T result)
{
static_assert(assertNotOperationSignature<T>);
return { result, scope.exception() };
}
template<typename Scope>
ALWAYS_INLINE ExceptionOperationImplicitResult<void> makeOperationResult(Scope& scope)
{
return { scope.exception() };
}
template<typename Scope, typename T>
requires (!canMakeExceptionOperationResult<T>)
ALWAYS_INLINE T makeOperationResult(Scope& scope, T result)
{
static_assert(assertNotOperationSignature<T>);
RELEASE_AND_RETURN(scope, result);
}
#endif // USE(JSVALUE64)
#define OPERATION_RETURN(scope__, ...) return makeOperationResult(scope__, ## __VA_ARGS__)
#define OPERATION_RETURN_IF_EXCEPTION(scope__, ...) RETURN_IF_EXCEPTION(scope__, makeOperationResult(scope__, ## __VA_ARGS__))
#if COMPILER(CLANG)
#define JSC_DECLARE_JIT_OPERATION(functionName, returnType, parameters) \
_Pragma("clang diagnostic push") \
_Pragma("clang diagnostic ignored \"-Wreturn-type-c-linkage\"") \
JSC_DECLARE_JIT_OPERATION_IMPL(functionName, JSC::OperationReturnType<returnType>, parameters) \
_Pragma("clang diagnostic pop")
#define JSC_DECLARE_JIT_OPERATION_WITHOUT_WTF_INTERNAL(functionName, returnType, parameters) \
_Pragma("clang diagnostic push") \
_Pragma("clang diagnostic ignored \"-Wreturn-type-c-linkage\"") \
JSC_DECLARE_JIT_OPERATION_WITHOUT_WTF_INTERNAL_IMPL(functionName, JSC::OperationReturnType<returnType>, parameters) \
_Pragma("clang diagnostic pop")
#else
#define JSC_DECLARE_JIT_OPERATION(functionName, returnType, parameters) JSC_DECLARE_JIT_OPERATION_IMPL(functionName, JSC::OperationReturnType<returnType>, parameters)
#define JSC_DECLARE_JIT_OPERATION_WITHOUT_WTF_INTERNAL(functionName, returnType, parameters) JSC_DECLARE_JIT_OPERATION_WITHOUT_WTF_INTERNAL_IMPL(functionName, JSC::OperationReturnType<returnType>, parameters)
#endif
#define JSC_DEFINE_JIT_OPERATION(functionName, returnType, parameters) JSC_DEFINE_JIT_OPERATION_IMPL(functionName, JSC::OperationReturnType<returnType>, parameters)
#define JSC_DECLARE_NOEXCEPT_JIT_OPERATION(functionName, returnType, parameters) JSC_DECLARE_JIT_OPERATION_IMPL(functionName, returnType, parameters)
#define JSC_DECLARE_NOEXCEPT_JIT_OPERATION_WITH_ATTRIBUTES(functionName, attributes, returnType, parameters) JSC_DECLARE_JIT_OPERATION_WITH_ATTRIBUTES_IMPL(functionName, attributes, returnType, parameters)
#define JSC_DECLARE_NOEXCEPT_JIT_OPERATION_WITHOUT_WTF_INTERNAL(functionName, returnType, parameters) JSC_DECLARE_JIT_OPERATION_WITHOUT_WTF_INTERNAL_IMPL(functionName, returnType, parameters)
#define JSC_DEFINE_NOEXCEPT_JIT_OPERATION_WITH_ATTRIBUTES(functionName, attributes, returnType, parameters) JSC_DEFINE_JIT_OPERATION_WITH_ATTRIBUTES_IMPL(functionName, attributes, returnType, parameters)
#define JSC_DEFINE_NOEXCEPT_JIT_OPERATION(functionName, returnType, parameters) JSC_DEFINE_JIT_OPERATION_IMPL(functionName, returnType, parameters)
} // namespace JSC