hb-wasm-shape.cc (12216B)
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 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 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_t<hb_wasm_shape_plan_t *> plan; 117 }; 118 119 static bool 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 * 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 * 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 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 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 * 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 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 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