xref: /aosp_15_r20/external/harfbuzz_ng/src/hb-wasm-shape.cc (revision 2d1272b857b1f7575e6e246373e1cb218663db8a)
1 /*
2  * Copyright © 2011  Google, Inc.
3  *
4  *  This is part of HarfBuzz, a text shaping library.
5  *
6  * Permission is hereby granted, without written agreement and without
7  * license or royalty fees, to use, copy, modify, and distribute this
8  * software and its documentation for any purpose, provided that the
9  * above copyright notice and the following two paragraphs appear in
10  * all copies of this software.
11  *
12  * IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE TO ANY PARTY FOR
13  * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
14  * ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN
15  * IF THE COPYRIGHT HOLDER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
16  * DAMAGE.
17  *
18  * THE COPYRIGHT HOLDER SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING,
19  * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
20  * FITNESS FOR A PARTICULAR PURPOSE.  THE SOFTWARE PROVIDED HEREUNDER IS
21  * ON AN "AS IS" BASIS, AND THE COPYRIGHT HOLDER HAS NO OBLIGATION TO
22  * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
23  *
24  * Google Author(s): Behdad Esfahbod
25  */
26 
27 #undef HB_DEBUG_WASM
28 #define HB_DEBUG_WASM 1
29 
30 #include "hb-shaper-impl.hh"
31 
32 #ifdef HAVE_WASM
33 
34 /* Compile wasm-micro-runtime with:
35  *
36  * $ cmake -DWAMR_BUILD_MULTI_MODULE=1 -DWAMR_BUILD_REF_TYPES=1 -DWAMR_BUILD_FAST_JIT=1
37  * $ make
38  *
39  * If you manage to build a wasm shared module successfully and want to use it,
40  * do the following:
41  *
42  *   - Add -DWAMR_BUILD_MULTI_MODULE=1 to your cmake build for wasm-micro-runtime,
43  *
44  *   - Remove the #define HB_WASM_NO_MODULES line below,
45  *
46  *   - Install your shared module with name ending in .wasm in
47  *     $(prefix)/$(libdir)/harfbuzz/wasm/
48  *
49  *   - Build your font's wasm code importing the shared modules with the desired
50  *     name. This can be done eg.: __attribute__((import_module("graphite2")))
51  *     before each symbol in the shared-module's headers.
52  *
53  *   - Try shaping your font and hope for the best...
54  *
55  * I haven't been able to get this to work since emcc's support for shared libraries
56  * requires support from the host that seems to be missing from wasm-micro-runtime?
57  */
58 
59 #include "hb-wasm-api.hh"
60 #include "hb-wasm-api-list.hh"
61 
62 #ifndef HB_WASM_NO_MODULES
63 #define HB_WASM_NO_MODULES
64 #endif
65 
66 
67 #ifndef HB_WASM_NO_MODULES
68 static bool HB_UNUSED
_hb_wasm_module_reader(const char * module_name,uint8_t ** p_buffer,uint32_t * p_size)69 _hb_wasm_module_reader (const char *module_name,
70 		       uint8_t **p_buffer, uint32_t *p_size)
71 {
72   char path[sizeof (HB_WASM_MODULE_DIR) + 64] = HB_WASM_MODULE_DIR "/";
73   strncat (path, module_name, sizeof (path) - sizeof (HB_WASM_MODULE_DIR) - 16);
74   strncat (path, ".wasm", 6);
75 
76   auto *blob = hb_blob_create_from_file (path);
77 
78   unsigned length;
79   auto *data = hb_blob_get_data (blob, &length);
80 
81   *p_buffer = (uint8_t *) hb_malloc (length);
82 
83   if (length && !p_buffer)
84     return false;
85 
86   memcpy (*p_buffer, data, length);
87   *p_size = length;
88 
89   hb_blob_destroy (blob);
90 
91   return true;
92 }
93 
94 static void HB_UNUSED
_hb_wasm_module_destroyer(uint8_t * buffer,uint32_t size)95 _hb_wasm_module_destroyer (uint8_t *buffer, uint32_t size)
96 {
97   hb_free (buffer);
98 }
99 #endif
100 
101 /*
102  * shaper face data
103  */
104 
105 #define HB_WASM_TAG_WASM HB_TAG('W','a','s','m')
106 
107 struct hb_wasm_shape_plan_t {
108   wasm_module_inst_t module_inst;
109   wasm_exec_env_t exec_env;
110   ptr_d(void, wasm_shape_plan);
111 };
112 
113 struct hb_wasm_face_data_t {
114   hb_blob_t *wasm_blob;
115   wasm_module_t wasm_module;
116   mutable hb_atomic_ptr_t<hb_wasm_shape_plan_t> plan;
117 };
118 
119 static bool
_hb_wasm_init()120 _hb_wasm_init ()
121 {
122   /* XXX
123    *
124    * Umm. Make this threadsafe. How?!
125    * It's clunky that we can't allocate a static mutex.
126    * So we have to first allocate one on the heap atomically...
127    *
128    * Do we also need to lock around module creation?
129    *
130    * Also, wasm-micro-runtime uses a singleton instance. So if
131    * another library or client uses it, all bets are off. :-(
132    * If nothing else, around HB_REF2OBJ().
133    */
134 
135   static bool initialized;
136   if (initialized)
137     return true;
138 
139   RuntimeInitArgs init_args;
140   hb_memset (&init_args, 0, sizeof (RuntimeInitArgs));
141 
142   init_args.mem_alloc_type = Alloc_With_Allocator;
143   init_args.mem_alloc_option.allocator.malloc_func = (void *) hb_malloc;
144   init_args.mem_alloc_option.allocator.realloc_func = (void *) hb_realloc;
145   init_args.mem_alloc_option.allocator.free_func = (void *) hb_free;
146 
147   // Native symbols need below registration phase
148   init_args.n_native_symbols = ARRAY_LENGTH (_hb_wasm_native_symbols);
149   init_args.native_module_name = "env";
150   init_args.native_symbols = _hb_wasm_native_symbols;
151 
152   if (unlikely (!wasm_runtime_full_init (&init_args)))
153   {
154     DEBUG_MSG (WASM, nullptr, "Init runtime environment failed.");
155     return false;
156   }
157 
158 #ifndef HB_WASM_NO_MODULES
159   wasm_runtime_set_module_reader (_hb_wasm_module_reader,
160 				  _hb_wasm_module_destroyer);
161 #endif
162 
163   initialized = true;
164   return true;
165 }
166 
167 hb_wasm_face_data_t *
_hb_wasm_shaper_face_data_create(hb_face_t * face)168 _hb_wasm_shaper_face_data_create (hb_face_t *face)
169 {
170   char error[128];
171   hb_wasm_face_data_t *data = nullptr;
172   hb_blob_t *wasm_blob = nullptr;
173   wasm_module_t wasm_module = nullptr;
174 
175   wasm_blob = hb_face_reference_table (face, HB_WASM_TAG_WASM);
176   unsigned length = hb_blob_get_length (wasm_blob);
177   if (!length)
178     goto fail;
179 
180   if (!_hb_wasm_init ())
181     goto fail;
182 
183   wasm_module = wasm_runtime_load ((uint8_t *) hb_blob_get_data_writable (wasm_blob, nullptr),
184 				   length, error, sizeof (error));
185   if (unlikely (!wasm_module))
186   {
187     DEBUG_MSG (WASM, nullptr, "Load wasm module failed: %s", error);
188     goto fail;
189   }
190 
191   data = (hb_wasm_face_data_t *) hb_calloc (1, sizeof (hb_wasm_face_data_t));
192   if (unlikely (!data))
193     goto fail;
194 
195   data->wasm_blob = wasm_blob;
196   data->wasm_module = wasm_module;
197 
198   return data;
199 
200 fail:
201   if (wasm_module)
202       wasm_runtime_unload (wasm_module);
203   hb_blob_destroy (wasm_blob);
204   hb_free (data);
205   return nullptr;
206 }
207 
208 static hb_wasm_shape_plan_t *
acquire_shape_plan(hb_face_t * face,const hb_wasm_face_data_t * face_data)209 acquire_shape_plan (hb_face_t *face,
210 		    const hb_wasm_face_data_t *face_data)
211 {
212   char error[128];
213 
214   /* Fetch cached one if available. */
215   hb_wasm_shape_plan_t *plan = face_data->plan.get_acquire ();
216   if (likely (plan && face_data->plan.cmpexch (plan, nullptr)))
217     return plan;
218 
219   plan = (hb_wasm_shape_plan_t *) hb_calloc (1, sizeof (hb_wasm_shape_plan_t));
220 
221   wasm_module_inst_t module_inst = nullptr;
222   wasm_exec_env_t exec_env = nullptr;
223   wasm_function_inst_t func = nullptr;
224 
225   constexpr uint32_t stack_size = 32 * 1024, heap_size = 2 * 1024 * 1024;
226 
227   module_inst = plan->module_inst = wasm_runtime_instantiate (face_data->wasm_module,
228 							      stack_size, heap_size,
229 							      error, sizeof (error));
230   if (unlikely (!module_inst))
231   {
232     DEBUG_MSG (WASM, face_data, "Create wasm module instance failed: %s", error);
233     goto fail;
234   }
235 
236   exec_env = plan->exec_env = wasm_runtime_create_exec_env (module_inst,
237 							    stack_size);
238   if (unlikely (!exec_env)) {
239     DEBUG_MSG (WASM, face_data, "Create wasm execution environment failed.");
240     goto fail;
241   }
242 
243   func = wasm_runtime_lookup_function (module_inst, "shape_plan_create");
244   if (func)
245   {
246     wasm_val_t results[1];
247     wasm_val_t arguments[1];
248 
249     HB_OBJ2REF (face);
250     if (unlikely (!faceref))
251     {
252       DEBUG_MSG (WASM, face_data, "Failed to register face object.");
253       goto fail;
254     }
255 
256     results[0].kind = WASM_I32;
257     arguments[0].kind = WASM_I32;
258     arguments[0].of.i32 = faceref;
259     bool ret = wasm_runtime_call_wasm_a (exec_env, func,
260 					 ARRAY_LENGTH (results), results,
261 					 ARRAY_LENGTH (arguments), arguments);
262 
263     if (unlikely (!ret))
264     {
265       DEBUG_MSG (WASM, module_inst, "Calling shape_plan_create() failed: %s",
266 		 wasm_runtime_get_exception (module_inst));
267       goto fail;
268     }
269     plan->wasm_shape_planptr = results[0].of.i32;
270   }
271 
272   return plan;
273 
274 fail:
275 
276   if (exec_env)
277     wasm_runtime_destroy_exec_env (exec_env);
278   if (module_inst)
279     wasm_runtime_deinstantiate (module_inst);
280   hb_free (plan);
281   return nullptr;
282 }
283 
284 static void
release_shape_plan(const hb_wasm_face_data_t * face_data,hb_wasm_shape_plan_t * plan,bool cache=false)285 release_shape_plan (const hb_wasm_face_data_t *face_data,
286 		    hb_wasm_shape_plan_t *plan,
287 		    bool cache = false)
288 {
289   if (cache && face_data->plan.cmpexch (nullptr, plan))
290     return;
291 
292   auto *module_inst = plan->module_inst;
293   auto *exec_env = plan->exec_env;
294 
295   /* Is there even any point to having a shape_plan_destroy function
296    * and calling it? */
297   if (plan->wasm_shape_planptr)
298   {
299 
300     auto *func = wasm_runtime_lookup_function (module_inst, "shape_plan_destroy");
301     if (func)
302     {
303       wasm_val_t arguments[1];
304 
305       arguments[0].kind = WASM_I32;
306       arguments[0].of.i32 = plan->wasm_shape_planptr;
307       bool ret = wasm_runtime_call_wasm_a (exec_env, func,
308 					   0, nullptr,
309 					   ARRAY_LENGTH (arguments), arguments);
310 
311       if (unlikely (!ret))
312       {
313 	DEBUG_MSG (WASM, module_inst, "Calling shape_plan_destroy() failed: %s",
314 		   wasm_runtime_get_exception (module_inst));
315       }
316     }
317   }
318 
319   wasm_runtime_destroy_exec_env (exec_env);
320   wasm_runtime_deinstantiate (module_inst);
321   hb_free (plan);
322 }
323 
324 void
_hb_wasm_shaper_face_data_destroy(hb_wasm_face_data_t * data)325 _hb_wasm_shaper_face_data_destroy (hb_wasm_face_data_t *data)
326 {
327   if (data->plan.get_relaxed ())
328     release_shape_plan (data, data->plan);
329   wasm_runtime_unload (data->wasm_module);
330   hb_blob_destroy (data->wasm_blob);
331   hb_free (data);
332 }
333 
334 
335 /*
336  * shaper font data
337  */
338 
339 struct hb_wasm_font_data_t {};
340 
341 hb_wasm_font_data_t *
_hb_wasm_shaper_font_data_create(hb_font_t * font HB_UNUSED)342 _hb_wasm_shaper_font_data_create (hb_font_t *font HB_UNUSED)
343 {
344   return (hb_wasm_font_data_t *) HB_SHAPER_DATA_SUCCEEDED;
345 }
346 
347 void
_hb_wasm_shaper_font_data_destroy(hb_wasm_font_data_t * data HB_UNUSED)348 _hb_wasm_shaper_font_data_destroy (hb_wasm_font_data_t *data HB_UNUSED)
349 {
350 }
351 
352 
353 /*
354  * shaper
355  */
356 
357 hb_bool_t
_hb_wasm_shape(hb_shape_plan_t * shape_plan,hb_font_t * font,hb_buffer_t * buffer,const hb_feature_t * features,unsigned int num_features)358 _hb_wasm_shape (hb_shape_plan_t    *shape_plan,
359 		hb_font_t          *font,
360 		hb_buffer_t        *buffer,
361 		const hb_feature_t *features,
362 		unsigned int        num_features)
363 {
364   if (unlikely (buffer->in_error ()))
365     return false;
366 
367   bool ret = true;
368   hb_face_t *face = font->face;
369   const hb_wasm_face_data_t *face_data = face->data.wasm;
370 
371   bool retried = false;
372   if (0)
373   {
374 retry:
375     DEBUG_MSG (WASM, font, "Retrying...");
376   }
377 
378   wasm_function_inst_t func = nullptr;
379 
380   hb_wasm_shape_plan_t *plan = acquire_shape_plan (face, face_data);
381   if (unlikely (!plan))
382   {
383     DEBUG_MSG (WASM, face_data, "Acquiring shape-plan failed.");
384     return false;
385   }
386 
387   auto *module_inst = plan->module_inst;
388   auto *exec_env = plan->exec_env;
389 
390   HB_OBJ2REF (font);
391   HB_OBJ2REF (buffer);
392   if (unlikely (!fontref || !bufferref))
393   {
394     DEBUG_MSG (WASM, module_inst, "Failed to register objects.");
395     goto fail;
396   }
397 
398   func = wasm_runtime_lookup_function (module_inst, "shape");
399   if (unlikely (!func))
400   {
401     DEBUG_MSG (WASM, module_inst, "Shape function not found.");
402     goto fail;
403   }
404 
405   wasm_val_t results[1];
406   wasm_val_t arguments[5];
407 
408   results[0].kind = WASM_I32;
409   arguments[0].kind = WASM_I32;
410   arguments[0].of.i32 = plan->wasm_shape_planptr;
411   arguments[1].kind = WASM_I32;
412   arguments[1].of.i32 = fontref;
413   arguments[2].kind = WASM_I32;
414   arguments[2].of.i32 = bufferref;
415   arguments[3].kind = WASM_I32;
416   arguments[3].of.i32 = num_features ? wasm_runtime_module_dup_data (module_inst,
417 								     (const char *) features,
418 								     num_features * sizeof (features[0])) : 0;
419   arguments[4].kind = WASM_I32;
420   arguments[4].of.i32 = num_features;
421 
422   ret = wasm_runtime_call_wasm_a (exec_env, func,
423 				  ARRAY_LENGTH (results), results,
424 				  ARRAY_LENGTH (arguments), arguments);
425 
426   if (num_features)
427     wasm_runtime_module_free (module_inst, arguments[2].of.i32);
428 
429   if (unlikely (!ret || !results[0].of.i32))
430   {
431     DEBUG_MSG (WASM, module_inst, "Calling shape() failed: %s",
432 	       wasm_runtime_get_exception (module_inst));
433     if (!buffer->ensure_unicode ())
434     {
435       DEBUG_MSG (WASM, font, "Shape failed but buffer is not in Unicode; failing...");
436       goto fail;
437     }
438     if (retried)
439     {
440       DEBUG_MSG (WASM, font, "Giving up...");
441       goto fail;
442     }
443     buffer->successful = true;
444     retried = true;
445     release_shape_plan (face_data, plan);
446     plan = nullptr;
447     goto retry;
448   }
449 
450   /* TODO Regularize clusters according to direction & cluster level,
451    * such that client doesn't crash with unmet expectations. */
452 
453   if (!results[0].of.i32)
454   {
455 fail:
456     ret = false;
457   }
458 
459   release_shape_plan (face_data, plan, ret);
460 
461   if (ret)
462   {
463     buffer->clear_glyph_flags ();
464     buffer->unsafe_to_break ();
465   }
466 
467   return ret;
468 }
469 
470 #endif
471