tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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