2 * Copyright © 2011 Google, Inc.
4 * This is part of HarfBuzz, a text shaping library.
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.
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
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.
24 * Google Author(s): Behdad Esfahbod
28 #define HB_DEBUG_WASM 1
30 #include "hb-shaper-impl.hh"
34 /* Compile wasm-micro-runtime with:
36 * $ cmake -DWAMR_BUILD_MULTI_MODULE=1 -DWAMR_BUILD_REF_TYPES=1 -DWAMR_BUILD_FAST_JIT=1
39 * If you manage to build a wasm shared module successfully and want to use it,
42 * - Add -DWAMR_BUILD_MULTI_MODULE=1 to your cmake build for wasm-micro-runtime,
44 * - Remove the #define HB_WASM_NO_MODULES line below,
46 * - Install your shared module with name ending in .wasm in
47 * $(prefix)/$(libdir)/harfbuzz/wasm/
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.
53 * - Try shaping your font and hope for the best...
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?
59 #include "hb-wasm-api.hh"
60 #include "hb-wasm-api-list.hh"
62 #ifndef HB_WASM_NO_MODULES
63 #define HB_WASM_NO_MODULES
67 #ifndef HB_WASM_NO_MODULES
69 _hb_wasm_module_reader (const char *module_name
,
70 uint8_t **p_buffer
, uint32_t *p_size
)
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);
76 auto *blob
= hb_blob_create_from_file (path
);
79 auto *data
= hb_blob_get_data (blob
, &length
);
81 *p_buffer
= (uint8_t *) hb_malloc (length
);
83 if (length
&& !p_buffer
)
86 memcpy (*p_buffer
, data
, length
);
89 hb_blob_destroy (blob
);
95 _hb_wasm_module_destroyer (uint8_t *buffer
, uint32_t size
)
105 #define HB_WASM_TAG_WASM HB_TAG('W','a','s','m')
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
);
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
;
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...
128 * Do we also need to lock around module creation?
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().
135 static bool initialized
;
139 RuntimeInitArgs init_args
;
140 hb_memset (&init_args
, 0, sizeof (RuntimeInitArgs
));
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
;
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
;
152 if (unlikely (!wasm_runtime_full_init (&init_args
)))
154 DEBUG_MSG (WASM
, nullptr, "Init runtime environment failed.");
158 #ifndef HB_WASM_NO_MODULES
159 wasm_runtime_set_module_reader (_hb_wasm_module_reader
,
160 _hb_wasm_module_destroyer
);
167 hb_wasm_face_data_t
*
168 _hb_wasm_shaper_face_data_create (hb_face_t
*face
)
171 hb_wasm_face_data_t
*data
= nullptr;
172 hb_blob_t
*wasm_blob
= nullptr;
173 wasm_module_t wasm_module
= nullptr;
175 wasm_blob
= hb_face_reference_table (face
, HB_WASM_TAG_WASM
);
176 unsigned length
= hb_blob_get_length (wasm_blob
);
180 if (!_hb_wasm_init ())
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
))
187 DEBUG_MSG (WASM
, nullptr, "Load wasm module failed: %s", error
);
191 data
= (hb_wasm_face_data_t
*) hb_calloc (1, sizeof (hb_wasm_face_data_t
));
192 if (unlikely (!data
))
195 data
->wasm_blob
= wasm_blob
;
196 data
->wasm_module
= wasm_module
;
202 wasm_runtime_unload (wasm_module
);
203 hb_blob_destroy (wasm_blob
);
208 static hb_wasm_shape_plan_t
*
209 acquire_shape_plan (hb_face_t
*face
,
210 const hb_wasm_face_data_t
*face_data
)
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)))
219 plan
= (hb_wasm_shape_plan_t
*) hb_calloc (1, sizeof (hb_wasm_shape_plan_t
));
221 wasm_module_inst_t module_inst
= nullptr;
222 wasm_exec_env_t exec_env
= nullptr;
223 wasm_function_inst_t func
= nullptr;
225 constexpr uint32_t stack_size
= 32 * 1024, heap_size
= 2 * 1024 * 1024;
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
))
232 DEBUG_MSG (WASM
, face_data
, "Create wasm module instance failed: %s", error
);
236 exec_env
= plan
->exec_env
= wasm_runtime_create_exec_env (module_inst
,
238 if (unlikely (!exec_env
)) {
239 DEBUG_MSG (WASM
, face_data
, "Create wasm execution environment failed.");
243 func
= wasm_runtime_lookup_function (module_inst
, "shape_plan_create", nullptr);
246 wasm_val_t results
[1];
247 wasm_val_t arguments
[1];
250 if (unlikely (!faceref
))
252 DEBUG_MSG (WASM
, face_data
, "Failed to register face object.");
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
);
265 DEBUG_MSG (WASM
, module_inst
, "Calling shape_plan_create() failed: %s",
266 wasm_runtime_get_exception (module_inst
));
269 plan
->wasm_shape_planptr
= results
[0].of
.i32
;
277 wasm_runtime_destroy_exec_env (exec_env
);
279 wasm_runtime_deinstantiate (module_inst
);
285 release_shape_plan (const hb_wasm_face_data_t
*face_data
,
286 hb_wasm_shape_plan_t
*plan
,
289 if (cache
&& face_data
->plan
.cmpexch (nullptr, plan
))
292 auto *module_inst
= plan
->module_inst
;
293 auto *exec_env
= plan
->exec_env
;
295 /* Is there even any point to having a shape_plan_destroy function
297 if (plan
->wasm_shape_planptr
)
300 auto *func
= wasm_runtime_lookup_function (module_inst
, "shape_plan_destroy", nullptr);
303 wasm_val_t arguments
[1];
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
,
309 ARRAY_LENGTH (arguments
), arguments
);
313 DEBUG_MSG (WASM
, module_inst
, "Calling shape_plan_destroy() failed: %s",
314 wasm_runtime_get_exception (module_inst
));
319 wasm_runtime_destroy_exec_env (exec_env
);
320 wasm_runtime_deinstantiate (module_inst
);
325 _hb_wasm_shaper_face_data_destroy (hb_wasm_face_data_t
*data
)
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
);
339 struct hb_wasm_font_data_t
{};
341 hb_wasm_font_data_t
*
342 _hb_wasm_shaper_font_data_create (hb_font_t
*font HB_UNUSED
)
344 return (hb_wasm_font_data_t
*) HB_SHAPER_DATA_SUCCEEDED
;
348 _hb_wasm_shaper_font_data_destroy (hb_wasm_font_data_t
*data HB_UNUSED
)
358 _hb_wasm_shape (hb_shape_plan_t
*shape_plan
,
361 const hb_feature_t
*features
,
362 unsigned int num_features
)
364 if (unlikely (buffer
->in_error ()))
368 hb_face_t
*face
= font
->face
;
369 const hb_wasm_face_data_t
*face_data
= face
->data
.wasm
;
371 bool retried
= false;
375 DEBUG_MSG (WASM
, font
, "Retrying...");
378 wasm_function_inst_t func
= nullptr;
380 hb_wasm_shape_plan_t
*plan
= acquire_shape_plan (face
, face_data
);
381 if (unlikely (!plan
))
383 DEBUG_MSG (WASM
, face_data
, "Acquiring shape-plan failed.");
387 auto *module_inst
= plan
->module_inst
;
388 auto *exec_env
= plan
->exec_env
;
392 if (unlikely (!fontref
|| !bufferref
))
394 DEBUG_MSG (WASM
, module_inst
, "Failed to register objects.");
398 func
= wasm_runtime_lookup_function (module_inst
, "shape", nullptr);
399 if (unlikely (!func
))
401 DEBUG_MSG (WASM
, module_inst
, "Shape function not found.");
405 wasm_val_t results
[1];
406 wasm_val_t arguments
[5];
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
;
422 ret
= wasm_runtime_call_wasm_a (exec_env
, func
,
423 ARRAY_LENGTH (results
), results
,
424 ARRAY_LENGTH (arguments
), arguments
);
427 wasm_runtime_module_free (module_inst
, arguments
[2].of
.i32
);
429 if (unlikely (!ret
|| !results
[0].of
.i32
))
431 DEBUG_MSG (WASM
, module_inst
, "Calling shape() failed: %s",
432 wasm_runtime_get_exception (module_inst
));
433 if (!buffer
->ensure_unicode ())
435 DEBUG_MSG (WASM
, font
, "Shape failed but buffer is not in Unicode; failing...");
440 DEBUG_MSG (WASM
, font
, "Giving up...");
443 buffer
->successful
= true;
445 release_shape_plan (face_data
, plan
);
450 /* TODO Regularize clusters according to direction & cluster level,
451 * such that client doesn't crash with unmet expectations. */
453 if (!results
[0].of
.i32
)
459 release_shape_plan (face_data
, plan
, ret
);
463 buffer
->clear_glyph_flags ();
464 buffer
->unsafe_to_break ();