1{{/* 2 Copyright 2021 The Dawn Authors 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15*/}} 16 17{{- /* 18-------------------------------------------------------------------------------- 19Template file for use with src/dawn_node/tools/cmd/idlgen/main.go to generate 20the WebGPU.cpp source file. 21 22See: 23* https://github.com/ben-clayton/webidlparser/blob/main/ast/ast.go for the AST 24 types used by this template 25* src/dawn_node/tools/cmd/idlgen/main.go for additional structures and functions 26 used by this template 27* https://golang.org/pkg/text/template/ for documentation on the template syntax 28-------------------------------------------------------------------------------- 29*/ -}} 30 31{{- Include "WebGPUCommon.tmpl" -}} 32 33#include "src/dawn_node/interop/WebGPU.h" 34 35#include <unordered_map> 36 37#include "src/dawn_node/utils/Debug.h" 38 39namespace wgpu { 40namespace interop { 41 42namespace { 43 44{{template "Wrappers" $}} 45 46} // namespace 47 48{{ range $ := .Declarations}} 49{{- if IsDictionary $}}{{template "Dictionary" $}} 50{{- else if IsInterface $}}{{template "Interface" $}} 51{{- else if IsEnum $}}{{template "Enum" $}} 52{{- end}} 53{{- end}} 54 55 56void Initialize(Napi::Env env) { 57 auto* wrapper = Wrappers::Init(env); 58 auto global = env.Global(); 59{{ range $ := .Declarations}} 60{{- if IsInterfaceOrNamespace $}} 61 global.Set(Napi::String::New(env, "{{$.Name}}"), wrapper->{{$.Name}}_ctor.Value()); 62{{- end}} 63{{- end}} 64} 65 66} // namespace interop 67} // namespace wgpu 68 69 70{{- /* 71-------------------------------------------------------------------------------- 72-- Wrappers emits the C++ 'Wrappers' class, which holds all the interface and 73-- namespace interop wrapper classes. 74-------------------------------------------------------------------------------- 75*/ -}} 76{{- define "Wrappers"}} 77// Wrappers holds all the Napi class constructors, and Napi::ObjectWrap type 78// declarations, for each of the WebIDL interface and namespace types. 79class Wrappers { 80 Wrappers(Napi::Env env) { 81{{- range $ := .Declarations}} 82{{- if IsInterfaceOrNamespace $}} 83 {{$.Name}}_ctor = Napi::Persistent(W{{$.Name}}::Class(env)); 84{{- end}} 85{{- end}} 86 } 87 88 static Wrappers* instance; 89 90public: 91{{- range $ := .Declarations}} 92{{- if IsInterfaceOrNamespace $}}{{template "Wrapper" $}} 93{{- end}} 94{{- end}} 95 96 // Allocates and constructs the Wrappers instance 97 static Wrappers* Init(Napi::Env env) { 98 instance = new Wrappers(env); 99 return instance; 100 } 101 102 // Destructs and frees the Wrappers instance 103 static void Term(Napi::Env env) { 104 delete instance; 105 instance = nullptr; 106 } 107 108 static Wrappers* For(Napi::Env env) { 109 // Currently Napi only actually supports a single Env, so there's no point 110 // maintaining a map of Env to Wrapper. Note: This might not always be true. 111 return instance; 112 } 113 114{{ range $ := .Declarations}} 115{{- if IsInterfaceOrNamespace $}} 116 Napi::FunctionReference {{$.Name}}_ctor; 117{{- end}} 118{{- end}} 119}; 120 121Wrappers* Wrappers::instance = nullptr; 122{{- end}} 123 124 125{{- /* 126-------------------------------------------------------------------------------- 127-- Wrapper emits the C++ wrapper class for the given ast.Interface or 128-- ast.Namespace. 129-- This wrapper class inherits from Napi::ObjectWrap, which binds the lifetime 130-- of the JavaScript object to the lifetime of the wrapper class instance. 131-- If the wrapper is for an interface, the wrapper object holds a unique_ptr to 132-- the interface implementation, and delegates all exposed method calls on to 133-- the implementation. 134-- See: https://github.com/nodejs/node-addon-api/blob/main/doc/object_wrap.md 135-------------------------------------------------------------------------------- 136*/ -}} 137{{- define "Wrapper"}} 138 struct W{{$.Name}} : public Napi::ObjectWrap<W{{$.Name}}> { 139{{- if IsInterface $}} 140 std::unique_ptr<{{$.Name}}> impl; 141{{- end}} 142 static Napi::Function Class(Napi::Env env) { 143 return DefineClass(env, "{{$.Name}}", { 144{{ if $s := SetlikeOf $}} 145 InstanceMethod("has", &W{{$.Name}}::has), 146 InstanceMethod("keys", &W{{$.Name}}::keys), 147{{- end}} 148{{- range $m := MethodsOf $}} 149 InstanceMethod("{{$m.Name}}", &W{{$.Name}}::{{$m.Name}}), 150{{- end}} 151{{- range $a := AttributesOf $}} 152 InstanceAccessor("{{$a.Name}}", &W{{$.Name}}::get{{Title $a.Name}}, 153{{- if $a.Readonly}} nullptr{{else}} &W{{$.Name}}::set{{Title $a.Name}}{{end -}} 154 ), 155{{- end}} 156{{- range $c := ConstantsOf $}} 157 StaticValue("{{$c.Name}}", ToJS(env, {{$.Name}}::{{$c.Name}}), napi_default_jsproperty), 158{{- end}} 159 }); 160 } 161 162 W{{$.Name}}(const Napi::CallbackInfo& info) : ObjectWrap(info) {} 163 164{{ if $s := SetlikeOf $}} 165 Napi::Value has(const Napi::CallbackInfo& info) { 166 std::tuple<{{template "Type" $s.Elem}}> args; 167 auto res = FromJS(info, args); 168 if (res) { 169 return ToJS(info.Env(), impl->has(info.Env(), std::get<0>(args))); 170 } 171 Napi::Error::New(info.Env(), res.error).ThrowAsJavaScriptException(); 172 return {}; 173 } 174 Napi::Value keys(const Napi::CallbackInfo& info) { 175 return ToJS(info.Env(), impl->keys(info.Env())); 176 } 177{{- end}} 178{{- range $m := MethodsOf $}} 179 Napi::Value {{$m.Name}}(const Napi::CallbackInfo& info) { 180 std::string error; 181{{- range $overload_idx, $o := $m.Overloads}} 182{{- $overloaded := gt (len $m.Overloads) 1}} 183 { {{if $overloaded}}// Overload {{$overload_idx}}{{end}} 184 std::tuple< 185{{- range $i, $p := $o.Parameters}} 186{{- if $i}}, {{end}} 187{{- if $p.Init }}DefaultedParameter<{{template "Type" $p.Type}}> 188{{- else if $p.Optional}}std::optional<{{template "Type" $p.Type}}> 189{{- else }}{{template "Type" $p.Type}} 190{{- end}} 191{{- end}}> args; 192 193{{- range $i, $p := $o.Parameters}} 194{{- if $p.Init}} 195 std::get<{{$i}} /* {{$p.Name}} */>(args).default_value = {{Eval "Literal" "Value" $p.Init "Type" $p.Type}}; 196{{- end}} 197{{- end}} 198 199 auto res = FromJS(info, args); 200 if (res) { 201 {{/* indent */}}INTEROP_LOG( 202{{- range $i, $p := $o.Parameters}} 203{{- if $i}}, ", {{$p.Name}}: "{{else}}"{{$p.Name}}: "{{end}}, std::get<{{$i}}>(args) 204{{- end}}); 205 {{/* indent */}} 206{{- if not (IsUndefinedType $o.Type) }}auto result = {{end -}} 207 impl->{{$o.Name}}(info.Env(){{range $i, $_ := $o.Parameters}}, std::get<{{$i}}>(args){{end}}); 208 {{/* indent */ -}} 209{{- if IsUndefinedType $o.Type}}return info.Env().Null(); 210{{- else }}return ToJS(info.Env(), result); 211{{- end }} 212 } 213 error = {{if $overloaded}}"\noverload {{$overload_idx}} failed to match:\n" + {{end}}res.error; 214 } 215{{- end}} 216 Napi::Error::New(info.Env(), "no overload matched for {{$m.Name}}:\n" + error).ThrowAsJavaScriptException(); 217 return {}; 218 } 219{{- end}} 220 221{{- range $a := AttributesOf $}} 222 Napi::Value get{{Title $a.Name}}(const Napi::CallbackInfo& info) { 223 return ToJS(info.Env(), impl->get{{Title $a.Name}}(info.Env())); 224 } 225{{- if not $a.Readonly}} 226 void set{{Title $a.Name}}(const Napi::CallbackInfo& info, const Napi::Value& value) { 227 {{template "Type" $a.Type}} v{}; 228 auto res = FromJS(info.Env(), value, v); 229 if (res) { 230 impl->set{{Title $a.Name}}(info.Env(), std::move(v)); 231 } else { 232 res = res.Append("invalid value to {{$a.Name}}"); 233 Napi::Error::New(info.Env(), res.error).ThrowAsJavaScriptException(); 234 } 235 } 236{{- end }} 237{{- end}} 238 }; 239{{end}} 240 241 242{{- /* 243-------------------------------------------------------------------------------- 244-- Dictionary emits the C++ method implementations and associated functions of 245-- the interop type that defines the given ast.Dictionary 246-------------------------------------------------------------------------------- 247*/ -}} 248{{- define "Dictionary"}} 249Result Converter<{{$.Name}}>::FromJS(Napi::Env env, Napi::Value value, {{$.Name}}& out) { 250 auto object = value.ToObject(); 251 Result res; 252{{- template "DictionaryMembersFromJS" $}}; 253 return Success; 254} 255 256Napi::Value Converter<{{$.Name}}>::ToJS(Napi::Env env, {{$.Name}} value) { 257 auto object = Napi::Object::New(env); 258{{- template "DictionaryMembersToJS" $}} 259 return object; 260} 261 262std::ostream& operator<<(std::ostream& o, const {{$.Name}}& dict) { 263 o << "{{$.Name}} {"; 264{{- range $i, $m := $.Members}} 265 o << {{if $i}}", "{{else}}" "{{end}} << "{{$m.Name}}: "; 266 utils::Write(o, dict.{{$m.Name}}); 267{{- end }} 268 o << "}" << std::endl; 269 return o; 270} 271{{ end}} 272 273 274{{- /* 275-------------------------------------------------------------------------------- 276-- DictionaryMembersFromJS emits the C++ logic to convert each of the 277-- dictionary ast.Member fields from JavaScript to C++. Each call to ToJS() is 278-- emitted as a separate statement, and requires a 'Result res' local to be 279-- declared 280-------------------------------------------------------------------------------- 281*/ -}} 282{{- define "DictionaryMembersFromJS"}} 283{{- if $.Inherits}}{{template "DictionaryMembersFromJS" (Lookup $.Inherits)}}{{end}} 284{{- range $i, $m := $.Members}} 285 {{/* indent */}} 286{{- if $m.Init }}res = interop::FromJSOptional(env, object.Get("{{$m.Name}}"), out.{{$m.Name}}); 287{{- else }}res = interop::FromJS(env, object.Get("{{$m.Name}}"), out.{{$m.Name}}); 288{{- end }} 289 if (!res) { 290 return res.Append("while converting member '{{$m.Name}}'"); 291 } 292{{- end}} 293{{- end}} 294 295 296{{- /* 297-------------------------------------------------------------------------------- 298-- DictionaryMembersToJS emits the C++ logic to convert each of the 299-- dictionary ast.Member fields to JavaScript from C++. Each call to ToJS() is 300-- emitted as a separate statement 301-------------------------------------------------------------------------------- 302*/ -}} 303{{- define "DictionaryMembersToJS"}} 304{{- if $.Inherits}}{{template "DictionaryMembersToJS" (Lookup $.Inherits)}}{{end}} 305{{- range $m := $.Members}} 306 object.Set(Napi::String::New(env, "{{$m.Name}}"), interop::ToJS(env, value.{{$m.Name}})); 307{{- end}} 308{{- end}} 309 310 311{{- /* 312-------------------------------------------------------------------------------- 313-- Interface emits the C++ method implementations that define the given 314-- ast.Interface. 315-- Note: Most of the actual binding logic lives in the interface wrapper class. 316-------------------------------------------------------------------------------- 317*/ -}} 318{{- define "Interface"}} 319{{$.Name}}::{{$.Name}}() = default; 320 321{{$.Name}}* {{$.Name}}::Unwrap(Napi::Object object) { 322 auto* wrappers = Wrappers::For(object.Env()); 323 if (!object.InstanceOf(wrappers->{{$.Name}}_ctor.Value())) { 324 return nullptr; 325 } 326 return Wrappers::W{{$.Name}}::Unwrap(object)->impl.get(); 327} 328 329Interface<{{$.Name}}> {{$.Name}}::Bind(Napi::Env env, std::unique_ptr<{{$.Name}}>&& impl) { 330 auto* wrappers = Wrappers::For(env); 331 auto object = wrappers->{{$.Name}}_ctor.New({}); 332 auto* wrapper = Wrappers::W{{$.Name}}::Unwrap(object); 333 wrapper->impl = std::move(impl); 334 return Interface<{{$.Name}}>(object); 335} 336 337{{$.Name}}::~{{$.Name}}() = default; 338{{ end}} 339 340 341{{- /* 342-------------------------------------------------------------------------------- 343-- Enum emits the C++ associated functions of the interop type that defines the 344-- given ast.Enum 345-------------------------------------------------------------------------------- 346*/ -}} 347{{- define "Enum"}} 348bool Converter<{{$.Name}}>::FromString(std::string str, {{$.Name}}& out) { 349{{- range $e := $.Values}} 350 if (str == {{$e.Value}}) { 351 out = {{$.Name}}::{{EnumEntryName $e.Value}}; 352 return true; 353 } 354{{- end}} 355 return false; 356} 357 358const char* Converter<{{$.Name}}>::ToString({{$.Name}} value) { 359 switch (value) { 360{{- range $e := $.Values}} 361 case {{$.Name}}::{{EnumEntryName $e.Value}}: 362 return {{$e.Value}}; 363{{- end}} 364 } 365 return nullptr; 366} 367 368Result Converter<{{$.Name}}>::FromJS(Napi::Env env, Napi::Value value, {{$.Name}}& out) { 369 std::string str = value.ToString(); 370 if (FromString(str, out)) { 371 return Success; 372 } 373 return Error(str + " is not a valid enum value of {{$.Name}}"); 374} 375 376Napi::Value Converter<{{$.Name}}>::ToJS(Napi::Env env, {{$.Name}} value) { 377 switch (value) { 378{{- range $e := $.Values}} 379 case {{$.Name}}::{{EnumEntryName $e.Value}}: 380 return Napi::String::New(env, {{$e.Value}}); 381{{- end}} 382 } 383 return env.Undefined(); 384} 385 386std::ostream& operator<<(std::ostream& o, {{$.Name}} value) { 387 if (auto* s = Converter<{{$.Name}}>::ToString(value)) { 388 return o << s; 389 } 390 return o << "undefined<{{$.Name}}>"; 391} 392 393{{end}} 394