Emscripten教程之代码可移植性与限制(一)
Emscripten教程之代码可移植性与限制(一)
翻译:云荒杯倾
本文是Emscripten-WebAssembly专栏系列文章之一,更多文章请查看专栏。
也可以去作者的博客阅读文章。
欢迎加入Wasm和emscripten技术交流群,群聊号码:939206522。
Emscripten代码移植主题涵盖了将C、C++代码移植到Emscripten时需要考虑的所有核心考虑问题,以及一般的编码和调试指南。
共有以下主题。
代码可移植性与限制、emscripten的运行环境、连接C++与JavaScript、文件和文件系统、多媒体数据和图形、调试、多线程编程支持、移植SIMD代码、Asyncify、Emterpreter
每一部分内容都比较多,本文主要讲第一部分,代码可移植性与限制。下面是正文:
代码可移植性与限制
Emscripten几乎可以编译任何可移植的c/c++代码到JavaScript。但由于浏览器环境限制和Emscripten编译出来的代码的限制,一些代码为了能被编译需要做改动,本文就帮我们找出这部分代码。
1、关于可移植性的指导
本节解释了哪些类型的代码是不可移植的(或者更难于移植);哪些代码可以编译,但会运行得很慢。开发人员可以使用这些信息来评估移植代码和重写代码的工作量。
1.1 不能编译的代码
为了使Emscripten工作,下面类型的代码需要重写。(理论上,在使用模拟的情况下,可以使用Emscripten解决这些问题,但速度非常慢。)
- 代码是多线程的,并使用共享状态。JavaScript有线程(web workers),但它们不能共享状态。它们传递消息postMessage()。
Note:
如果JavaScript标准机构将共享状态添加到webworker中,支持多线程代码将成为可能。
- 代码依赖于大端序架构。Emscripten编译的代码目前需要一个little-endian主机运行,这种主机占了连接到互联网的99%的机器。这是因为JavaScript类型化数组服从主机字节序,LLVM需要知道目标字节序是什么。
- 依赖于x86对齐方式的代码。x86允许未对齐的内存读写(例如,您可以从一个非偶数地址读取一个16位的值),但是其他的架构不是。对于emscripten生成的JavaScript,其内存对齐方式是未定义的。如果您使用
SAFE_HEAP = 1构建您的代码,那么您将得到一个清晰的运行时异常,参见调试。
- 使用本机环境的底层特性的代码,例如setjmp / longjmp涉及的本地堆栈操作。(we support proper setjmp/longjmp, i.e.,
jumping down the stack, but not jumping up to an unwound stack, which is undefined behavior).
- 扫描寄存器或堆栈的代码。因为在寄存器或堆栈上的变量可能是被放置到一个并不能被扫描的js局部变量里保存的。
NOTE:
如果你是一个喜欢自己写垃圾回收程序的程序员,可能对这类代码比较熟。。。
- 具有特定于体系结构的内联汇编(比如包含x86代码的asm())的代码是不可移植的。这段代码需要用可移植的C或C++来替换。有时,代码库会将可移植的代码和可选的内联程序集写在一起作为优化,你需要找到一个选项使内联汇编代码不可用。
1.2 能编译但是运行得比较慢的代码
Note:
当你要优化代码的时候,就会知道了解这些事项是有用的。
下面类型的代码会被编译,但是可能运行的很慢:
- 64位整型变量。数学运算(+,-,*,/)是慢的,因为它们是被模拟的。这是因为JavaScript没有本地64位int类型,因此这是不可避免的。
- C++异常。在JavaScript中,这些代码通常会使JavaScript引擎关闭各种优化。因此,在- o1和上面的默认情况下,异常会被关闭。要重新启用它们,请运行emcc与- s DISABLE_EXCEPTION_CATCHING= 0。
- setjmp also prevents relooping around it,迫使我们使用一种效率较低的方法来模拟控制流。
2、API限制
浏览器环境和JavaScript不同于C/C++通常运行的本地环境。这些差异对如何调用和使用本地API施加了一些限制。本部分列出了一些比较明显的限制。
2.1 网络
Emscripten支持libc库的网络函数,但您必须限制他们是异步(非阻塞)操作。这是因为底层的JavaScript网络函数是异步的。
2.2 文件系统
Emscripten支持libc文件系统函数,C /C++代码可以以正常方式编写。
在浏览器环境中运行的代码是沙盒sandboxed,并且不直接访问本地文件系统。然后,Emscripten就创建了一个虚拟文件系统,它可以预装数据,或者链接到url来懒加载。这会影响同步文件系统函数调用以及一个项目如何被编译。关于这方面,请参见文件系统概述。
2.3 主函数死循环
浏览器事件模型使用合作模式的多任务处理——每个事件都有一个运行的“turn”,然后必须将控制权返回给浏览器事件循环,这样其他事件就可以处理了。
HTML页面挂起的一个常见原因是JavaScript未完成并且未将控制权返回给浏览器。
这将影响含有死循环的主函数的代码编写。有关更多信息,请参见Emscripten Runtime环境。
3、函数指针的问题
函数指针有三个主要的问题:
1、指针类型转换会引起指针调用失败。
针对函数声明时的签名不同,函数指针会被存储到不同的表中。当一个函数被调用时,它会在与当前函数指针签名关联的表中搜索它。如果你进行了指针类型转换,而指针和所有的表并没有被修改,则调用代码将在错误的表中查找。而错误的表中实际上很可能并没有一个叫该名字的指针,这样就出错了。
例如,一个声明为int(int)(返回int,接收int)的函数,会被添加到表FUNCTION_TABLE_ii。如果您将一个指向该函数指针投射到void(int)(不返回,接收int),那么代码将在FUNCTION_TABLE_vi中查找函数。
你可能看到编译警告:
warning: implicit declaration of function
推荐的解决方案是重构代码以避免这种情况,如下面的Asm指针转换所描述的那样。
2、当你使用-o2以及更高优化级别的时候,比较不同类型的函数指针会产生错误的结果,而错误的函数指针可能更具误导性。要检查你的代码出问题的原因,可以将aliasing_function_pointer设为零,(- s aliasing_function_pointer= 0)进行编译。
NOTE:
在asm.js中,函数指针存储在特定函数类型的表中。如FUNCTION_TABLE_ii。
在较低级别的优化中,每个函数指针在所有函数类型表上都有一个惟一的索引值(一个函数指针只在其中一个表的某个索引位置存在,在所有其他表中
这个索引位置都是一个空槽)。因此,比较函数指针(索引)能给出了一个准确的结果,但如果是试图在错误的表中调用函数指针,将会抛出一个错误,因为该索引是空的。
在-o2和更高级别的优化设置下,表被优化,以至于所有函数指针都在顺序索引中。这是一个有用的优化,因为如果没有所有空槽,表就更紧凑,
但它确实意味着函数索引不再是“全局”的惟一(因为一个函数指针在这张表中的索引位置与在另一张表中的索引位置不同了)。此时需要一张特定的表
和在这样表中的特定位置索引才能够唯一索引到一个函数。
因此,高级别的优化编译:
1、由于不同类型的函数可以有相同的索引(尽管在不同的表中),函数指针的比较可能会产生错误的结果。
2、函数指针代码中的错误更难于调试,因为它们导致错误的代码被调用,而不是显式的错误(就像在表中的“漏洞”中那样)。
3、结构体按值传递时,老版本的clang会为c和c++代码生成两种不同的代码,这两种格式的代码不兼容,你可能会收到一个警告。
解决方案是按引用传递结构体,或者不要在有结构体的位置混淆c和c++(比如,重命名.c为.cpp)。
Asm指针转换
如上所述,在asm.js模式下,函数指针必须使用正确的类型调用,否则调用将失败。这是因为在函数声明的时候,每个函数指针会基于这个函数的签名被存储在一个特定的表中: 将指针转换为另一个类型会导致调用代码在错误的位置(表)查找函数指针。
NOTE:
对于每种类型的函数指针都有一个单独的表,可以让JavaScript引擎知道每个函数指针调用的确切类型,这样也好进而优化他们。
有三种解决办法,优先选择第二种:
- 调用者在函数指针被调用之前把指针类型再转回原来的指针类型,这是有问题的因为这需要调用者知道它原来的类型是什么,而调用者实际上并不知道一个指针之前的类型是什么。
- 创建一个不需要转换的适配器函数,从适配器函数调用原始函数。
- 使用EMULATE_FUNCTION_POINTER_CASTS。当你使用-s EMULATE_FUNCTION_POINTER_CASTS=1编译时,Emscripten将发出代码来模拟运行时的函数指针类型转换,
添加额外的参数/删除参数/更改参数类型/添加或删除返回类型等。这可以增加显著的运行时开销,因此不推荐,但值得尝试。
4、特定浏览器限制
本页面列出了一些 与Emscripten编译出来的应用程序和游戏相关的 主要浏览器的最新版本之间的差异:
- 函数emscripten_get_now()以毫秒的形式返回一个wallclock time。
Opera 12.16和Windows谷歌Chrome 28.0.1500.95有一个限制,即计时器的精度仅为毫秒。
在其他主流浏览器上(IE10,firefox22,非windows的Chrome 28),也都是亚毫秒精度。
- WebGL并没有在Internet Explorer得到完全支持(至少在IE12之前)。
- Opera 12.16对W3C的File API的支持有限。特别是它不支持createObjectURL函数,
这意味着不可能使用浏览器的图像编解码器来解码Emscripten虚拟文件系统中的预加载文件。
- Emscripten中OpenAL和SDL audio的支持依赖于Web Audio API。
Emscripten代码移植系列文章
Emscripten代码移植主题系列文章是emscripten中文站点的一部分内容。
第一个主题介绍代码可移植性与限制
第二个主题介绍Emscripten的运行时环境
第三个主题第一篇文章介绍连接C++和JavaScript
第三个主题第二篇文章介绍embind
第四个主题介绍文件和文件系统
第六个主题介绍Emscripten如何调试代码
Emscripten教程之代码可移植性与限制(一)的更多相关文章
- Emscripten教程之入门指导
翻译:云荒杯倾本文是Emscripten-WebAssembly专栏系列文章之一,更多文章请查看专栏.也可以去作者的博客阅读文章.欢迎加入Wasm和emscripten技术交流群,群聊号码:93920 ...
- Emscripten教程之连接C++和JavaScript(三)
本文是Emscripten-WebAssembly专栏系列文章之一,更多文章请查看专栏.也可以去作者的博客阅读文章.欢迎加入Wasm和emscripten技术交流群,群聊号码:939206522. E ...
- Windows Live Writer教程及代码高亮工具
十分感谢六仙庵对于Windows Live Writer的教程,方便了编辑与发布,教程地址如下: http://www.cnblogs.com/liuxianan/archive/2013/04/13 ...
- Python之简单的SMTP发送邮件详细教程附代码
简介 Python发送邮件的教程本人在网站搜索的时候搜索出来了一大堆,但是都是说了一大堆原理然后就推出了实现代码,我测试用给出的代码进行发送邮件时都不成功,后面找了很久才找到原因,这都是没有一个详 ...
- ArcGIS API for JavaScript 入门教程[4] 代码的骨架
[回顾与本篇预览] 上篇简单介绍了JsAPI中的数据与视图,并告诉大家这两部分有什么用.如何有机连接在一起. 这一篇快速介绍一下前端代码的骨架.当然,假定你已经熟悉HTML5.CSS3和JavaScr ...
- Vim技能修炼教程(10) - 代码跳转
程序员功能 前面我们用了5讲的篇幅来讲基本编辑的基本功:第4讲是基本操作,第5讲是操作符,第6讲行编辑ex命令,第7讲可视模式,第8讲多窗口,第9讲缓冲区和标签页. 从这一讲开始,我们从通用功能向程序 ...
- Python Kite 使用教程 轻量级代码提示
1: 概述 今天升级annacoda 插件 spyder (4.0.0 )的时候 提示安装kite ,这是什么玩意? 下载下来试一试? 原来:就是一个代码提示插件.. 说白了" 就是让开发 ...
- TestNG入门教程-12-Java代码执行testng.xml和失败后重跑
前面我们都在IDEA上右键testng.xml文件来运行testng用例,这个在编写测试用例过程是 可以这么做,但是,如果测试用例写完了,也是这么做吗?有没有什么方法,例如自动化去实现.测试脚本维护后 ...
- gin框架教程:代码系列demo地址
gin框架教程代码地址: https://github.com/jiujuan/gin-tutorial demo目录: 01quickstart 02parameter 03route 04midd ...
随机推荐
- C# 反编译工具之dnSpy
下载地址:https://github.com/0xd4d/dnSpy/releases无需安装,和 ILSPY同门,感觉比ILSPY还强大 直接把dll拖拽到程序集资源管理器里面就可以啦
- CSDN中Markdown编辑器使用方法
Markdown编辑器 如果想学习如何使用Markdown编辑器, 可以仔细阅读这篇文章,了解一下Markdown的基本语法知识. 新的改变 CSDN中Markdown编辑器进行了一些功能拓展与语法支 ...
- Django-模板布局
- CentOS Linux服务器 挂载硬盘
1.通过fdisk -l 查看目前的硬盘信息,默认是从sda开始排,增加第二块硬盘的时候,会显示sdb,以此类推,我的是vda,vdb,以自己实际的为主,下面以sda,sdb 讲解 2.添加硬盘3.重 ...
- 实践2:如何使用word2vec和k-means聚类寻找相似的城市
理解业务 一个需求:把相似的目的地整理出来,然后可以通过这些相似目的地做相关推荐,或者是相关目的地的推荐 准备数据 Word2Vec算法:可以学习输入的文本,并输出一个词向量模型 对数据进行清洗,去出 ...
- SqlServer Split 的实现
数据库如何处理传参用指定字符隔开参数的情况 如"name1,name3,name5" 共2种方式, 1.数据库内置函数STRING_SPLIT(sql2016之前的版本不支持该函数 ...
- SpringDoc-OpenApi与Fastjson冲突——FastJsonHttpMessageConverter对String的默认处理
我的项目中默认是这样使用FastJsonHttpMessageConverter的: @Override public void configureMessageConverters(List< ...
- 添加ico图标
1. 先添加资源文件XXX.Ico,然后引用的时候用如下代码即可. Icon ico=Properties.Resources.XXX;
- 【深度学习 01】线性回归+PyTorch实现
1. 线性回归 1.1 线性模型 当输入包含d个特征,预测结果表示为: 记x为样本的特征向量,w为权重向量,上式可表示为: 对于含有n个样本的数据集,可用X来表示n个样本的特征集合,其中行代表样本,列 ...
- 《前端运维》五、k8s--3灰度发布、滚动更新与探针
一.灰度发布 灰度发布是一种发布方式,也叫金丝雀发布,起源是矿工在下井之前会先放一只金丝雀到井里,如果金丝雀不叫了,就代表瓦斯浓度高.原因是金丝雀对瓦斯气体很敏感.灰度发布的做法是:会在现存旧应用的基 ...