/* * Copyright © 2011 Google, Inc. * * This is part of HarfBuzz, a text shaping library. * * Permission is hereby granted, without written agreement and without * license or royalty fees, to use, copy, modify, and distribute this * software and its documentation for any purpose, provided that the * above copyright notice and the following two paragraphs appear in * all copies of this software. * * IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE TO ANY PARTY FOR * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES * ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN * IF THE COPYRIGHT HOLDER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH * DAMAGE. * * THE COPYRIGHT HOLDER SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND * FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS * ON AN "AS IS" BASIS, AND THE COPYRIGHT HOLDER HAS NO OBLIGATION TO * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. * * Google Author(s): Behdad Esfahbod */ #define HB_DEBUG_WASM 1 #include "hb-shaper-impl.hh" #ifdef HAVE_WASM /* Compile wasm-micro-runtime with: * * $ cmake -DWAMR_BUILD_MULTI_MODULE=1 -DWAMR_BUILD_REF_TYPES=1 -DWAMR_BUILD_FAST_JIT=1 * $ make * * If you manage to build a wasm shared module successfully and want to use it, * do the following: * * - Add -DWAMR_BUILD_MULTI_MODULE=1 to your cmake build for wasm-micro-runtime, * * - Remove the #define HB_WASM_NO_MODULES line below, * * - Install your shared module with name ending in .wasm in * $(prefix)/$(libdir)/harfbuzz/wasm/ * * - Build your font's wasm code importing the shared modules with the desired * name. This can be done eg.: __attribute__((import_module("graphite2"))) * before each symbol in the the shared-module's headers. * * - Try shaping your font and hope for the best... * * I haven't been able to get this to work since emcc's support for shared libraries * requires support from the host that seems to be missing from wasm-micro-runtime? */ #include "hb-wasm-api.hh" #include "hb-wasm-api-list.hh" #define HB_WASM_NO_MODULES static bool HB_UNUSED _hb_wasm_module_reader (const char *module_name, uint8_t **p_buffer, uint32_t *p_size) { char path[sizeof (HB_WASM_MODULE_DIR) + 64] = HB_WASM_MODULE_DIR "/"; strncat (path, module_name, sizeof (path) - sizeof (HB_WASM_MODULE_DIR) - 16); strncat (path, ".wasm", 6); auto *blob = hb_blob_create_from_file (path); unsigned length; auto *data = hb_blob_get_data (blob, &length); *p_buffer = (uint8_t *) hb_malloc (length); if (length && !p_buffer) return false; memcpy (*p_buffer, data, length); *p_size = length; hb_blob_destroy (blob); return true; } static void HB_UNUSED _hb_wasm_module_destroyer (uint8_t *buffer, uint32_t size) { hb_free (buffer); } /* * shaper face data */ #define HB_WASM_TAG_WASM HB_TAG('W','a','s','m') struct hb_wasm_shape_plan_t { wasm_module_inst_t module_inst; wasm_exec_env_t exec_env; ptr_d(void, wasm_shape_plan); }; struct hb_wasm_face_data_t { hb_blob_t *wasm_blob; wasm_module_t wasm_module; mutable hb_atomic_ptr_t plan; }; static bool _hb_wasm_init () { /* XXX * * Umm. Make this threadsafe. How?! * It's clunky that we can't allocate a static mutex. * So we have to first allocate one on the heap atomically... * * Do we also need to lock around module creation? * * Also, wasm-micro-runtime uses a singleton instance. So if * another library or client uses it, all bets are off. :-( * If nothing else, around HB_REF2OBJ(). */ static bool initialized; if (initialized) return true; RuntimeInitArgs init_args; memset (&init_args, 0, sizeof (RuntimeInitArgs)); init_args.mem_alloc_type = Alloc_With_Allocator; init_args.mem_alloc_option.allocator.malloc_func = (void *) hb_malloc; init_args.mem_alloc_option.allocator.realloc_func = (void *) hb_realloc; init_args.mem_alloc_option.allocator.free_func = (void *) hb_free; // Native symbols need below registration phase init_args.n_native_symbols = ARRAY_LENGTH (_hb_wasm_native_symbols); init_args.native_module_name = "env"; init_args.native_symbols = _hb_wasm_native_symbols; if (unlikely (!wasm_runtime_full_init (&init_args))) { DEBUG_MSG (WASM, nullptr, "Init runtime environment failed."); return false; } #ifndef HB_WASM_NO_MODULES wasm_runtime_set_module_reader (_hb_wasm_module_reader, _hb_wasm_module_destroyer); #endif initialized = true; return true; } hb_wasm_face_data_t * _hb_wasm_shaper_face_data_create (hb_face_t *face) { char error[128]; hb_wasm_face_data_t *data = nullptr; hb_blob_t *wasm_blob = nullptr; wasm_module_t wasm_module = nullptr; wasm_blob = hb_face_reference_table (face, HB_WASM_TAG_WASM); unsigned length = hb_blob_get_length (wasm_blob); if (!length) goto fail; if (!_hb_wasm_init ()) goto fail; wasm_module = wasm_runtime_load ((uint8_t *) hb_blob_get_data_writable (wasm_blob, nullptr), length, error, sizeof (error)); if (unlikely (!wasm_module)) { DEBUG_MSG (WASM, nullptr, "Load wasm module failed: %s", error); goto fail; } data = (hb_wasm_face_data_t *) hb_calloc (1, sizeof (hb_wasm_face_data_t)); if (unlikely (!data)) goto fail; data->wasm_blob = wasm_blob; data->wasm_module = wasm_module; return data; fail: if (wasm_module) wasm_runtime_unload (wasm_module); hb_blob_destroy (wasm_blob); hb_free (data); return nullptr; } static hb_wasm_shape_plan_t * acquire_shape_plan (hb_face_t *face, const hb_wasm_face_data_t *face_data) { char error[128]; /* Fetch cached one if available. */ hb_wasm_shape_plan_t *plan = face_data->plan.get_acquire (); if (likely (plan && face_data->plan.cmpexch (plan, nullptr))) return plan; plan = (hb_wasm_shape_plan_t *) hb_calloc (1, sizeof (hb_wasm_shape_plan_t)); wasm_module_inst_t module_inst = nullptr; wasm_exec_env_t exec_env = nullptr; wasm_function_inst_t func = nullptr; constexpr uint32_t stack_size = 32 * 1024, heap_size = 2 * 1024 * 1024; module_inst = plan->module_inst = wasm_runtime_instantiate (face_data->wasm_module, stack_size, heap_size, error, sizeof (error)); if (unlikely (!module_inst)) { DEBUG_MSG (WASM, face_data, "Create wasm module instance failed: %s", error); goto fail; } exec_env = plan->exec_env = wasm_runtime_create_exec_env (module_inst, stack_size); if (unlikely (!exec_env)) { DEBUG_MSG (WASM, face_data, "Create wasm execution environment failed."); goto fail; } func = wasm_runtime_lookup_function (module_inst, "shape_plan_create", nullptr); if (func) { wasm_val_t results[1]; wasm_val_t arguments[1]; HB_OBJ2REF (face); if (unlikely (!faceref)) { DEBUG_MSG (WASM, face_data, "Failed to register face object."); goto fail; } results[0].kind = WASM_I32; arguments[0].kind = WASM_I32; arguments[0].of.i32 = faceref; bool ret = wasm_runtime_call_wasm_a (exec_env, func, ARRAY_LENGTH (results), results, ARRAY_LENGTH (arguments), arguments); if (unlikely (!ret)) { DEBUG_MSG (WASM, module_inst, "Calling shape_plan_create() failed: %s", wasm_runtime_get_exception (module_inst)); goto fail; } plan->wasm_shape_planptr = results[0].of.i32; } return plan; fail: if (exec_env) wasm_runtime_destroy_exec_env (exec_env); if (module_inst) wasm_runtime_deinstantiate (module_inst); hb_free (plan); return nullptr; } static void release_shape_plan (const hb_wasm_face_data_t *face_data, hb_wasm_shape_plan_t *plan, bool cache = false) { if (cache && face_data->plan.cmpexch (nullptr, plan)) return; auto *module_inst = plan->module_inst; auto *exec_env = plan->exec_env; /* Is there even any point to having a shape_plan_destroy function * and calling it? */ if (plan->wasm_shape_planptr) { auto *func = wasm_runtime_lookup_function (module_inst, "shape_plan_destroy", nullptr); if (func) { wasm_val_t arguments[1]; arguments[0].kind = WASM_I32; arguments[0].of.i32 = plan->wasm_shape_planptr; bool ret = wasm_runtime_call_wasm_a (exec_env, func, 0, nullptr, ARRAY_LENGTH (arguments), arguments); if (unlikely (!ret)) { DEBUG_MSG (WASM, module_inst, "Calling shape_plan_destroy() failed: %s", wasm_runtime_get_exception (module_inst)); } } } wasm_runtime_destroy_exec_env (exec_env); wasm_runtime_deinstantiate (module_inst); hb_free (plan); } void _hb_wasm_shaper_face_data_destroy (hb_wasm_face_data_t *data) { if (data->plan.get_relaxed ()) release_shape_plan (data, data->plan); wasm_runtime_unload (data->wasm_module); hb_blob_destroy (data->wasm_blob); hb_free (data); } /* * shaper font data */ struct hb_wasm_font_data_t {}; hb_wasm_font_data_t * _hb_wasm_shaper_font_data_create (hb_font_t *font HB_UNUSED) { return (hb_wasm_font_data_t *) HB_SHAPER_DATA_SUCCEEDED; } void _hb_wasm_shaper_font_data_destroy (hb_wasm_font_data_t *data HB_UNUSED) { } /* * shaper */ 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) { if (unlikely (buffer->in_error ())) return false; bool ret = true; hb_face_t *face = font->face; const hb_wasm_face_data_t *face_data = face->data.wasm; bool retried = false; if (0) { retry: DEBUG_MSG (WASM, font, "Retrying..."); } wasm_function_inst_t func = nullptr; hb_wasm_shape_plan_t *plan = acquire_shape_plan (face, face_data); if (unlikely (!plan)) { DEBUG_MSG (WASM, face_data, "Acquiring shape-plan failed."); return false; } auto *module_inst = plan->module_inst; auto *exec_env = plan->exec_env; HB_OBJ2REF (font); HB_OBJ2REF (buffer); if (unlikely (!fontref || !bufferref)) { DEBUG_MSG (WASM, module_inst, "Failed to register objects."); goto fail; } func = wasm_runtime_lookup_function (module_inst, "shape", nullptr); if (unlikely (!func)) { DEBUG_MSG (WASM, module_inst, "Shape function not found."); goto fail; } wasm_val_t results[1]; wasm_val_t arguments[5]; results[0].kind = WASM_I32; arguments[0].kind = WASM_I32; arguments[0].of.i32 = plan->wasm_shape_planptr; arguments[1].kind = WASM_I32; arguments[1].of.i32 = fontref; arguments[2].kind = WASM_I32; arguments[2].of.i32 = bufferref; arguments[3].kind = WASM_I32; arguments[3].of.i32 = num_features ? wasm_runtime_module_dup_data (module_inst, (const char *) features, num_features * sizeof (features[0])) : 0; arguments[4].kind = WASM_I32; arguments[4].of.i32 = num_features; ret = wasm_runtime_call_wasm_a (exec_env, func, ARRAY_LENGTH (results), results, ARRAY_LENGTH (arguments), arguments); if (num_features) wasm_runtime_module_free (module_inst, arguments[2].of.i32); if (unlikely (!ret || !results[0].of.i32)) { DEBUG_MSG (WASM, module_inst, "Calling shape() failed: %s", wasm_runtime_get_exception (module_inst)); if (retried) { DEBUG_MSG (WASM, font, "Giving up..."); goto fail; } buffer->successful = true; retried = true; release_shape_plan (face_data, plan); plan = nullptr; goto retry; } /* TODO Regularize clusters according to direction & cluster level, * such that client doesn't crash with unmet expectations. */ if (!results[0].of.i32) { fail: ret = false; } release_shape_plan (face_data, plan, ret); if (ret) { buffer->clear_glyph_flags (); buffer->unsafe_to_break (); } return ret; } #endif