dawn.node: Implement the [Clamp] and [EnforceRange] WebIDL attributes.

These are implemented by wrapping the integer types in transparent
ClampedInteger<> and EnforceRangeInteger<> structures.

Some parts of the core needed to be updated after this, either to
disambiguate conversions, or because of bugs (u32 vs u64).

To make the CTS tests checking for this pass, the errors returned when
conversion FromJS failed needed to be updated to TypeError and not just
the generic Napi::Error.

Bug: dawn:1123
Change-Id: Ife1d0baa7687e43d735a1814ec41883c49ae74a6
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/85640
Reviewed-by: Ben Clayton <bclayton@google.com>
Commit-Queue: Corentin Wallez <cwallez@chromium.org>
This commit is contained in:
Corentin Wallez 2022-04-04 14:46:12 +00:00 committed by Dawn LUCI CQ
parent e56b5f1097
commit 8033af0947
10 changed files with 162 additions and 19 deletions

View File

@ -464,27 +464,27 @@ namespace wgpu::binding {
}
bool Converter::Convert(wgpu::TextureUsage& out, const interop::GPUTextureUsageFlags& in) {
out = static_cast<wgpu::TextureUsage>(in);
out = static_cast<wgpu::TextureUsage>(in.value);
return true;
}
bool Converter::Convert(wgpu::ColorWriteMask& out, const interop::GPUColorWriteFlags& in) {
out = static_cast<wgpu::ColorWriteMask>(in);
out = static_cast<wgpu::ColorWriteMask>(in.value);
return true;
}
bool Converter::Convert(wgpu::BufferUsage& out, const interop::GPUBufferUsageFlags& in) {
out = static_cast<wgpu::BufferUsage>(in);
out = static_cast<wgpu::BufferUsage>(in.value);
return true;
}
bool Converter::Convert(wgpu::MapMode& out, const interop::GPUMapModeFlags& in) {
out = static_cast<wgpu::MapMode>(in);
out = static_cast<wgpu::MapMode>(in.value);
return true;
}
bool Converter::Convert(wgpu::ShaderStage& out, const interop::GPUShaderStageFlags& in) {
out = static_cast<wgpu::ShaderStage>(in);
out = static_cast<wgpu::ShaderStage>(in.value);
return true;
}

View File

@ -281,6 +281,20 @@ namespace wgpu::binding {
return true;
}
// ClampedInteger<T>
template <typename T>
inline bool Convert(T& out, const interop::ClampedInteger<T>& in) {
out = in;
return true;
}
// EnforceRangeInteger<T>
template <typename T>
inline bool Convert(T& out, const interop::EnforceRangeInteger<T>& in) {
out = in;
return true;
}
template <typename OUT, typename... IN_TYPES>
inline bool Convert(OUT& out, const std::variant<IN_TYPES...>& in) {
return std::visit([&](auto&& i) { return Convert(out, i); }, in);

View File

@ -69,7 +69,7 @@ namespace wgpu::binding {
auto ctx = new Context{env, interop::Promise<void>(env, PROMISE_INFO), async_, state_};
auto promise = ctx->promise;
uint64_t s = size.has_value() ? size.value() : (desc_.size - offset);
uint64_t s = size.has_value() ? size.value().value : (desc_.size - offset);
state_ = State::MappingPending;
@ -114,7 +114,7 @@ namespace wgpu::binding {
return {};
}
uint64_t s = size.has_value() ? size.value() : (desc_.size - offset);
uint64_t s = size.has_value() ? size.value().value : (desc_.size - offset);
uint64_t start = offset;
uint64_t end = offset + s;

View File

@ -156,7 +156,7 @@ namespace wgpu::binding {
Converter conv(env);
wgpu::Buffer b{};
uint32_t o = 0;
uint64_t o = 0;
if (!conv(b, indirectBuffer) || //
!conv(o, indirectOffset)) {
@ -172,7 +172,7 @@ namespace wgpu::binding {
Converter conv(env);
wgpu::Buffer b{};
uint32_t o = 0;
uint64_t o = 0;
if (!conv(b, indirectBuffer) || //
!conv(o, indirectOffset)) {

View File

@ -218,7 +218,7 @@ namespace wgpu::binding {
Converter conv(env);
wgpu::Buffer b{};
uint32_t o = 0;
uint64_t o = 0;
if (!conv(b, indirectBuffer) || //
!conv(o, indirectOffset)) {
@ -234,7 +234,7 @@ namespace wgpu::binding {
Converter conv(env);
wgpu::Buffer b{};
uint32_t o = 0;
uint64_t o = 0;
if (!conv(b, indirectBuffer) || //
!conv(o, indirectOffset)) {

View File

@ -69,6 +69,40 @@ namespace wgpu::interop {
template <typename T>
using FrozenArray = std::vector<T>;
// A wrapper class for integers that's as transparent as possible and is used to distinguish
// that the type is tagged with the [Clamp] WebIDL attribute.
template <typename T>
struct ClampedInteger {
static_assert(std::is_integral_v<T>);
using IntegerType = T;
ClampedInteger() : value(0) {
}
ClampedInteger(T value) : value(value) {
}
operator T() const {
return value;
}
T value;
};
// A wrapper class for integers that's as transparent as possible and is used to distinguish
// that the type is tagged with the [EnforceRange] WebIDL attribute.
template <typename T>
struct EnforceRangeInteger {
static_assert(std::is_integral_v<T>);
using IntegerType = T;
EnforceRangeInteger() : value(0) {
}
EnforceRangeInteger(T value) : value(value) {
}
operator T() const {
return value;
}
T value;
};
////////////////////////////////////////////////////////////////////////////////
// Result
////////////////////////////////////////////////////////////////////////////////
@ -447,6 +481,75 @@ namespace wgpu::interop {
static Napi::Value ToJS(Napi::Env, double);
};
// [Clamp]ed integers must convert values outside of the integer range by clamping them.
template <typename T>
class Converter<ClampedInteger<T>> {
public:
static Result FromJS(Napi::Env env, Napi::Value value, ClampedInteger<T>& out) {
double doubleValue;
Result res = Converter<double>::FromJS(env, value, doubleValue);
if (!res) {
return res;
}
// Check for clamping first.
constexpr T kMin = std::numeric_limits<T>::min();
constexpr T kMax = std::numeric_limits<T>::max();
if (doubleValue < kMin) {
out = kMin;
return Success;
}
if (doubleValue > kMax) {
out = kMax;
return Success;
}
// Yay, no clamping! We can convert the integer type as usual.
T correctValue;
res = Converter<T>::FromJS(env, value, correctValue);
if (!res) {
return res;
}
out = correctValue;
return Success;
}
static Napi::Value ToJS(Napi::Env env, const ClampedInteger<T>& value) {
return Converter<T>::ToJS(env, value.value);
}
};
// [EnforceRange] integers cause a TypeError when converted from out of range values
template <typename T>
class Converter<EnforceRangeInteger<T>> {
public:
static Result FromJS(Napi::Env env, Napi::Value value, EnforceRangeInteger<T>& out) {
double doubleValue;
Result res = Converter<double>::FromJS(env, value, doubleValue);
if (!res) {
return res;
}
// Check for out of range and throw a type error.
constexpr T kMin = std::numeric_limits<T>::min();
constexpr T kMax = std::numeric_limits<T>::max();
if (!(kMin <= doubleValue && doubleValue <= kMax)) {
return Error("Values are out of the range of that integer.");
}
// Yay, no error! We can convert the integer type as usual.
T correctValue;
res = Converter<T>::FromJS(env, value, correctValue);
if (!res) {
return res;
}
out = correctValue;
return Success;
}
static Napi::Value ToJS(Napi::Env env, const EnforceRangeInteger<T>& value) {
return Converter<T>::ToJS(env, value.value);
}
};
template <>
class Converter<UndefinedType> {
public:

View File

@ -172,7 +172,7 @@ Wrappers* Wrappers::instance = nullptr;
if (res) {
return ToJS(info.Env(), impl->has(info.Env(), std::get<0>(args)));
}
Napi::Error::New(info.Env(), res.error).ThrowAsJavaScriptException();
Napi::TypeError::New(info.Env(), res.error).ThrowAsJavaScriptException();
return {};
}
Napi::Value keys(const Napi::CallbackInfo& info) {
@ -217,7 +217,7 @@ Wrappers* Wrappers::instance = nullptr;
error = {{if $overloaded}}"\noverload {{$overload_idx}} failed to match:\n" + {{end}}res.error;
}
{{- end}}
Napi::Error::New(info.Env(), "no overload matched for {{$m.Name}}:\n" + error).ThrowAsJavaScriptException();
Napi::TypeError::New(info.Env(), "no overload matched for {{$m.Name}}:\n" + error).ThrowAsJavaScriptException();
return {};
}
{{- end}}
@ -235,7 +235,7 @@ Wrappers* Wrappers::instance = nullptr;
impl->set{{Title $a.Name}}(info.Env(), std::move(v));
} else {
res = res.Append("invalid value to {{$a.Name}}");
Napi::Error::New(info.Env(), res.error).ThrowAsJavaScriptException();
Napi::TypeError::New(info.Env(), res.error).ThrowAsJavaScriptException();
}
}
{{- end}}

View File

@ -146,7 +146,11 @@ public:
--------------------------------------------------------------------------------
*/ -}}
{{- define "Typedef"}}
{{- if HasAnnotation $ "EnforceRange"}}
using {{$.Name}} = EnforceRangeInteger<{{template "Type" $.Type}}>;
{{- else}}
using {{$.Name}} = {{template "Type" $.Type}};
{{- end}}
{{end}}

View File

@ -39,6 +39,8 @@ See:
{{- if IsUndefinedType $}}void
{{- else if IsTypeName $}}
{{- if eq $.Name "boolean" }}bool
{{- else if eq $.Name "short" }}int16_t
{{- else if eq $.Name "unsigned short" }}uint16_t
{{- else if eq $.Name "long" }}int32_t
{{- else if eq $.Name "unsigned long" }}uint32_t
{{- else if eq $.Name "long long" }}int64_t
@ -66,13 +68,29 @@ See:
--------------------------------------------------------------------------------
*/ -}}
{{- define "AttributeType" -}}
{{- if $.Required }}{{template "Type" $.Type}}
{{- else if $.Init }}{{template "Type" $.Type}}
{{- else }}std::optional<{{template "Type" $.Type}}>
{{- if $.Required }}{{template "AttributeClampHelper" $}}
{{- else if $.Init }}{{template "AttributeClampHelper" $}}
{{- else }}std::optional<{{template "AttributeClampHelper" $}}>
{{- end}}
{{- end }}
{{- /*
A helper for AttributeType that wraps integer types if necessary for WebIDL attributes.
Note that [Clamp] and [EnforceRange] are supposed to be an annotation on the type and not
the attribute, but webidlparser doesn't parse this correctly.
*/ -}}
{{- define "AttributeClampHelper" -}}
{{- if HasAnnotation $ "Clamp" }}
ClampedInteger<{{template "Type" $.Type}}>
{{- else if HasAnnotation $ "EnforceRange" }}
EnforceRangeInteger<{{template "Type" $.Type}}>
{{- else}}
{{template "Type" $.Type}}
{{- end }}
{{- end }}
{{- /*
--------------------------------------------------------------------------------
-- Literal generates a C++ literal value using the following arguments:

View File

@ -519,12 +519,16 @@ func findAnnotation(list []*ast.Annotation, name string) *ast.Annotation {
func hasAnnotation(obj interface{}, name string) bool {
switch obj := obj.(type) {
case *ast.Member:
return findAnnotation(obj.Annotations, name) != nil
case *ast.Interface:
return findAnnotation(obj.Annotations, name) != nil
case *ast.Member:
return findAnnotation(obj.Annotations, name) != nil
case *ast.Namespace:
return findAnnotation(obj.Annotations, name) != nil
case *ast.Parameter:
return findAnnotation(obj.Annotations, name) != nil
case *ast.Typedef:
return findAnnotation(obj.Annotations, name) != nil || findAnnotation(obj.TypeAnnotations, name) != nil
}
panic("Unhandled AST node type in hasAnnotation")
}