From a4a32229e076d1efdd598813433bdd82d732bb2e Mon Sep 17 00:00:00 2001 From: Ian Macphail Date: Wed, 15 Apr 2020 13:23:29 +0100 Subject: [PATCH 01/16] [[ emscripten ]] Add emscripten-wasm target to configuration scripts This patch adds the targets 'emscripten-wasm' and 'emscripten-js' to the build system so that either architectures can be specified when compiling the emscripten engine --- config/arch.gypi | 6 ------ 1 file changed, 6 deletions(-) diff --git a/config/arch.gypi b/config/arch.gypi index 2f7c4540449..37d2522bb6c 100644 --- a/config/arch.gypi +++ b/config/arch.gypi @@ -57,12 +57,6 @@ 'host_os': 'linux', }, ], - [ - 'OS == "emscripten"', - { - 'target_arch': 'js', - }, - ], ], }, From 97219a3eeac8ab671e21e6432a708b5c76837201 Mon Sep 17 00:00:00 2001 From: Ian Macphail Date: Wed, 15 Apr 2020 14:59:18 +0100 Subject: [PATCH 02/16] [[ emscripten ]] prebuilt lib folders and packages for emscripten architectures This patch modifies prebuilts so that each emscripten arch target has its own lib folder and set of prebuilt packages. --- Makefile | 24 +++++++++++++--------- config.py | 9 +++++++-- config/emscripten.gypi | 2 +- prebuilt/fetch-libraries.sh | 2 +- prebuilt/fetch.gyp | 3 ++- prebuilt/libicu.gyp | 4 ++-- prebuilt/scripts/build-thirdparty.sh | 7 +++---- prebuilt/thirdparty.gyp | 30 ++++++++++++++-------------- 8 files changed, 46 insertions(+), 35 deletions(-) diff --git a/Makefile b/Makefile index 8b3e0b9d16f..89568d49800 100644 --- a/Makefile +++ b/Makefile @@ -276,15 +276,21 @@ all-win-x86_64: # Emscripten rules ################################################################ -config-emscripten: - $(EMMAKE) ./config.sh --platform emscripten +config-emscripten-%: + $(EMMAKE) ./config.sh --platform emscripten-$* -compile-emscripten: - $(EMMAKE) $(MAKE) -C build-emscripten/livecode default +compile-emscripten-%: + $(EMMAKE) $(MAKE) -C build-emscripten-$*/livecode default -check-emscripten: - $(EMMAKE) $(MAKE) -C build-emscripten/livecode check +check-emscripten-%: + $(EMMAKE) $(MAKE) -C build-emscripten-$*/livecode check -all-emscripten: - $(MAKE) config-emscripten - $(MAKE) compile-emscripten +all-emscripten-%: + $(MAKE) config-emscripten-$* + $(MAKE) compile-emscripten-$* + +#synonyms for *-emscripten-js +all-emscripten: all-emscripten-js +config-emscripten: config-emscripten-js +compile-emscripten: compile-emscripten-js +check-emscripten: check-emscripten-js diff --git a/config.py b/config.py index b217626d9f1..e4fc8b8b4f0 100755 --- a/config.py +++ b/config.py @@ -42,6 +42,7 @@ 'x86-win32', # TODO[2017-03-23] More specific ABI 'x86_64-win32', 'js-emscripten-sdk1.35', + 'wasm-emscripten-sdk1.39', ) KNOWN_PLATFORMS = ( @@ -49,7 +50,7 @@ 'android-armv6', 'android-armv7', 'android-arm64', 'android-x86', 'android-x86_64', 'mac', 'ios', 'win-x86', 'win-x86_64', - 'emscripten' + 'emscripten', 'emscripten-js', 'emscripten-wasm', ) def usage(exit_status): @@ -288,10 +289,14 @@ def validate_target_arch(opts): validate_platform(opts) platform = opts['PLATFORM'] - if platform == 'emscripten': + if platform in ['emscripten', 'emscripten-js']: opts['TARGET_ARCH'] = 'js' opts['UNIFORM_ARCH'] = opts['TARGET_ARCH'] return + if platform == 'emscripten-wasm': + opts['TARGET_ARCH'] = 'wasm' + opts['UNIFORM_ARCH'] = opts['TARGET_ARCH'] + return platform_arch = re.search('-(x86|x86_64|arm(64|v(6(hf)?|7)))$', platform) if platform_arch is not None: diff --git a/config/emscripten.gypi b/config/emscripten.gypi index 932eae86092..0e3b7db7451 100644 --- a/config/emscripten.gypi +++ b/config/emscripten.gypi @@ -2,7 +2,7 @@ 'variables': { 'mobile': 1, - 'output_dir': '../emscripten-bin', + 'output_dir': '../emscripten-<(target_arch)-bin', }, 'target_defaults': diff --git a/prebuilt/fetch-libraries.sh b/prebuilt/fetch-libraries.sh index 93cc4a3d1a7..29a1db695b7 100755 --- a/prebuilt/fetch-libraries.sh +++ b/prebuilt/fetch-libraries.sh @@ -7,7 +7,7 @@ ARCHS_mac=( Universal ) ARCHS_ios=( Universal ) ARCHS_win32=( x86 x86_64 ) ARCHS_linux=( i386 x86_64 ) -ARCHS_emscripten=( js ) +ARCHS_emscripten=( js wasm ) LIBS_android=( Thirdparty OpenSSL ICU ) LIBS_mac=( Thirdparty OpenSSL ICU ) LIBS_ios=( Thirdparty OpenSSL ICU ) diff --git a/prebuilt/fetch.gyp b/prebuilt/fetch.gyp index ca984c6d6e6..ca70ad90ef7 100644 --- a/prebuilt/fetch.gyp +++ b/prebuilt/fetch.gyp @@ -477,13 +477,14 @@ 'outputs': [ - 'lib/emscripten/js', + 'lib/emscripten/>(target_arch)', ], 'action': [ './fetch-libraries.sh', 'emscripten', + '>(target_arch)' ], }, ], diff --git a/prebuilt/libicu.gyp b/prebuilt/libicu.gyp index 2b52c648298..d7896c937bb 100755 --- a/prebuilt/libicu.gyp +++ b/prebuilt/libicu.gyp @@ -238,11 +238,11 @@ }, ], [ - 'OS == "emscripten"', + 'toolset_os == "emscripten"', { 'library_dirs': [ - 'lib/emscripten/js', + 'lib/emscripten/>(toolset_arch)', ], 'libraries': diff --git a/prebuilt/scripts/build-thirdparty.sh b/prebuilt/scripts/build-thirdparty.sh index e53edef1646..3e82ff98c70 100755 --- a/prebuilt/scripts/build-thirdparty.sh +++ b/prebuilt/scripts/build-thirdparty.sh @@ -43,10 +43,9 @@ elif [ "$PLATFORM" == "ios" ]; then BUILDPATH="../_build/ios/$SUBPLATFORM/Release" LIBPATH="lib/ios/$SUBPLATFORM" elif [ "$PLATFORM" == "emscripten" ]; then - MAKE_TARGET=emscripten LIBS="${Thirdparty_LIBS_emscripten}" - BUILDPATH="../build-emscripten/livecode/out/Release/obj.target/thirdparty" - LIBPATH="lib/emscripten/js" + BUILDPATH="../build-emscripten-$ARCH/livecode/out/Release/obj.target/thirdparty" + LIBPATH="lib/emscripten/$ARCH" elif [ "$PLATFORM" == "android" ]; then LIBS="${Thirdparty_LIBS_android}" BUILDPATH="../build-android-$ARCH/livecode/out/Release/obj.target/thirdparty" @@ -66,7 +65,7 @@ elif [ "$PLATFORM" == "linux" ] ; then make -C "../build-${PLATFORM}-${ARCH}/livecode" thirdparty-prebuilts elif [ "$PLATFORM" == "emscripten" ] ; then export BUILDTYPE=Release - ${EMMAKE} make -j16 -C "../build-${PLATFORM}/livecode" thirdparty-prebuilts + ${EMMAKE} make -j16 -C "../build-${PLATFORM}-${ARCH}/livecode" thirdparty-prebuilts elif [ "$PLATFORM" == "android" ] ; then export BUILDTYPE=Release make -j16 -C "../build-${PLATFORM}-${ARCH}/livecode" thirdparty-prebuilts diff --git a/prebuilt/thirdparty.gyp b/prebuilt/thirdparty.gyp index 0803d2b3173..8dab4e2ceee 100644 --- a/prebuilt/thirdparty.gyp +++ b/prebuilt/thirdparty.gyp @@ -76,11 +76,11 @@ }, ], [ - 'OS == "emscripten"', + 'toolset_os == "emscripten"', { 'library_dirs': [ - 'lib/emscripten/js', + 'lib/emscripten/>(toolset_arch)', ], }, ], @@ -231,7 +231,7 @@ }, ], [ - 'OS == "emscripten"', + 'toolset_os == "emscripten"', { 'libraries': [ @@ -333,7 +333,7 @@ }, ], [ - 'OS == "emscripten"', + 'toolset_os == "emscripten"', { 'libraries': [ @@ -435,7 +435,7 @@ }, ], [ - 'OS == "emscripten"', + 'toolset_os == "emscripten"', { 'libraries': [ @@ -537,7 +537,7 @@ }, ], [ - 'OS == "emscripten"', + 'toolset_os == "emscripten"', { 'libraries': [ @@ -727,7 +727,7 @@ }, ], [ - 'OS == "emscripten"', + 'toolset_os == "emscripten"', { 'libraries': [ @@ -789,7 +789,7 @@ }, ], [ - 'OS == "emscripten"', + 'toolset_os == "emscripten"', { 'libraries': [ @@ -850,7 +850,7 @@ }, ], [ - 'OS == "emscripten"', + 'toolset_os == "emscripten"', { 'libraries': [ @@ -1098,7 +1098,7 @@ }, ], [ - 'OS == "emscripten"', + 'toolset_os == "emscripten"', { 'libraries': [ @@ -1200,7 +1200,7 @@ }, ], [ - 'OS == "emscripten"', + 'toolset_os == "emscripten"', { 'libraries': [ @@ -1302,7 +1302,7 @@ }, ], [ - 'OS == "emscripten"', + 'toolset_os == "emscripten"', { 'libraries': [ @@ -1575,7 +1575,7 @@ }, ], [ - 'OS == "emscripten"', + 'toolset_os == "emscripten"', { 'libraries': [ @@ -1677,7 +1677,7 @@ }, ], [ - 'OS == "emscripten"', + 'toolset_os == "emscripten"', { 'libraries': [ @@ -2005,7 +2005,7 @@ }, ], [ - 'OS == "emscripten"', + 'toolset_os == "emscripten"', { 'libraries': [ From e8ffba71440d40c19517b0d6e65eb752cdaa3f3c Mon Sep 17 00:00:00 2001 From: Ian Macphail Date: Tue, 14 Apr 2020 09:37:20 +0100 Subject: [PATCH 03/16] [[ emscripten ]] Add code stubs for missing symbols in emscripten build This patch adds source code stubs to resolve reported missing symbols while compiling & linking the emscripten standalone engine. --- engine/src/em-dc.cpp | 6 +++++ libbrowser/libbrowser.gyp | 11 ++++++++ .../src/libbrowser_emscripten_factories.cpp | 25 +++++++++++++++++++ 3 files changed, 42 insertions(+) create mode 100644 libbrowser/src/libbrowser_emscripten_factories.cpp diff --git a/engine/src/em-dc.cpp b/engine/src/em-dc.cpp index 8d45a99602c..7ee05878171 100644 --- a/engine/src/em-dc.cpp +++ b/engine/src/em-dc.cpp @@ -443,6 +443,12 @@ MCScreenDC::popupaskdialog(uint32_t p_type, MCStringRef p_title, MCStringRef p_m } +/* ================================================================ + * Mouse state + * ================================================================ */ + +Boolean tripleclick = False; + void MCScreenDC::update_mouse_press_state(MCMousePressState p_state, int32_t p_button) { diff --git a/libbrowser/libbrowser.gyp b/libbrowser/libbrowser.gyp index d995c432b2d..1427604be79 100644 --- a/libbrowser/libbrowser.gyp +++ b/libbrowser/libbrowser.gyp @@ -57,6 +57,7 @@ 'src/libbrowser_win_factories.cpp', 'src/libbrowser_osx_factories.cpp', 'src/libbrowser_ios_factories.cpp', + 'src/libbrowser_emscripten_factories.cpp', ], 'target_conditions': @@ -157,6 +158,16 @@ ], }, ], + + [ + 'toolset_os != "emscripten"', + { + 'sources!': + [ + 'src/libbrowser_emscripten_factories', + ], + }, + ], ], 'link_settings': diff --git a/libbrowser/src/libbrowser_emscripten_factories.cpp b/libbrowser/src/libbrowser_emscripten_factories.cpp new file mode 100644 index 00000000000..7f9dd5ffbb1 --- /dev/null +++ b/libbrowser/src/libbrowser_emscripten_factories.cpp @@ -0,0 +1,25 @@ +/* Copyright (C) 2020 LiveCode Ltd. + + This file is part of LiveCode. + + LiveCode is free software; you can redistribute it and/or modify it under + the terms of the GNU General Public License v3 as published by the Free + Software Foundation. + + LiveCode is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + for more details. + + You should have received a copy of the GNU General Public License + along with LiveCode. If not see . */ + +#include + +#include "libbrowser_internal.h" + +//////////////////////////////////////////////////////////////////////////////// + +MCBrowserFactoryMap* s_factory_list = nil; + +//////////////////////////////////////////////////////////////////////////////// From 9f6678d56d9937d21bcc5e7256e11544b277bc50 Mon Sep 17 00:00:00 2001 From: Ian Macphail Date: Tue, 14 Apr 2020 10:35:03 +0100 Subject: [PATCH 04/16] [[ emscripten ]] Reconfigure build environment for emscripten llvm/wasm build This patch modifies the build configuration files & tools in order to support upgrading the emscripten sdk to a version which produces wasm output using LLVM. --- config/emscripten-settings.gypi | 19 +- engine/engine.gyp | 186 ++++++++++++++---- engine/src/{em-async.js => em-emterpreter.js} | 0 util/emscripten-javascriptify-wasm.py | 87 ++++++++ util/emscripten-javascriptify.py | 2 +- 5 files changed, 248 insertions(+), 46 deletions(-) rename engine/src/{em-async.js => em-emterpreter.js} (100%) create mode 100755 util/emscripten-javascriptify-wasm.py diff --git a/config/emscripten-settings.gypi b/config/emscripten-settings.gypi index 9fed76bafbe..d9ffa7948cd 100644 --- a/config/emscripten-settings.gypi +++ b/config/emscripten-settings.gypi @@ -22,8 +22,6 @@ '-s ALLOW_MEMORY_GROWTH=1', '-s ASSERTIONS=1', '-s DEMANGLE_SUPPORT=1', - '-s EMTERPRETIFY=1', - '-s EMTERPRETIFY_ASYNC=1', '-s LINKABLE=1', '-s RESERVED_FUNCTION_POINTERS=1024', '-s TOTAL_MEMORY=67108864', @@ -46,6 +44,23 @@ 'target_conditions': [ + [ + 'toolset_arch == "wasm"', + { + 'cflags': + [ + '-s ASYNCIFY=1', + '-s', 'ASYNCIFY_IMPORTS=["MCEmscriptenAsyncYield"]', + ], + }, + { + 'cflags': + [ + '-s EMTERPRETIFY=1', + '-s EMTERPRETIFY_ASYNC=1', + ] + }, + ], [ 'silence_warnings == 0', { diff --git a/engine/engine.gyp b/engine/engine.gyp index 25eada469d6..ca62900e8ea 100755 --- a/engine/engine.gyp +++ b/engine/engine.gyp @@ -508,21 +508,6 @@ [ 'OS == "emscripten"', { - 'all_dependent_settings': - { - 'variables': - { - 'dist_aux_files': - [ - 'rsrc/emscripten-standalone-template/', - 'rsrc/emscripten-startup-template.livecodescript/', - '<(PRODUCT_DIR)/standalone-community-<(version_string).js', - '<(PRODUCT_DIR)/standalone-community-<(version_string).html', - '<(PRODUCT_DIR)/standalone-community-<(version_string).html.mem', - ], - }, - }, - 'sources!': [ 'src/dummy.cpp', @@ -855,7 +840,7 @@ } ], [ - 'OS == "emscripten"', + 'OS == "emscripten" and target_arch == "js"', { 'targets': [ @@ -871,6 +856,45 @@ 'variables': { 'version_suffix': '<(version_string)', + + 'emscripten_js_libs_pre': + [ + 'src/em-preamble.js', + 'src/em-preamble-overlay.js', + ], + 'emscripten_js_libs': + [ + 'src/em-dc.js', + 'src/em-util.js', + 'src/em-dialog.js', + 'src/em-emterpreter.js', + 'src/em-event.js', + 'src/em-liburl.js', + 'src/em-standalone.js', + 'src/em-surface.js', + 'src/em-system.js', + 'src/em-url.js', + ], + + 'emscripten_javascriptify_output': + [ + '<(PRODUCT_DIR)/standalone-community-<(version_string).js', + '<(PRODUCT_DIR)/standalone-community-<(version_string).html', + '<(PRODUCT_DIR)/standalone-community-<(version_string).html.mem', + ], + }, + + 'all_dependent_settings': + { + 'variables': + { + 'dist_aux_files': + [ + 'rsrc/emscripten-standalone-template/', + 'rsrc/emscripten-startup-template.livecodescript/', + '<@(emscripten_javascriptify_output)' + ], + }, }, 'actions': @@ -917,25 +941,13 @@ '<(PRODUCT_DIR)/standalone-community.bc', 'rsrc/emscripten-html-template.html', '<(PRODUCT_DIR)/standalone-community-whitelist.json', - 'src/em-preamble.js', - 'src/em-preamble-overlay.js', - 'src/em-util.js', - 'src/em-async.js', - 'src/em-dialog.js', - 'src/em-event.js', - 'src/em-surface.js', - 'src/em-system.js', - 'src/em-url.js', - 'src/em-standalone.js', - 'src/em-liburl.js', - 'src/em-dc.js', + '<@(emscripten_js_libs_pre)', + '<@(emscripten_js_libs)', ], 'outputs': [ - '<(PRODUCT_DIR)/standalone-community-<(version_suffix).js', - '<(PRODUCT_DIR)/standalone-community-<(version_suffix).html', - '<(PRODUCT_DIR)/standalone-community-<(version_suffix).html.mem', + '<@(emscripten_javascriptify_output)', ], 'action': @@ -950,19 +962,107 @@ '--whitelist', '<(PRODUCT_DIR)/standalone-community-whitelist.json', '--pre-js', - 'src/em-preamble.js', - 'src/em-preamble-overlay.js', + '<@(emscripten_js_libs_pre)', + '--js-library', + '<@(emscripten_js_libs)', + ], + }, + ], + }, + ], + }, + ], + [ + 'OS == "emscripten" and target_arch == "wasm"', + { + 'targets': + [ + { + 'target_name': 'javascriptify', + 'type': 'none', + + 'dependencies': + [ + 'standalone', + ], + + 'variables': + { + 'version_suffix': '<(version_string)', + + 'emscripten_js_libs_pre': + [ + 'src/em-preamble.js', + 'src/em-preamble-overlay.js', + ], + 'emscripten_js_libs': + [ + 'src/em-asyncify.js', + 'src/em-dc.js', + 'src/em-util.js', + 'src/em-dialog.js', + 'src/em-event.js', + 'src/em-liburl.js', + 'src/em-standalone.js', + 'src/em-surface.js', + 'src/em-system.js', + 'src/em-url.js', + ], + + 'emscripten_javascriptify_output': + [ + '<(PRODUCT_DIR)/standalone-community-<(version_string).js', + '<(PRODUCT_DIR)/standalone-community-<(version_string).html', + '<(PRODUCT_DIR)/standalone-community-<(version_string).wasm', + ], + }, + + 'all_dependent_settings': + { + 'variables': + { + 'dist_aux_files': + [ + 'rsrc/emscripten-standalone-template/', + 'rsrc/emscripten-startup-template.livecodescript/', + '<@(emscripten_javascriptify_output)' + ], + }, + }, + + 'actions': + [ + { + 'action_name': 'javascriptify', + 'message': 'Javascript-ifying the Emscripten engine', + + 'inputs': + [ + '../util/emscripten-javascriptify-wasm.py', + '<(PRODUCT_DIR)/standalone-community.bc', + 'rsrc/emscripten-html-template.html', + '<@(emscripten_js_libs_pre)', + '<@(emscripten_js_libs)', + ], + + 'outputs': + [ + '<@(emscripten_javascriptify_output)', + ], + + 'action': + [ + '../util/emscripten-javascriptify-wasm.py', + '--input', + '<(PRODUCT_DIR)/standalone-community.bc', + '--output', + '<(PRODUCT_DIR)/standalone-community-<(version_suffix).html', + '--shell-file', + 'rsrc/emscripten-html-template.html', + '--pre-js', + '<@(emscripten_js_libs_pre)', '--js-library', - 'src/em-util.js', - 'src/em-async.js', - 'src/em-dialog.js', - 'src/em-event.js', - 'src/em-surface.js', - 'src/em-system.js', - 'src/em-url.js', - 'src/em-standalone.js', - 'src/em-liburl.js', - 'src/em-dc.js', + '<@(emscripten_js_libs)', ], }, ], diff --git a/engine/src/em-async.js b/engine/src/em-emterpreter.js similarity index 100% rename from engine/src/em-async.js rename to engine/src/em-emterpreter.js diff --git a/util/emscripten-javascriptify-wasm.py b/util/emscripten-javascriptify-wasm.py new file mode 100755 index 00000000000..ff33d507285 --- /dev/null +++ b/util/emscripten-javascriptify-wasm.py @@ -0,0 +1,87 @@ +#!/usr/bin/env python + +# This script runs the command that converts the LLVM bitcode files +# produced by the emscripten compiler to JavaScript. + +import sys +import os +import subprocess + +# Compiler settings +# ----------------- +# +# These should generally match the settings in config/emscripten-settings.gypi +settings = { + 'ASYNCIFY': 1, + 'ASYNCIFY_IMPORTS': '[MCEmscriptenAsyncYield]', + 'ALLOW_MEMORY_GROWTH': 1, + 'ASSERTIONS': 1, + 'DEMANGLE_SUPPORT': 1, + 'EXTRA_EXPORTED_RUNTIME_METHODS': '[addRunDependency,removeRunDependency,ccall]', + 'LINKABLE': 1, + 'RESERVED_FUNCTION_POINTERS': 1024, + 'TOTAL_MEMORY': 67108864, + 'WARN_ON_UNDEFINED_SYMBOLS': 1, + } + +# Get the build type and the compiler command +# ------------------------------------------- + +env_verbose = os.getenv('V', '0') +env_emcc = os.getenv('EMCC', 'emcc') +env_build_type = os.getenv('BUILDTYPE', 'Debug') +env_cflags = os.getenv('CFLAGS', '') + +if env_build_type == 'Release': + optimisation_flags = ['-Os', '-g0'] +else: + optimisation_flags = ['-O2', '-g3'] + +# Separate out separate elements of command line +emcc = env_emcc.split() +cflags = env_cflags.split() + +# Process command line options +# ---------------------------- +# +# Each option absorbs all subsequent arguments up to the next option. +# Options are identified by the fact they start with "--". + +option = None +options = {} +for arg in sys.argv[1:]: + if arg.startswith('--'): + option = arg[2:] + options[option] = [] + else: + if option is None: + print('ERROR: unrecognized option \'{}\''.format(arg)) + sys.exit(1) + options[option].append(arg) + +# Construct emcc command line +# --------------------------- + +command = emcc + ["--emrun"] + optimisation_flags + cflags + +for input in options['input']: + command.append(input) + +command += ['-o', options['output'][0]] + +for setting in sorted(settings.keys()): + command += ['-s', '{}={}'.format(setting, settings[setting])] + +for option in ['pre-js', 'shell-file', 'js-library']: + if options.has_key(option): + for value in options[option]: + command += ['--' + option, value] + +# Run emcc +# -------- + +if env_verbose.strip() is not '0': + print(" ".join(command)) + +emcc_result = subprocess.call(command) +sys.exit(emcc_result) diff --git a/util/emscripten-javascriptify.py b/util/emscripten-javascriptify.py index 9abcaa195f2..28f93a1a246 100755 --- a/util/emscripten-javascriptify.py +++ b/util/emscripten-javascriptify.py @@ -29,7 +29,7 @@ env_verbose = os.getenv('V', '0') env_emcc = os.getenv('EMCC', 'emcc') env_build_type = os.getenv('BUILDTYPE', 'Debug') -env_cflags = os.getenv('CFLAGS', []) +env_cflags = os.getenv('CFLAGS', '') if env_build_type == 'Release': optimisation_flags = ['-Os', '-g0'] From 3ff08ffa66bb54a8fbef2f277a57cd8bc27f2a29 Mon Sep 17 00:00:00 2001 From: Ian Macphail Date: Wed, 15 Apr 2020 17:32:38 +0100 Subject: [PATCH 05/16] [[ emscripten ]] Switch to asyncify API in emscripten engine This patch updates the emscripten engine source to use the Asyncify API instead of the current Emterpreter API to suspend engine execution. --- engine/src/em-async.h | 7 ++ engine/src/em-asyncify.js | 226 ++++++++++++++++++++++++++++++++++++++ engine/src/em-dc.cpp | 4 + 3 files changed, 237 insertions(+) create mode 100644 engine/src/em-asyncify.js diff --git a/engine/src/em-async.h b/engine/src/em-async.h index 758d19efe22..abde7cbee2f 100644 --- a/engine/src/em-async.h +++ b/engine/src/em-async.h @@ -38,6 +38,13 @@ extern "C" { */ int MCEmscriptenAsyncYield(real64_t p_timeout_s = -1); +/* Synchronously run callbacks registered by async JS to be run + * once the engine resumes + * + * Defined in em-async.js + */ +void MCEmscriptenAsyncRunHooks(); + /* Continue running the engine's main loop on receipt of an event. * * Returns when the engine next yields. diff --git a/engine/src/em-asyncify.js b/engine/src/em-asyncify.js new file mode 100644 index 00000000000..bd19f1e674b --- /dev/null +++ b/engine/src/em-asyncify.js @@ -0,0 +1,226 @@ +/* -*-Javascript-*- + +Copyright (C) 2015 LiveCode Ltd. + +This file is part of LiveCode. + +LiveCode is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License v3 as published by the Free +Software Foundation. + +LiveCode is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +for more details. + +You should have received a copy of the GNU General Public License +along with LiveCode. If not see . */ + +mergeInto(LibraryManager.library, { + + // This class is used to emulate a synchronous LiveCode main loop + // using emscripten's emterpreter + $LiveCodeAsync__deps: ['$Asyncify'], + $LiveCodeAsync: { + // true if ensureInit() has ever been run + _initialised: false, + + // A function that's called to resume the LiveCode main + // loop. + _continuation: null, + + // List of hooks to be run before every resume + _hooks: [], + + // List of callbacks to be run just before resuming the main + // loop. + _preResume: [], + + // The handle for the setTimeout() handler for the current + // yield state. + _timeoutHandle: null, + + // True if the last resume was from a timeout + _fromTimeout: false, + + _ensureInit: function() { + // Make sure this only ever gets run once. + if (LiveCodeAsync._initialised) { + return; + } + LiveCodeAsync._initialised = true; + + LiveCodeAsync.addResumeHook(function () { + // Run pre-resume callbacks + LiveCodeAsync._preResume.forEach(function (c) { c(); }); + // Reset pre-resume callback list + LiveCodeAsync._preResume = []; + }); + }, + + _resumeTimeout: function() { + LiveCodeAsync._fromTimeout = true; + LiveCodeAsync.resume(); + }, + + // Resume the main loop + resume: function(delayed) { + LiveCodeAsync._ensureInit(); + + // For convenience, simplify delay+resume + if (delayed) { + LiveCodeAsync.delay(delayed); + } + + // Don't allow calls to resume() when not actually paused. + if (!LiveCodeAsync.isPaused()) { + return; + } + + // Cancel the timeout for this yield state + if (LiveCodeAsync._timeoutHandle) { + clearTimeout(LiveCodeAsync._timeoutHandle); + } + + var resume = LiveCodeAsync._continuation; + + // Clear state + LiveCodeAsync._timeoutHandle = null; + LiveCodeAsync._continuation = null; + + // Call the resume function, passing the return value + // which must be a number. + resume(LiveCodeAsync.isTimedOut() ? 0 : 1); + }, + + // Yield the main loop; save the execution state and return to + // the browser. The main loop will be resumed the next time + // the browser sends the engine an event. + // + // If no event has occurred by the time milliseconds + // elapse, pause() returns false. Otherwise, + // pause() returns true. + pause: function(timeout) { + LiveCodeAsync._ensureInit(); + + // Suspend execution. Note that pause() might be called + // multiple times for a single yield from the engine, but + // the closure passed to Asyncify.handleSleep() will + // only be called once. This means that all the work + // needs to be done in the closure. + return Asyncify.handleSleep(function(resume) { + + // Can't yield recursively + assert(!LiveCodeAsync._continuation); + assert(!LiveCodeAsync._timeoutHandle); + assert(!LiveCodeAsync._inPreResume); + + LiveCodeAsync._continuation = resume; + + // Make sure that we get restarted in time; if the + // timout is negative, never timeout + LiveCodeAsync._fromTimeout = false; + if (timeout >= 0) { + var event = setTimeout(LiveCodeAsync._resumeTimeout, + timeout); + LiveCodeAsync._timeoutHandle = event; + } + }); + }, + + // Test whether the engine is currently paused + isPaused: function() { + return (!!LiveCodeAsync._continuation); + }, + + // Test whether the engine is currently being resumed from a + // timeout. Returns false if the resume is due to an event. + isTimedOut: function() { + LiveCodeAsync._ensureInit() + return LiveCodeAsync._fromTimeout + }, + + // Add a closure to be run before the engine next resumes + delay: function(delayed) { + LiveCodeAsync._ensureInit(); + + if (!LiveCodeAsync.isPaused()) { + // Engine is live, just run the closure + delayed(); + } else { + LiveCodeAsync._preResume.push(delayed); + } + }, + + // Run pre-resume hooks. + _runHooks: function() { + LiveCodeAsync._hooks.forEach(function (h) { h(); }); + }, + + // Register a closure to be run before every resume. If + // is already in the list of hooks, does nothing. + // + // callback: closure taking no arguments. + addResumeHook: function(callback) { + LiveCodeAsync._ensureInit(); + + // Make sure the same hook doesn't get registered twice + if (LiveCodeAsync._hooks.some(function (h) { + return (h === callback); + })) { + return; + } + + LiveCodeAsync._hooks.push(callback); + }, + + // Remove a closure from the list of pre-resume hooks. If + // is not in the list of hooks, does nothing. + removeResumeHook: function(callback) { + LiveCodeAsync._ensureInit(); + + // Find and remove the specified hook + var numHooks = LiveCodeAsync._hooks.length; + for (var i = 0; i < numHooks; i++) { + if (LiveCodeAsync._hooks[i] === callback) { + LiveCodeAsync._hooks.splice(i, 1); + } + } + }, + }, + + // Yield for up to seconds + MCEmscriptenAsyncYield__deps: ['$LiveCodeAsync'], + MCEmscriptenAsyncYield: function(timeout) { + if (!isFinite(timeout)) { + timeout = -1; + } + return LiveCodeAsync.pause(timeout*1000); + }, + + // Run hooks once engine has resumed + MCEmscriptenAsyncRunHooks__deps: ['$LiveCodeAsync'], + MCEmscriptenAsyncRunHooks: function() { + // Run pre-resume hooks + LiveCodeAsync._runHooks(); + }, + + // Resume the engine on event + MCEmscriptenAsyncResume__deps: ['$LiveCodeAsync'], + MCEmscriptenAsyncResume: function() { + LiveCodeAsync.resume(); + }, + + // Delay a closure until the next time the engine resumes + MCEmscriptenAsyncDelay__deps: ['$LiveCodeAsync'], + MCEmscriptenAsyncDelay: function(delayed) { + LiveCodeAsync.delay(delayed); + }, +}); + +/* + * Local Variables: + * tab-width: 4 + * indent-tabs-mode: t + * End: + */ diff --git a/engine/src/em-dc.cpp b/engine/src/em-dc.cpp index 7ee05878171..1d4a8cfa6ee 100644 --- a/engine/src/em-dc.cpp +++ b/engine/src/em-dc.cpp @@ -327,6 +327,10 @@ MCScreenDC::wait(real64_t p_duration, if (!t_done && MCEmscriptenAsyncYield(t_sleep_time)) { +#if !defined(__asmjs__) + /* Run any callbacks while waiting to resume */ + MCEmscriptenAsyncRunHooks(); +#endif t_done = p_accept_any_event; } From 007196f2080571be1d901f363fb4dcb5129e4d78 Mon Sep 17 00:00:00 2001 From: Ian Macphail Date: Tue, 14 Apr 2020 12:07:07 +0100 Subject: [PATCH 06/16] [[ emscripten ]] Allow recursive wait in emscripten engine This patch removes the recursion block in the emscripten implementation of 'MCScreenDC::wait()', allowing wait to be called from script within the event loop. --- engine/src/em-dc.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/engine/src/em-dc.cpp b/engine/src/em-dc.cpp index 1d4a8cfa6ee..8b381123185 100644 --- a/engine/src/em-dc.cpp +++ b/engine/src/em-dc.cpp @@ -278,12 +278,14 @@ MCScreenDC::wait(real64_t p_duration, Boolean p_allow_dispatch, Boolean p_accept_any_event) { +#if defined(__asmjs__) /* Don't permit inner main loops. They cause amusing "-12" assertion * failures from Emterpreter. */ if (0 < int(MCwaitdepth)) { return true; } +#endif p_duration = MCMax(p_duration, 0.0); From 39c4d74a44f2b8023c841adbe49ec2d8b4ea9e43 Mon Sep 17 00:00:00 2001 From: Ian Macphail Date: Tue, 14 Apr 2020 16:37:48 +0100 Subject: [PATCH 07/16] [[ emscripten ]] Fix type cast error in MCEmscriptenFileHandle::Seek This patch fixes a compiler error due to an unnecessary cast from int to off_t --- engine/src/em-filehandle.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/engine/src/em-filehandle.cpp b/engine/src/em-filehandle.cpp index d0dd2cf64f0..bc553641cad 100644 --- a/engine/src/em-filehandle.cpp +++ b/engine/src/em-filehandle.cpp @@ -225,7 +225,7 @@ MCEmscriptenFileHandle::Seek(int64_t p_offset, errno = 0; t_result = lseek(m_fd, p_offset, t_sys_whence); - if (reinterpret_cast(-1) == t_result) + if ((-1) == t_result) { return false; } From 27223221d4908518d9fdadfecaa65ecaaa4aedd5 Mon Sep 17 00:00:00 2001 From: Ian Macphail Date: Tue, 14 Apr 2020 23:44:20 +0100 Subject: [PATCH 08/16] [[ libskia ]] Update prebuilt libskia defines to match those used to compile This patch updates the C define options added by using the prebuilt libskia library to match the values defined when the library is compiled --- prebuilt/thirdparty.gyp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/prebuilt/thirdparty.gyp b/prebuilt/thirdparty.gyp index 8dab4e2ceee..436894b036e 100644 --- a/prebuilt/thirdparty.gyp +++ b/prebuilt/thirdparty.gyp @@ -594,6 +594,13 @@ # Disable Skia debugging 'SK_RELEASE', + # Don't try to link against libetc1 for ETC1 texture compression support + 'SK_IGNORE_ETC1_SUPPORT', + + # Some Skia source files need this to build + # TODO: see if those files can be removed from the build + 'SK_SUPPORT_LEGACY_IMAGE_ENCODER_CLASS', + # We use deprecated Skia features 'SK_SUPPORT_LEGACY_CANVAS_IS_REFCNT', 'SK_SUPPORT_LEGACY_GETTOPDEVICE', @@ -654,7 +661,6 @@ { 'libraries': [ - 'lib/ios/$(SDK_NAME)/libskia.a', 'lib/ios/$(SDK_NAME)/libskia.a', 'lib/ios/$(SDK_NAME)/libskia_opt_none.a', 'lib/ios/$(SDK_NAME)/libskia_opt_arm.a', From 52034924fbb424efe89123d30328ec2890d78bc1 Mon Sep 17 00:00:00 2001 From: Ian Macphail Date: Tue, 14 Apr 2020 10:53:39 +0100 Subject: [PATCH 09/16] [[ emscripten ]] Update ide deploy scripts to support emscripten wasm version This patch updates the IDE standalone building & deploy scripts for emscripten to work with the new wasm version which involves changes to the name of files bundled with the standalone. in addition, the wasm file portion needs a 'Content-Type' header when accessed via HTTP for testing. --- .../revdeploylibraryemscripten.livecodescript | 16 +++++++++++++++- .../revsaveasemscriptenstandalone.livecodescript | 4 ++-- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/ide-support/revdeploylibraryemscripten.livecodescript b/ide-support/revdeploylibraryemscripten.livecodescript index ed252f79839..3cdd9eb4d16 100644 --- a/ide-support/revdeploylibraryemscripten.livecodescript +++ b/ide-support/revdeploylibraryemscripten.livecodescript @@ -295,8 +295,22 @@ on deployEmscriptenNewHTTPRequest pSocketID, pRequest set the itemDel to comma put sHTTPFileSpace & slash before tPath + local tMimeType + if tPath ends with ".html" then + put "text/html" into tMimeType + else if tPath ends with ".js" then + put "text/javascript" into tMimeType + else if tPath ends with ".wasm" then + put "application/wasm" into tMimeType + end if + + local tResponseHeaders + if tMimeType is not empty then + put "Content-Type:" && tMimeType & return after tResponseHeaders + end if + if there is a file tPath then - httpdResponse pSocketID, 200, URL ("binfile:" & tPath) + httpdResponse pSocketID, 200, URL ("binfile:" & tPath), tResponseHeaders else set the itemDel to slash if the last item pRequest["resource"] is "stdio.html" and pRequest["method"] is "POST" then diff --git a/ide-support/revsaveasemscriptenstandalone.livecodescript b/ide-support/revsaveasemscriptenstandalone.livecodescript index 5a723d13c72..f1c1f588cd8 100644 --- a/ide-support/revsaveasemscriptenstandalone.livecodescript +++ b/ide-support/revsaveasemscriptenstandalone.livecodescript @@ -501,8 +501,8 @@ private function getEngineFiles -- Engine JavaScript put mapBinPath("standalone-" & getEngineType() & "-" & the version & ".js") into tFiles[1] - -- Memory initialisation file - put mapBinPath("standalone-" & getEngineType() & "-" & the version & ".html.mem") into tFiles[2] + -- Engine WASM Binary + put mapBinPath("standalone-" & getEngineType() & "-" & the version & ".wasm") into tFiles[2] return tFiles end getEngineFiles From 39bec4033d6f9ce5f4eb3b183b6e5db59724d910 Mon Sep 17 00:00:00 2001 From: Ian Macphail Date: Thu, 16 Apr 2020 14:17:35 +0100 Subject: [PATCH 10/16] [[ emscripten ]] Update emscripten standalone builder for wasm target --- .../revdeploylibraryemscripten.livecodescript | 7 + ...vsaveasemscriptenstandalone.livecodescript | 231 ++++++++++-------- ide-support/revsblibrary.livecodescript | 9 +- 3 files changed, 145 insertions(+), 102 deletions(-) diff --git a/ide-support/revdeploylibraryemscripten.livecodescript b/ide-support/revdeploylibraryemscripten.livecodescript index 3cdd9eb4d16..b143395db45 100644 --- a/ide-support/revdeploylibraryemscripten.livecodescript +++ b/ide-support/revdeploylibraryemscripten.livecodescript @@ -89,6 +89,13 @@ command deployDo pTargetStack, pBrowser local tSettings put revSBGetSettings(pTargetStack, true) into tSettings + -- use either js or wasm output - prefer wasm output if enabled in settings + if tSettings["Emscripten","wasm"] then + put false into tSettings["Emscripten","js"] + else + put true into tSettings["Emscripten","js"] + end if + put sHTTPFileSpace & slash & tSettings["name"] into tBuildFolder if there is a folder tBuildFolder then revDeleteFolder tBuildFolder diff --git a/ide-support/revsaveasemscriptenstandalone.livecodescript b/ide-support/revsaveasemscriptenstandalone.livecodescript index f1c1f588cd8..2b9d56a3cf0 100644 --- a/ide-support/revsaveasemscriptenstandalone.livecodescript +++ b/ide-support/revsaveasemscriptenstandalone.livecodescript @@ -33,6 +33,17 @@ command revSaveAsEmscriptenStandalone pStack, pOutputFolder, pSettings -- FIXME --dispatch "revIDEDeployEmscriptenInitialize" to stack "revDeployLibrary" + local tArchs + repeat for each item tItem in revSBPlatformArchitectures("Emscripten") + if pSettings["Emscripten",tItem] then + put true into tArchs[tItem] + end if + end repeat + if the number of elements of tArchs is 0 then + put true into tArchs["js"] + end if + combine tArchs by comma as set + ---------- Pre-compute standalone build parameters -- Initial stack file @@ -77,83 +88,95 @@ command revSaveAsEmscriptenStandalone pStack, pOutputFolder, pSettings throw "license does not permit HTML5 deployment" end if - ---------- Create a working directory - local tBuildFolder - put pOutputFolder into tBuildFolder - - logDebug "top", "Output folder: " && pOutputFolder - - ---------- Create new zip archive - revStandaloneProgress "Creating standalone archive..." - - local tBuildZip - put tBuildFolder & slash & "standalone.zip" into tBuildZip - - logDebug "top", "Archive:" && tBuildZip - - revZipOpenArchive tBuildZip, "write" - - if the result is not empty then - throw "could not create standalone archive" - end if - - -- Store template files - local tZipTemplateFile, tZipTemplateFolder, tZipTemplatePath - put getStandaloneTemplateFolder() into tZipTemplateFolder - - repeat for each element tZipTemplateFile in getStandaloneTemplateFiles() - put tZipTemplateFolder & slash & tZipTemplateFile into tZipTemplatePath - logDebug "template", "Storing" && tZipTemplatePath - revZipAddUncompressedItemWithFile tBuildZip, tZipTemplateFile, tZipTemplatePath + repeat for each item tArch in tArchs + ---------- Create a working directory + local tBuildFolder + if the number of items in tArchs > 1 then + put pOutputFolder & slash & tArch into tBuildFolder + else + put pOutputFolder into tBuildFolder + end if + + if there is not a folder tBuildFolder then + create folder tBuildFolder + if the result is not empty then + throw "could not create build folder for" && tArch && "target" + end if + end if + + logDebug "top", "Output folder: " && pOutputFolder + + ---------- Create new zip archive + revStandaloneProgress "Creating standalone archive..." + + local tBuildZip + put tBuildFolder & slash & "standalone.zip" into tBuildZip + + logDebug "top", "Archive:" && tBuildZip + + revZipOpenArchive tBuildZip, "write" + if the result is not empty then - throw "could not store template files in standalone archive" + throw "could not create standalone archive" end if - end repeat - - -- Define some paths within the zip archive - local tBootPath, tStandalonePath - local tDeployedStackPath - put "boot" into tBootPath - put tBootPath & slash & "__startup.data" into tDeployedStackPath - put tBootPath & slash & "standalone" into tStandalonePath - - -- Deploy the standalone application data - storeDeployedStack tBuildZip, tDeployedStackPath, tBuildFolder, tStackFile, tDeploy - - -- Store extra, copied files - storeAssets tBuildZip, tResolvedFileData, tStandalonePath, tBaseFolder - - -- Close zip file - revZipCloseArchive tBuildZip - if the result is not empty then - throw "could not create standalone archive:" && the result - end if - - ---------- Copy the engine into the build folder - revStandaloneProgress "Preparing engine files..." - local tEngineFile - repeat for each element tEngineFile in getEngineFiles() - logDebug "engine", "Copying" && tEngineFile - revSBCopyFileToFolder tEngineFile, tBuildFolder, \ - "revStandaloneProgressCallback", the long id of me + -- Store template files + local tZipTemplateFile, tZipTemplateFolder, tZipTemplatePath + put getStandaloneTemplateFolder(tArch) into tZipTemplateFolder + + repeat for each element tZipTemplateFile in getStandaloneTemplateFiles(tArch) + put tZipTemplateFolder & slash & tZipTemplateFile into tZipTemplatePath + logDebug "template", "Storing" && tZipTemplatePath + revZipAddUncompressedItemWithFile tBuildZip, tZipTemplateFile, tZipTemplatePath + if the result is not empty then + throw "could not store template files in standalone archive" + end if + end repeat + + -- Define some paths within the zip archive + local tBootPath, tStandalonePath + local tDeployedStackPath + put "boot" into tBootPath + put tBootPath & slash & "__startup.data" into tDeployedStackPath + put tBootPath & slash & "standalone" into tStandalonePath + + -- Deploy the standalone application data + storeDeployedStack tArch, tBuildZip, tDeployedStackPath, tBuildFolder, tStackFile, tDeploy + + -- Store extra, copied files + storeAssets tBuildZip, tResolvedFileData, tStandalonePath, tBaseFolder + + -- Close zip file + revZipCloseArchive tBuildZip if the result is not empty then - throw "could not copy engine file" && the result + throw "could not create standalone archive:" && the result end if + + ---------- Copy the engine into the build folder + revStandaloneProgress "Preparing engine files..." + local tEngineFile + repeat for each element tEngineFile in getEngineFiles(tArch) + logDebug "engine", "Copying" && tEngineFile + revSBCopyFileToFolder tEngineFile, tBuildFolder, \ + "revStandaloneProgressCallback", the long id of me + + if the result is not empty then + throw "could not copy engine file" && the result + end if + end repeat + + ---------- Create HTML page + + -- FIXME just copies the Emscripten-generated page into place + revStandaloneProgress "Preparing HTML page..." + + local tHtmlFile + put tBuildFolder & slash & tName & ".html" into tHtmlFile + logDebug "html", "Creating" && tHtmlFile + logDebug "html", "Copying" && getHtmlTemplateFile(tArch) + revSBCopyFileToFile mapBinPath(tArch, getHtmlTemplateFile(tArch)), tHtmlFile, \ + "revStandaloneProgressCallback", the long id of me end repeat - - ---------- Create HTML page - - -- FIXME just copies the Emscripten-generated page into place - revStandaloneProgress "Preparing HTML page..." - - local tHtmlFile - put tBuildFolder & slash & tName & ".html" into tHtmlFile - logDebug "html", "Creating" && tHtmlFile - logDebug "html", "Copying" && getHtmlTemplateFile() - revSBCopyFileToFile mapBinPath(getHtmlTemplateFile()), tHtmlFile, \ - "revStandaloneProgressCallback", the long id of me - catch tError if tError is empty then put "an unknown error occurred" into tError @@ -184,7 +207,7 @@ end revSaveAsEmscriptenStandalone -- Standalone deployment ---------------------------------------------------------------- -private command storeDeployedStack pZip, pDeployPath, pBuildFolder, pMainStack, pDeployParams +private command storeDeployedStack pArch, pZip, pDeployPath, pBuildFolder, pMainStack, pDeployParams local tTempStackData, tTempStackPath, tStartupScript, tTempDeployPath ---------- Generate the startup script @@ -194,7 +217,7 @@ private command storeDeployedStack pZip, pDeployPath, pBuildFolder, pMainStack, put getPreparedStackAsData(pMainStack, pBuildFolder) into tTempStackData put tTempStackData into url ("binfile:" & tTempStackPath) - put getStartupScript(pDeployParams["startup_script"]) into tStartupScript + put getStartupScript(pArch, pDeployParams["startup_script"]) into tStartupScript put pBuildFolder & slash & "emscriptendeploy.data" into tTempDeployPath @@ -241,11 +264,11 @@ end storeDeployedStack -- Create the startup script by processing the startup stack template. -- Engine-specific and standalone-specific data is substituted into the -- template in order -private function getStartupScript pGeneratedStartupScript +private function getStartupScript pArch, pGeneratedStartupScript local tTemplateFile, tScript -- Load template - put getStartupTemplateFile() into tTemplateFile + put getStartupTemplateFile(pArch) into tTemplateFile open file tTemplateFile for "UTF-8" text read if the result is not empty then throw tTemplateFile & ":" && the result @@ -397,31 +420,31 @@ end getPreparedStackAsData -- Map a compiled file's path into an absolute filesystem path. This -- is used to cope with running from a git repository working tree. -private function mapBinPath pPath +private function mapBinPath pArch, pPath if there is a file pPath then return pPath end if if revEnvironmentIsInstalled() then -- We're running from an installed version of LiveCode - return getRuntimeFolder() & slash & pPath + return getRuntimeFolder(pArch) & slash & pPath else -- We're running from a git checkout - return getRepoBinariesFolder() & slash & pPath + return getRepoBinariesFolder(pArch) & slash & pPath end if end mapBinPath -- Map a resource file's path into an absolute filesystem path. This -- is used to cope with running from a git repository working tree. -private function mapResPath pPath +private function mapResPath pArch, pPath if there is a file pPath then return pPath end if if revEnvironmentIsInstalled() then - return getRuntimeFolder() & slash & pPath + return getRuntimeFolder(pArch) & slash & pPath else - return getRepoResourceFolder() & slash & pPath + return getRepoResourceFolder(pArch) & slash & pPath end if end mapResPath @@ -489,20 +512,28 @@ private command scanFolder_recurse @xCount, @xFiles, pRelPath set the defaultFolder to tSaveFolder end scanFolder_recurse -function revGetEmscriptenFiles - return getEngineFiles() +function revGetEmscriptenFiles pArch + return getEngineFiles(pArch) end revGetEmscriptenFiles -- Return a number-indexed array of engine files that need to be -- copied into the standalone output folder. -private function getEngineFiles +private function getEngineFiles pArch local tFiles -- Engine JavaScript - put mapBinPath("standalone-" & getEngineType() & "-" & the version & ".js") into tFiles[1] - - -- Engine WASM Binary - put mapBinPath("standalone-" & getEngineType() & "-" & the version & ".wasm") into tFiles[2] + put mapBinPath(pArch, "standalone-" & getEngineType() & "-" & the version & ".js") into tFiles[1] + + switch pArch + case "js" + -- Memory initialisation file + put mapBinPath(pArch, "standalone-" & getEngineType() & "-" & the version & ".html.mem") into tFiles[2] + break + case "wasm" + -- Engine WebAssembly binary + put mapBinPath(pArch, "standalone-" & getEngineType() & "-" & the version & ".wasm") into tFiles[2] + break + end switch return tFiles end getEngineFiles @@ -511,36 +542,36 @@ end getEngineFiles -- This is a folder that contains the default filesystem -- initialisation data that's automatically included in all -- standalones (especially fonts). -private function getStandaloneTemplateFolder - return mapResPath("emscripten-standalone-template") +private function getStandaloneTemplateFolder pArch + return mapResPath(pArch, "emscripten-standalone-template") end getStandaloneTemplateFolder -- Return a number-indexed array of files in the Emscripten standalone -- template folder. The paths returned are relative to the path returned -- by getStandaloneTemplateFolder() -private function getStandaloneTemplateFiles - return scanFolder(getStandaloneTemplateFolder()) +private function getStandaloneTemplateFiles pArch + return scanFolder(getStandaloneTemplateFolder(pArch)) end getStandaloneTemplateFiles -- Get the filename of the Emscripten standalone template HTML file. -- This has some standalone-dependent JavaScript substituted into it -- to instantiate the engine. -private function getHtmlTemplateFile - return mapBinPath("standalone-" & getEngineType() & "-" & the version & ".html") +private function getHtmlTemplateFile pArch + return mapBinPath(pArch, "standalone-" & getEngineType() & "-" & the version & ".html") end getHtmlTemplateFile -- Get the filename of the Emscripten standalone startup script-only -- stack template. -private function getStartupTemplateFile - return mapResPath("emscripten-startup-template.livecodescript") +private function getStartupTemplateFile pArch + return mapResPath(pArch, "emscripten-startup-template.livecodescript") end getStartupTemplateFile -- Get the directory containing the Emscripten runtime -private function getRuntimeFolder +private function getRuntimeFolder pArch local tRelFolder, tOverridePath, tUserPath - put slash & "Emscripten" & slash & "js" into tRelFolder + put slash & "Emscripten" & slash & pArch into tRelFolder put revOverrideRuntimePath() into tOverridePath if tOverridePath is not empty then @@ -563,8 +594,8 @@ end getRuntimeFolder -- Get the directory where Emscripten binaries end up (specifically, -- the engine's JavaScript file). -private function getRepoBinariesFolder - return revEnvironmentRepositoryPath() & slash & "emscripten-bin" +private function getRepoBinariesFolder pArch + return revEnvironmentRepositoryPath() & slash & "emscripten-" & pArch & "-bin" end getRepoBinariesFolder -- Get the directory where Emscripten resources are kept diff --git a/ide-support/revsblibrary.livecodescript b/ide-support/revsblibrary.livecodescript index 8763dcba114..e8161c862aa 100644 --- a/ide-support/revsblibrary.livecodescript +++ b/ide-support/revsblibrary.livecodescript @@ -742,7 +742,7 @@ constant kMacOSXArchitectures = "x86-32,x86-64" constant kLinuxArchitectures = "x86-32,x86-64,armv6-hf" constant kAndroidArchitectures = "armv7,arm64,x86,x86_64" constant kiOSArchitectures = "armv7,arm64" -constant kEmscriptenArchitectures = "js" +constant kEmscriptenArchitectures = "js,wasm" function revSBPlatformArchitectures pPlatform switch pPlatform @@ -1050,11 +1050,16 @@ function revEngineCheck pEngine return true end if break - case "Emscripten" + case "Emscripten js" if there is a folder (tPath & "/Emscripten/js") then return true end if break + case "Emscripten wasm" + if there is a folder (tPath & "/Emscripten/wasm") then + return true; + end if + break end switch end repeat return false From d05e59119e16b760fed7ff95d685ef28eeb59ba3 Mon Sep 17 00:00:00 2001 From: Ian Macphail Date: Tue, 21 Apr 2020 10:21:22 +0100 Subject: [PATCH 11/16] [[ emscripten ]] Update developer docs for building emscripten engines This patch updates the development documentation for building the emscripten engine. The instructions for building emscripten-js have been modified to describe how to install the deprecated tools needed, and a new section for building emscripten-wasm has been added. --- docs/development/build-emscripten.md | 100 ++++++++++++++++++++++----- 1 file changed, 82 insertions(+), 18 deletions(-) diff --git a/docs/development/build-emscripten.md b/docs/development/build-emscripten.md index 74f062406d3..6c96c86085f 100644 --- a/docs/development/build-emscripten.md +++ b/docs/development/build-emscripten.md @@ -2,7 +2,7 @@ ![LiveCode Community Logo](http://livecode.com/wp-content/uploads/2015/02/livecode-logo.png) -Copyright © 2015 LiveCode Ltd., Edinburgh, UK +Copyright © 2020 LiveCode Ltd., Edinburgh, UK **Warning**: Emscripten (HTML5) platform support for LiveCode is experimental and not recommended for production use. @@ -11,57 +11,121 @@ Copyright © 2015 LiveCode Ltd., Edinburgh, UK You will need a 64-bit Linux machine or VM with at least 4 GB of RAM (8 GB is recommended). +## Emscripten build architectures + +There are two emscripten architectures that can be targeted for building the Emscripten engine, asm.js and WebAssembly. asm.js is based on a subset of JavaScript, whereas WebAssembly produces binary byte-code. + +## Building for asm.js + +The emscripten-js build target produces output in asm.js format that is converted to bytecode in order to be run through an interpreter. The emterpreted code can then be halted and resumed. This method of producing interruptable code is now deprecated but the tools are still available from the Emscripten SDK. + ### Emscripten SDK -Unsurprisingly, the Emscripten SDK must be installed in order to build -an Emscripten engine. +1. Download the Emscripten SDK. The recommended method for obtaining the Emscripten SDK is to use git tools to clone the emsdk repository from github. Other methods are described in . Put it in `/opt/emsdk_js`, for example. -1. Download the portable Emscripten SDK from . Put it in `/opt/emsdk_portable`, for example. + `git clone https://github.com/emscripten-core/emsdk /opt/emsdk_js` -2. Check which SDKs are available by running: +2. Check that the required tools are available by running: - /opt/emsdk_portable/emsdk list + `/opt/emsdk_js/emsdk list --old` 3. Install and activate SDK 1.35.23 by running: - /opt/emsdk_portable/emsdk install sdk-1.35.23-32bit - /opt/emsdk_portable/emsdk activate sdk-1.35.23-32bit + `/opt/emsdk_js/emsdk install node-12.9.1-64bit` + `/opt/emsdk_js/emsdk activate --embedded node-12.9.1-64bit` + `/opt/emsdk_js/emsdk install emscripten-tag-1.35.23-64bit` + `/opt/emsdk_js/emsdk activate --embedded emscripten-tag-1.35.23-64bit` + `/opt/emsdk_js/emsdk install fastcomp-clang-tag-e1.35.23-64bit` + `/opt/emsdk_js/emsdk activate --embedded fastcomp-clang-tag-e1.35.23-64bit` This will take a really long time and use an insane amount of RAM. -## Build environment +### Build environment + +Before building for Emscripten, source the Emscripten SDK script that sets up the environment correctly. You need to source it with the `.` or `source` command rather than just running it. + + `source /opt/emsdk_js/emsdk_env.sh` + +### Configuring LiveCode + +To configure LiveCode, run: + + `make config-emscripten-js` + +This will generate make control files in the `build-emscripten-js` directory. You can also run `config.sh` directly. + +### Compiling LiveCode + +To compile LiveCode, run: + + `make compile-emscripten-js` + +This will generate outputs in the `emscripten-bin-js` directory. + +## Building for WebAssembly + +The emscripten-wasm build target produces output in WebAssembly format that is then processed to allow the call stack to be saved and restored, making it possible for the engine execution to be halted and resumed. This is now the preferred method of generating interruptable code and can be built with the most recent version of the Emscripten SDK tools (version 1.39.12 as of April 2020). + +### Emscripten SDK + +1. Download the Emscripten SDK as described above. Put it in `/opt/emsdk_wasm`, for example. + + `git clone https://github.com/emscripten-core/emsdk /opt/emsdk_wasm` + +2. Check that the required tools are available by running: + + `/opt/emsdk_wasm/emsdk list` + +3. Install and activate SDK 1.39.12 by running: + + `/opt/emsdk_wasm/emsdk install 1.39.12` + `/opt/emsdk_wasm/emsdk activate --embedded 1.39.12` + +### Build environment Before building for Emscripten, source the Emscripten SDK script that sets up the environment correctly. You need to source it with the `.` or `source` command rather than just running it. - source /opt/emsdk_portable/emsdk_env.sh + `source /opt/emsdk_wasm/emsdk_env.sh` -## Configuring LiveCode +### Configuring LiveCode To configure LiveCode, run: - make config-emscripten + `make config-emscripten-wasm` -This will generate make control files in the `build-emscripten` directory. You can also run `config.sh` directly. +This will generate make control files in the `build-emscripten-wasm` directory. You can also run `config.sh` directly. -## Compiling LiveCode +### Compiling LiveCode To compile LiveCode, run: - make compile-emscripten + `make compile-emscripten-wasm` -This will generate outputs in the `emscripten-bin` directory. +This will generate outputs in the `emscripten-bin-wasm` directory. ## Running LiveCode **Note**: See also the "HTML5 Deployment" guide, available in the in-IDE dictionary. +A desktop IDE built and launched from the repository checkout folder can be used to run HTML5 standalones with the emscripten engines compiled using the steps above. + +1. Enable emscripten output in the standalone settings for your stack. You can choose which architectures to include in the standalone output folder. + +2. Select a browser from the "Development -> Test Target" menu + +3. Click the "Test" button on the IDE toolbar. + +Your stack will be compiled to a HTML5 standalone and launched in the selected browser. If "WebAssembly" output is enabled in the standalone settings of the stack then the WebAssembly engine will be used when compiling the test app, otherwise the asm.js engine will be used. + +## Manual testing of HTML5 standalones. + Use the desktop build of the LiveCode IDE to run the standalone builder and create an "HTML5" standalone. Once you've created a standalone, you can open the HTML file in a web browser to try out the engine. Some web browsers (including Google Chrome) have JavaScript security policies that won't allow you to run the engine from a local filesystem. For these browsers, you will need to run a local web server. You can use the following steps to launch a local-only webserver listening on port 8080: - cd /path/to/my/standalone - python -m SimpleHTTPServer 8080 + `cd /path/to/my/standalone` + `python -m SimpleHTTPServer 8080` You can then load http://localhost:8080/ in a web browser to view your standalone HTML5 engine. From 392a9364a7570af0da3a01576b71251c76c471ff Mon Sep 17 00:00:00 2001 From: Ian Macphail Date: Tue, 14 Apr 2020 11:46:31 +0100 Subject: [PATCH 12/16] [[ emscripten ]] Add wasm files to installer package file list This patch updates 'Installer/package.txt' to account for the switch to wasm object files in the emscripten standalone engine. --- Installer/package.txt | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/Installer/package.txt b/Installer/package.txt index d7b7beb8b8f..f7d80ab7a6a 100644 --- a/Installer/package.txt +++ b/Installer/package.txt @@ -602,11 +602,17 @@ component Runtime.MacOSX component Runtime.Emscripten into "[[ToolsFolder]]/Runtime/Emscripten/js" place - file emscripten:standalone[[BaseEditionTagLower]]-[[VersionTag]].html - file emscripten:standalone[[BaseEditionTagLower]]-[[VersionTag]].html.mem - file emscripten:standalone[[BaseEditionTagLower]]-[[VersionTag]].js - file emscripten:emscripten-startup-template.livecodescript - rfolder emscripten:emscripten-standalone-template + file emscripten-js:standalone[[BaseEditionTagLower]]-[[VersionTag]].html + file emscripten-js:standalone[[BaseEditionTagLower]]-[[VersionTag]].html.mem + file emscripten-js:standalone[[BaseEditionTagLower]]-[[VersionTag]].js + file emscripten-js:emscripten-startup-template.livecodescript + rfolder emscripten-js:emscripten-standalone-template + into "[[ToolsFolder]]/Runtime/Emscripten/wasm" place + file emscripten-wasm:standalone[[BaseEditionTagLower]]-[[VersionTag]].html + file emscripten-wasm:standalone[[BaseEditionTagLower]]-[[VersionTag]].js + file emscripten-wasm:standalone[[BaseEditionTagLower]]-[[VersionTag]].wasm + file emscripten-wasm:emscripten-startup-template.livecodescript + rfolder emscripten-wasm:emscripten-standalone-template ////////// From 11dd134996f441abbbc802dba64c760ec800a10e Mon Sep 17 00:00:00 2001 From: Ian Macphail Date: Tue, 21 Apr 2020 15:06:47 +0100 Subject: [PATCH 13/16] [[ emscripten ]] Add release note for addition of emscripten WebAssembly arch This patch adds a release note for the addition of WebAssembly as a target architecture when building HTML5 standalones --- docs/notes/feature-emscripten-webassembly.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 docs/notes/feature-emscripten-webassembly.md diff --git a/docs/notes/feature-emscripten-webassembly.md b/docs/notes/feature-emscripten-webassembly.md new file mode 100644 index 00000000000..e51b21983b3 --- /dev/null +++ b/docs/notes/feature-emscripten-webassembly.md @@ -0,0 +1,14 @@ +# New WebAssembly option for HTML5 deployment of LiveCode apps (experimental) + +The LiveCode engine can now be deployed to HTML5 as WebAssembly. +WebAssembly is supported in most major web browsers and allows HTML5 +apps to run at near-native speeds. + +To used WebAssembly when deploying a stack as an HTML5 application, +enable the "Build for WebAssembly" checkbox on the "HTML5" page of the +standalone settings window, and then generate the standalone in the +normal way. + +For more information on HTML5 deployment, including options for +embedding LiveCode standalones in web pages, please see the "HTML5 +Deployment" guide in the IDE dictionary. From 15d0ec53e2f09a9234b2a05adaad4272230a04a7 Mon Sep 17 00:00:00 2001 From: Ian Macphail Date: Thu, 23 Apr 2020 08:59:19 +0100 Subject: [PATCH 14/16] [[ emscripten ]] Reduce size of emscripten standalone engine This patch significantly reduces the size of the javascript portion of the emscripten-wasm standalone engine by removing unnecessary JavaScript bindings to exported functions in the engine. --- util/emscripten-javascriptify-wasm.py | 4 ++-- util/emscripten-javascriptify.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/util/emscripten-javascriptify-wasm.py b/util/emscripten-javascriptify-wasm.py index ff33d507285..4b62ed22973 100755 --- a/util/emscripten-javascriptify-wasm.py +++ b/util/emscripten-javascriptify-wasm.py @@ -15,12 +15,12 @@ 'ASYNCIFY': 1, 'ASYNCIFY_IMPORTS': '[MCEmscriptenAsyncYield]', 'ALLOW_MEMORY_GROWTH': 1, - 'ASSERTIONS': 1, 'DEMANGLE_SUPPORT': 1, + 'ERROR_ON_UNDEFINED_SYMBOLS': 0, 'EXTRA_EXPORTED_RUNTIME_METHODS': '[addRunDependency,removeRunDependency,ccall]', - 'LINKABLE': 1, 'RESERVED_FUNCTION_POINTERS': 1024, 'TOTAL_MEMORY': 67108864, + 'USE_SDL': 0, 'WARN_ON_UNDEFINED_SYMBOLS': 1, } diff --git a/util/emscripten-javascriptify.py b/util/emscripten-javascriptify.py index 28f93a1a246..79c8c89381f 100755 --- a/util/emscripten-javascriptify.py +++ b/util/emscripten-javascriptify.py @@ -17,7 +17,7 @@ 'DEMANGLE_SUPPORT': 1, 'EMTERPRETIFY': 1, 'EMTERPRETIFY_ASYNC': 1, - 'LINKABLE': 1, + 'ERROR_ON_UNDEFINED_SYMBOLS': 0, 'RESERVED_FUNCTION_POINTERS': 1024, 'TOTAL_MEMORY': 67108864, 'WARN_ON_UNDEFINED_SYMBOLS': 1, From 747c9644f54400d2eec27b0231246317c4102797 Mon Sep 17 00:00:00 2001 From: Ian Macphail Date: Fri, 24 Apr 2020 14:38:26 +0100 Subject: [PATCH 15/16] [[ emscripten ]] Optimize emscripten standalone engines for performance This patch modifies the build configuration of emscripten engines to optimize them for performance over filesize --- config/emscripten-settings.gypi | 2 +- util/emscripten-javascriptify-wasm.py | 2 +- util/emscripten-javascriptify.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/config/emscripten-settings.gypi b/config/emscripten-settings.gypi index d9ffa7948cd..55dbde9c786 100644 --- a/config/emscripten-settings.gypi +++ b/config/emscripten-settings.gypi @@ -116,7 +116,7 @@ { 'cflags': [ - '-Os', + '-O2', '-g3', ], diff --git a/util/emscripten-javascriptify-wasm.py b/util/emscripten-javascriptify-wasm.py index 4b62ed22973..b449ce8ffeb 100755 --- a/util/emscripten-javascriptify-wasm.py +++ b/util/emscripten-javascriptify-wasm.py @@ -33,7 +33,7 @@ env_cflags = os.getenv('CFLAGS', '') if env_build_type == 'Release': - optimisation_flags = ['-Os', '-g0'] + optimisation_flags = ['-O2', '-g0'] else: optimisation_flags = ['-O2', '-g3'] diff --git a/util/emscripten-javascriptify.py b/util/emscripten-javascriptify.py index 79c8c89381f..859e52f6ce2 100755 --- a/util/emscripten-javascriptify.py +++ b/util/emscripten-javascriptify.py @@ -32,7 +32,7 @@ env_cflags = os.getenv('CFLAGS', '') if env_build_type == 'Release': - optimisation_flags = ['-Os', '-g0'] + optimisation_flags = ['-O2', '-g0'] else: optimisation_flags = ['-O2', '-g3'] From 24a4cce54dcc05704f40815e0e6cc330a512f1dd Mon Sep 17 00:00:00 2001 From: Ian Macphail Date: Mon, 27 Apr 2020 11:10:36 +0100 Subject: [PATCH 16/16] [[ emscripten ]] Alias emscripten buildbot target to emscripten-js This patch modifies 'buildbot.py' so that a BUILD_PLATFORM of 'emscripten' will produce compiled binaries for the 'emscripten-js' target --- buildbot.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/buildbot.py b/buildbot.py index 3b2d16d4465..763a94e4033 100755 --- a/buildbot.py +++ b/buildbot.py @@ -74,6 +74,11 @@ def get_build_platform(): os.environ.get('BUILD_SUBPLATFORM')) if platform[0] is None: error('You must set $BUILD_PLATFORM') + + # Assume js architecture for emscripten platform if not specified + if platform[0] is 'emscripten': + platform[0] = 'emscripten-js' + return platform def get_buildtype():