Rietveld Code Review Tool
Help | Bug tracker | Discussion group | Source code

Side by Side Diff: compiled/bindings.ipp

Issue 29333474: Issue 4125 - [emscripten] Convert filter classes to C++ (Closed)
Patch Set: Properly determine ref_counted offset instead of assuming that it is zero Created March 14, 2017, 10:18 a.m.
Left:
Right:
Use n/p to move between diff chunks; N/P to move between comments.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 #pragma once
2
3 #include <cstdint>
4 #include <cstdio>
5 #include <cstdlib>
6 #include <exception>
7 #include <map>
8 #include <string>
9 #include <type_traits>
10 #include <utility>
11 #include <vector>
12
13 #include <emscripten.h>
14
15 #include "String.h"
16 #include "intrusive_ptr.h"
17
18 namespace bindings_internal
19 {
20 typedef void* TYPEID;
21
22 enum class TypeCategory
23 {
24 UNKNOWN,
25 VOID,
26 INT,
27 DEPENDENT_STRING,
28 OWNED_STRING,
29 STRING_REF,
30 CLASS_PTR
31 };
32
33 template<typename T>
34 struct TypeInfo
35 {
36 /*
37 * Since TypeInfo is a templated type, in practice the compiler will define
38 * a new type for each possible template parameter value. We use that fact
39 * to generate type identifiers: each of these TypeInfo types has a
40 * different s_typeIDHelper member, so we use a pointer to that static
41 * variable as a type identifier - it will be different for each template
42 * parameter.
43 */
44 static char s_typeIDHelper;
45 constexpr operator TYPEID() const
46 {
47 return &s_typeIDHelper;
48 }
49
50 constexpr operator TypeCategory() const
51 {
52 if (std::is_void<T>())
53 return TypeCategory::VOID;
54
55 if (std::is_integral<T>() || std::is_enum<T>())
56 return TypeCategory::INT;
57
58 if (std::is_same<DependentString, T>() || std::is_same<const DependentStri ng, T>())
59 return TypeCategory::DEPENDENT_STRING;
60
61 if (std::is_same<OwnedString, T>() || std::is_same<const OwnedString, T>() )
62 return TypeCategory::OWNED_STRING;
63
64 if (std::is_same<String&, T>() || std::is_same<const String&, T>() ||
65 std::is_same<DependentString&, T>())
66 {
67 return TypeCategory::STRING_REF;
68 }
69
70 if (std::is_pointer<T>() && std::is_class<typename std::remove_pointer<T>: :type>())
71 return TypeCategory::CLASS_PTR;
72
73 return TypeCategory::UNKNOWN;
74 }
75
76 constexpr TYPEID pointer_type() const
77 {
78 if (std::is_pointer<T>())
79 return TypeInfo<typename std::remove_pointer<T>::type>();
80 else
81 return nullptr;
82 }
83 };
84
85 template<typename T>
86 char TypeInfo<T>::s_typeIDHelper;
87
88 struct FunctionInfo
89 {
90 TypeCategory returnType;
91 TYPEID pointerType;
92 std::vector<TypeCategory> args;
93 bool instance_function;
94 int effectiveArgs;
95 TypeCategory effectiveReturnType;
96 char name[1024];
97
98 FunctionInfo()
99 {
100 name[0] = '\0';
101 }
102
103 FunctionInfo(TypeCategory returnType, TYPEID pointerType,
104 std::initializer_list<TypeCategory> argTypes, bool instance_function,
105 void* function)
106 : returnType(returnType), pointerType(pointerType),
107 instance_function(instance_function)
108 {
109 name[0] = '\0';
110
111 // The function parameter is a pointer to the function pointer.
112 // Emscripten's "function pointers" are actually integers indicating the
113 // position in the call table. 0 represents nullptr.
114 if (!*reinterpret_cast<int*>(function))
115 return;
116
117 for (const auto& item : argTypes)
118 {
119 if (item != TypeCategory::INT && item != TypeCategory::STRING_REF &&
120 item != TypeCategory::CLASS_PTR)
121 {
122 throw std::runtime_error("Unexpected function argument type");
123 }
124 args.push_back(item);
125 }
126
127 if (returnType != TypeCategory::VOID && returnType != TypeCategory::INT &&
128 returnType != TypeCategory::DEPENDENT_STRING &&
129 returnType != TypeCategory::OWNED_STRING &&
130 returnType != TypeCategory::STRING_REF &&
131 returnType != TypeCategory::CLASS_PTR)
132 {
133 throw std::runtime_error("Unexpected function return type");
134 }
135
136 effectiveArgs = args.size();
137 effectiveReturnType = returnType;
138 if (instance_function)
139 effectiveArgs++;
140
141 if (returnType == TypeCategory::DEPENDENT_STRING ||
142 returnType == TypeCategory::OWNED_STRING)
143 {
144 effectiveArgs++;
145 effectiveReturnType = TypeCategory::VOID;
146 }
147
148 get_function_name(function, effectiveArgs,
149 effectiveReturnType == TypeCategory::VOID);
150 }
151
152 template<typename ReturnType, typename... Args>
153 FunctionInfo(ReturnType (*function)(Args...))
154 : FunctionInfo(TypeInfo<ReturnType>(),
155 TypeInfo<ReturnType>().pointer_type(), { TypeInfo<Args>()... }, false,
156 &function)
157 {
158 }
159
160 template<typename ClassType, typename ReturnType, typename... Args>
161 FunctionInfo(ReturnType (ClassType::*function)(Args...))
162 : FunctionInfo(TypeInfo<ReturnType>(),
163 TypeInfo<ReturnType>().pointer_type(), { TypeInfo<Args>()... }, true,
164 &function)
165 {
166 }
167
168 template<typename ClassType, typename ReturnType, typename... Args>
169 FunctionInfo(ReturnType (ClassType::*function)(Args...) const)
170 : FunctionInfo(TypeInfo<ReturnType>(),
171 TypeInfo<ReturnType>().pointer_type(), { TypeInfo<Args>()... }, true,
172 &function)
173 {
174 }
175
176 bool empty() const
177 {
178 return name[0] == '\0';
179 }
180
181 void get_function_name(void* ptr, int numArgs, bool voidResult)
182 {
183 // This is a hack, C++ won't let us get the mangled function name.
184 // JavaScript is more dynamic so we pass the pointer to our function
185 // there. With that and the function signature we can call the function -
186 // with a full stack so that we will cause it to abort. Sometimes the
187 // function we are calling will also be missing from the build. The result
188 // is the same: abort() is called which in turn calls stackTrace(). By
189 // replacing stackTrace() we get access to the call stack and search it
190 // for the name of our function.
191
192 EM_ASM_ARGS({
193 var signature = $3 ? "v" : "i";
194 var args = [];
195 for (var i = 0; i < $2; i++)
196 {
197 signature += "i";
198 args.push(0);
199 }
200
201 var oldPrint = Module.print;
202 var oldPrintErr = Module.printErr;
203 var oldStackTrace = stackTrace;
204 var sp = Runtime.stackSave();
205 Module.print = function(){};
206 Module.printErr = function(){};
207 stackTrace = function()
208 {
209 var stack = [];
210 for (var f = arguments.callee.caller; f; f = f.caller)
211 {
212 if (f.name)
213 {
214 if (f.name.indexOf("dynCall") == 0)
215 break;
216 else
217 stack.push(f.name);
218 }
219 }
220
221 result = stack[stack.length - 1];
222 if (result && result.indexOf("__wrapper") >= 0)
223 result = stack[stack.length - 2];
224 throw result;
225 };
226
227 Runtime.stackRestore(STACK_MAX);
228
229 try
230 {
231 Runtime.dynCall(signature, HEAP32[$1 >> 2], args);
232 }
233 catch(e)
234 {
235 Module.stringToAscii(e, $0);
236 }
237 finally
238 {
239 Runtime.stackRestore(sp);
240 Module.print = oldPrint;
241 Module.printErr = oldPrintErr;
242 stackTrace = oldStackTrace;
243 }
244 }, name, ptr, numArgs, voidResult);
245 }
246 };
247
248 class NoBaseClass
249 {
250 };
251
252 struct PropertyInfo
253 {
254 std::string name;
255 FunctionInfo getter;
256 FunctionInfo setter;
257 std::string jsValue;
258 };
259
260 struct MethodInfo
261 {
262 std::string name;
263 FunctionInfo call;
264 };
265
266 struct DifferentiatorInfo
267 {
268 size_t offset;
269 std::vector<std::pair<int, std::string>> mapping;
270 };
271
272 struct ClassInfo
273 {
274 ClassInfo* baseClass;
275 std::string name;
276 std::vector<PropertyInfo> properties;
277 std::vector<MethodInfo> methods;
278 std::vector<FunctionInfo> initializers;
279 DifferentiatorInfo subclass_differentiator;
280 int ref_counted_offset;
sergei 2017/03/20 17:07:18 Can we use ptrdiff_t for ref_counted_offset?
Wladimir Palant 2017/03/21 10:10:15 Done.
281 };
282
283 std::map<TYPEID, ClassInfo> classes;
284
285 void register_class(const char* name, TYPEID classID, TYPEID baseClassID,
286 int ref_counted_offset)
287 {
288 auto it = classes.find(classID);
289 if (it != classes.end())
290 throw std::runtime_error(std::string("Duplicate definition for class ") + name);
291
292 ClassInfo* baseClass = nullptr;
293 if (baseClassID != TypeInfo<NoBaseClass>())
294 {
295 it = classes.find(baseClassID);
296 if (it == classes.end())
297 throw std::runtime_error(std::string("Unknown base class defined for cla ss ") + name);
298 baseClass = &(it->second);
299 }
300
301 ClassInfo classInfo;
302 classInfo.baseClass = baseClass;
303 classInfo.name = name;
304 classInfo.subclass_differentiator.offset = SIZE_MAX;
305 classInfo.ref_counted_offset = ref_counted_offset;
306 classes[classID] = classInfo;
307 }
308
309 void register_property(TYPEID classID, const char* name,
310 const FunctionInfo& getter, const FunctionInfo& setter,
311 const char* jsValue = "")
312 {
313 auto it = classes.find(classID);
314 if (it == classes.end())
315 throw std::runtime_error(std::string("Property defined on unknown class: " ) + name);
316
317 PropertyInfo propertyInfo;
318 propertyInfo.name = name;
319 propertyInfo.getter = getter;
320 propertyInfo.setter = setter;
321 propertyInfo.jsValue = jsValue;
322 it->second.properties.push_back(propertyInfo);
323 }
324
325 void register_method(TYPEID classID, const char* name,
326 const FunctionInfo& call)
327 {
328 auto it = classes.find(classID);
329 if (it == classes.end())
330 throw std::runtime_error(std::string("Method defined on unknown class: ") + name);
331
332 MethodInfo methodInfo;
333 methodInfo.name = name;
334 methodInfo.call = call;
335 it->second.methods.push_back(methodInfo);
336 }
337
338 void register_initializer(TYPEID classID, const FunctionInfo& call)
339 {
340 auto it = classes.find(classID);
341 if (it == classes.end())
342 throw std::runtime_error("Initializer defined on unknown class");
343
344 it->second.initializers.push_back(call);
345 }
346
347 void register_differentiator(TYPEID classID, size_t offset,
348 std::vector<std::pair<int, std::string>>& mapping)
349 {
350 auto it = classes.find(classID);
351 if (it == classes.end())
352 throw std::runtime_error("Subclass differentiator defined on unknown class ");
353
354 if (it->second.subclass_differentiator.offset != SIZE_MAX)
355 throw std::runtime_error("More than one subclass differentiator defined fo r class " + it->second.name);
356
357 DifferentiatorInfo differentiatorInfo;
358 differentiatorInfo.offset = offset;
359 differentiatorInfo.mapping = mapping;
360 it->second.subclass_differentiator = differentiatorInfo;
361 }
362
363 const std::string generateCall(const FunctionInfo& call,
364 std::vector<std::string>& params)
365 {
366 if (call.returnType == TypeCategory::DEPENDENT_STRING ||
367 call.returnType == TypeCategory::OWNED_STRING)
368 {
369 params.insert(params.begin(), "string");
370 }
371
372 std::string call_str(call.name);
373 call_str += "(";
374 for (int i = 0; i < params.size(); i++)
375 {
376 if (i > 0)
377 call_str += ", ";
378 call_str += params[i];
379 }
380 call_str += ")";
381
382 if (call.returnType == TypeCategory::VOID)
383 return " " + call_str + ";\n";
384 else if (call.returnType == TypeCategory::INT)
385 return " var result = " + call_str + ";\n";
386 else if (call.returnType == TypeCategory::DEPENDENT_STRING ||
387 call.returnType == TypeCategory::OWNED_STRING)
388 {
389 std::string result;
390 result += " var string = createString();\n";
391 result += " " + call_str + ";\n";
392 result += " var result = readString(string);\n";
393 if (call.returnType == TypeCategory::OWNED_STRING)
394 result += " Module._DestroyString(string);\n";
395 return result;
396 }
397 else if (call.returnType == TypeCategory::STRING_REF)
398 {
399 return " var result = readString(" + call_str + ");\n";
400 }
401 else if (call.returnType == TypeCategory::CLASS_PTR)
402 {
403 std::string result;
404 result += " var result = " + call_str + ";\n";
405 result += " if (result)\n";
406 result += " {\n";
407
408 auto it = classes.find(call.pointerType);
409 if (it == classes.end())
410 throw std::runtime_error("Function " + std::string(call.name) + " return s pointer to unknown class");
411
412 const ClassInfo& cls = it->second;
413 auto offset = cls.subclass_differentiator.offset;
414 if (offset == SIZE_MAX)
415 result += " result = " + cls.name + "(result);\n";
416 else
417 {
418 result += " var type = HEAP32[result + " + std::to_string(offset)+ " >> 2];\n";
419 result += " if (type in " + cls.name + "_mapping)\n";
420 result += " result = new (exports[" + cls.name + "_mapping[type]])( result);\n";
421 result += " else\n";
422 result += " throw new Error('Unexpected " + cls.name + " type: ' + type);\n";
423 }
424
425 result += " }\n";
426 return result;
427 }
428 else
429 throw std::runtime_error("Unexpected return type for " + std::string(call. name));
430 }
431
432 const std::string wrapCall(const FunctionInfo& call)
433 {
434 char buffer[20];
435 bool hasStringArgs = false;
436 std::vector<std::string> params;
437 std::string prefix = "function(";
438 for (int i = 0; i < call.args.size(); i++)
439 {
440 sprintf(buffer, "arg%i", i);
441 if (i > 0)
442 prefix += ", ";
443 prefix += buffer;
444
445 if (call.args[i] == TypeCategory::STRING_REF)
446 {
447 hasStringArgs = true;
448 params.push_back(std::string("createString(") + buffer + ")");
449 }
450 else
451 params.push_back(buffer);
452 }
453 prefix += ")\n{\n";
454
455 std::string suffix = "}";
456 if (call.returnType != TypeCategory::VOID)
457 suffix = " return result;\n" + suffix;
458
459 if (call.returnType == TypeCategory::DEPENDENT_STRING ||
460 call.returnType == TypeCategory::OWNED_STRING || hasStringArgs)
461 {
462 prefix += " var sp = Runtime.stackSave();\n";
463 suffix = " Runtime.stackRestore(sp);\n" + suffix;
464 }
465
466 if (call.instance_function)
467 params.insert(params.begin(), "this._pointer");
468
469 return prefix + generateCall(call, params) + suffix;
470 }
471
472 std::string generatePropertyDescriptor(const PropertyInfo& property)
473 {
474 if (!property.jsValue.empty())
475 return "value: " + property.jsValue;
476
477 std::string result("get: " + wrapCall(property.getter));
478 if (!property.setter.empty())
479 result += ", set: " + wrapCall(property.setter);
480 return result;
481 }
482
483 void printHelpers()
484 {
485 printf("var sizeofString = %i;\n", sizeof(String));
486
487 puts(R"(
488 function copyString(str, buffer)
489 {
490 var length = str.length;
491 for (var i = 0, pointer = (buffer >> 1); i < length; i++, pointer++)
492 HEAP16[pointer] = str.charCodeAt(i);
493 return length;
494 }
495
496 function createString(str)
497 {
498 var length = 0;
499 var buffer = 0;
500 if (str)
501 {
502 buffer = Runtime.stackAlloc(str.length * 2);
503 length = copyString(str, buffer);
504 }
505
506 var result = Module.Runtime.stackAlloc(sizeofString);
507 Module._InitString(result, buffer, length);
508 return result;
509 }
510
511 function readString(str)
512 {
513 var length = Module._GetStringLength(str);
514 var pointer = Module._GetStringData(str) >> 1;
515 return String.fromCharCode.apply(String, HEAP16.slice(pointer, pointer + len gth));
516 }
517
518 function createClass(superclass, ref_counted_offset)
519 {
520 var result = function(pointer)
521 {
522 this._pointer = pointer;
523 };
524 if (superclass)
525 result.prototype = Object.create(superclass.prototype);
526 result.prototype.delete = function()
527 {
528 Module._ReleaseRef(this._pointer + ref_counted_offset);
529 };
530 return result;
531 })");
532 }
533
534 void printClass(const ClassInfo& cls)
535 {
536 DifferentiatorInfo differentiator = cls.subclass_differentiator;
537 if (differentiator.offset != SIZE_MAX)
538 {
539 printf("var %s_mapping = \n", cls.name.c_str());
540 puts("{");
541 for (const auto& item : differentiator.mapping)
542 printf(" %i: '%s',\n", item.first, item.second.c_str());
543 puts("};");
544 }
545
546 printf("exports.%s = createClass(%s, %i);\n", cls.name.c_str(),
547 (cls.baseClass ? ("exports." + cls.baseClass->name).c_str() : "null"),
548 cls.ref_counted_offset);
549
550 for (const auto& item : cls.properties)
551 {
552 printf("Object.defineProperty(exports.%s.prototype, '%s', {%s});\n",
553 cls.name.c_str(), item.name.c_str(),
554 generatePropertyDescriptor(item).c_str());
555 }
556
557 for (const auto& item : cls.methods)
558 {
559 std::string obj("exports." + cls.name);
560 if (item.call.instance_function)
561 obj += ".prototype";
562 printf("%s.%s = %s;\n", obj.c_str(), item.name.c_str(),
563 wrapCall(item.call).c_str());
564 }
565
566 for (const auto& item : cls.initializers)
567 printf("%s()\n", item.name);
568 }
569
570 void printBindings()
571 {
572 printHelpers();
573
574 for (const auto& item : classes)
575 printClass(item.second);
576 }
577 }
578
579 #if defined(PRINT_BINDINGS)
580 // Bindings generation step: collect bindings information and print
581 // corresponding JS code.
582 #define EMSCRIPTEN_BINDINGS \
583 struct BindingsInitializer {\
584 BindingsInitializer();\
585 BindingsInitializer(bool dummy)\
586 {\
587 try\
588 {\
589 BindingsInitializer();\
590 bindings_internal::printBindings();\
591 }\
592 catch (const std::exception& e)\
593 {\
594 EM_ASM_ARGS(\
595 console.error("Error occurred generating JavaScript bindings: " +\
596 Module.AsciiToString($0)), e.what()\
597 );\
598 abort();\
599 }\
600 }\
601 } BindingsInitializer_instance(true);\
602 BindingsInitializer::BindingsInitializer()
603 #else
604 // Actual compilation step: ignore bindings information but define some
605 // exported helper functions necessary for the bindings.
606 #define EMSCRIPTEN_BINDINGS \
607 extern "C"\
608 {\
609 void EMSCRIPTEN_KEEPALIVE InitString(DependentString* str,\
610 String::value_type* data, String::size_type len)\
611 {\
612 /* String is already allocated on stack, we merely need to call*/\
613 /* constructor.*/\
614 new (str) DependentString(data, len);\
615 }\
616 void EMSCRIPTEN_KEEPALIVE DestroyString(OwnedString* str)\
617 {\
618 /* Stack memory will be freed automatically, we need to call*/\
619 /* destructor explicitly however.*/\
620 str->~OwnedString();\
621 }\
622 String::size_type EMSCRIPTEN_KEEPALIVE GetStringLength(\
623 const String& str)\
624 {\
625 return str.length();\
626 }\
627 const String::value_type* EMSCRIPTEN_KEEPALIVE GetStringData(\
628 const String& str)\
629 {\
630 return str.data();\
631 }\
632 void EMSCRIPTEN_KEEPALIVE ReleaseRef(ref_counted* ptr)\
633 {\
634 ptr->ReleaseRef();\
635 }\
636 }\
637 void BindingsInitializer_dummy()
638 #endif
639
640 template<typename ClassType,
641 typename BaseClass = bindings_internal::NoBaseClass,
642 typename std::enable_if<std::is_base_of<ref_counted, ClassType>::value>::typ e* = nullptr>
643 class class_
644 {
645 public:
646 class_(const char* name)
647 {
648 ClassType* ptr = reinterpret_cast<ClassType*>(0x10000000);
649 int ref_counted_offset =
650 reinterpret_cast<int>(dynamic_cast<ref_counted*>(ptr)) -
sergei 2017/03/20 17:07:15 Could we please use static_cast instead of dynamic
Wladimir Palant 2017/03/21 10:10:12 Done.
651 reinterpret_cast<int>(ptr);
652 bindings_internal::register_class(name,
653 bindings_internal::TypeInfo<ClassType>(),
654 bindings_internal::TypeInfo<BaseClass>(),
655 ref_counted_offset
656 );
657 }
658
659 template<typename FieldType>
660 const class_& property(const char* name,
661 FieldType (ClassType::*getter)() const,
662 void (ClassType::*setter)(FieldType) = nullptr) const
663 {
664 bindings_internal::register_property(
665 bindings_internal::TypeInfo<ClassType>(), name, getter, setter);
666 return *this;
667 }
668
669 const class_& class_property(const char* name,
670 const char* jsValue) const
671 {
672 bindings_internal::register_property(
673 bindings_internal::TypeInfo<ClassType>(), name,
674 bindings_internal::FunctionInfo(), bindings_internal::FunctionInfo(),
675 jsValue);
676 return *this;
677 }
678
679 template<typename ReturnType, typename... Args>
680 const class_& function(const char* name, ReturnType (ClassType::*method)(Args. ..)) const
681 {
682 bindings_internal::register_method(
683 bindings_internal::TypeInfo<ClassType>(), name, method);
684 return *this;
685 }
686
687 template<typename ReturnType, typename... Args>
688 const class_& function(const char* name, ReturnType (ClassType::*method)(Args. ..) const) const
689 {
690 bindings_internal::register_method(
691 bindings_internal::TypeInfo<ClassType>(), name, method);
692 return *this;
693 }
694
695 template<typename ReturnType, typename... Args>
696 const class_& class_function(const char* name, ReturnType (*method)(Args...)) const
697 {
698 bindings_internal::register_method(
699 bindings_internal::TypeInfo<ClassType>(), name, method);
700 return *this;
701 }
702
703 const class_& class_initializer(void (*function)()) const
704 {
705 bindings_internal::register_initializer(
706 bindings_internal::TypeInfo<ClassType>(), function);
707 return *this;
708 }
709
710 template<typename ReturnType,
711 typename std::enable_if<std::is_convertible<ReturnType, int32_t>::value>:: type* = nullptr>
712 const class_& subclass_differentiator(ReturnType ClassType::* member,
713 std::initializer_list<std::pair<ReturnType, const char*>> list) const
714 {
715 ClassType* instance = nullptr;
716 size_t offset = (char*)&(instance->*member) - (char*)instance;
717
718 std::vector<std::pair<int, std::string>> mapping;
719 for (const auto& item : list)
720 mapping.emplace_back(item.first, item.second);
721
722 bindings_internal::register_differentiator(
723 bindings_internal::TypeInfo<ClassType>(), offset, mapping);
724 return *this;
725 }
726 };
OLDNEW

Powered by Google App Engine
This is Rietveld