Emscripten教程之代码可移植性与限制(一)

Emscripten教程之代码可移植性与限制(一)

翻译:云荒杯倾
本文是Emscripten-WebAssembly专栏系列文章之一,更多文章请查看专栏。
也能够去做者的博客阅读文章。
欢迎加入Wasm和emscripten技术交流群,群聊号码:939206522。html

Emscripten代码移植主题涵盖了将C、C++代码移植到Emscripten时须要考虑的全部核心考虑问题,以及通常的编码和调试指南。
共有如下主题。c++

代码可移植性与限制、emscripten的运行环境、链接C++与JavaScript、文件和文件系统、多媒体数据和图形、调试、多线程编程支持、移植SIMD代码、Asyncify、Emterpreter

每一部份内容都比较多,本文主要讲第一部分,代码可移植性与限制。下面是正文:git

代码可移植性与限制

Emscripten几乎能够编译任何可移植的c/c++代码到JavaScript。但因为浏览器环境限制和Emscripten编译出来的代码的限制,一些代码为了能被编译须要作改动,本文就帮咱们找出这部分代码。程序员

一、关于可移植性的指导


本节解释了哪些类型的代码是不可移植的(或者更难于移植);哪些代码能够编译,但会运行得很慢。开发人员可使用这些信息来评估移植代码和重写代码的工做量。github

1.1 不能编译的代码

为了使Emscripten工做,下面类型的代码须要重写。(理论上,在使用模拟的状况下,可使用Emscripten解决这些问题,但速度很是慢。)web

  • 代码是多线程的,并使用共享状态。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).segmentfault

  • 扫描寄存器或堆栈的代码。由于在寄存器或堆栈上的变量多是被放置到一个并不能被扫描的js局部变量里保存的。
NOTE:
若是你是一个喜欢本身写垃圾回收程序的程序员,可能对这类代码比较熟。。。
  • 具备特定于体系结构的内联汇编(好比包含x86代码的asm())的代码是不可移植的。这段代码须要用可移植的C或C++来替换。有时,代码库会将可移植的代码和可选的内联程序集写在一块儿做为优化,你须要找到一个选项使内联汇编代码不可用。

1.2 能编译可是运行得比较慢的代码

Note:
当你要优化代码的时候,就会知道了解这些事项是有用的。

下面类型的代码会被编译,可是可能运行的很慢:windows

  • 64位整型变量。数学运算(+,-,*,/)是慢的,由于它们是被模拟的。这是由于JavaScript没有本地64位int类型,所以这是不可避免的。
  • C++异常。在JavaScript中,这些代码一般会使JavaScript引擎关闭各类优化。所以,在- o1和上面的默认状况下,异常会被关闭。要从新启用它们,请运行emcc与- s DISABLE_EXCEPTION_CATCHING= 0。
  • setjmp also prevents relooping around it,迫使咱们使用一种效率较低的方法来模拟控制流。

二、API限制


浏览器环境和JavaScript不一样于C/C++一般运行的本地环境。这些差别对如何调用和使用本地API施加了一些限制。本部分列出了一些比较明显的限制。api

2.1 网络

Emscripten支持libc库的网络函数,但您必须限制他们是异步(非阻塞)操做。这是由于底层的JavaScript网络函数是异步的。

2.2 文件系统

Emscripten支持libc文件系统函数,C /C++代码能够以正常方式编写。

在浏览器环境中运行的代码是沙盒sandboxed,而且不直接访问本地文件系统。而后,Emscripten就建立了一个虚拟文件系统,它能够预装数据,或者连接到url来懒加载。这会影响同步文件系统函数调用以及一个项目如何被编译。关于这方面,请参见文件系统概述

2.3 主函数死循环

浏览器事件模型使用合做模式的多任务处理——每一个事件都有一个运行的“turn”,而后必须将控制权返回给浏览器事件循环,这样其余事件就能够处理了。
HTML页面挂起的一个常见缘由是JavaScript未完成而且未将控制权返回给浏览器。

这将影响含有死循环的主函数的代码编写。有关更多信息,请参见Emscripten Runtime环境

三、函数指针的问题


函数指针有三个主要的问题:
一、指针类型转换会引发指针调用失败。

针对函数声明时的签名不一样,函数指针会被存储到不一样的表中。当一个函数被调用时,它会在与当前函数指针签名关联的表中搜索它。若是你进行了指针类型转换,而指针和全部的表并无被修改,则调用代码将在错误的表中查找。而错误的表中实际上极可能并无一个叫该名字的指针,这样就出错了。

例如,一个声明为int(int)(返回int,接收int)的函数,会被添加到表FUNCTION_TABLE_ii。若是您将一个指向该函数指针投射到void(int)(不返回,接收int),那么代码将在FUNCTION_TABLE_vi中查找函数。

你可能看到编译警告:

warning: implicit declaration of function

推荐的解决方案是重构代码以免这种状况,以下面的Asm指针转换所描述的那样。

二、当你使用-o2以及更高优化级别的时候,比较不一样类型的函数指针会产生错误的结果,而错误的函数指针可能更具误导性。要检查你的代码出问题的缘由,能够将aliasing_function_pointer设为零,(- s aliasing_function_pointer= 0)进行编译。

NOTE:
在asm.js中,函数指针存储在特定函数类型的表中。如FUNCTION_TABLE_ii。

在较低级别的优化中,每一个函数指针在全部函数类型表上都有一个唯一的索引值(一个函数指针只在其中一个表的某个索引位置存在,在全部其余表中
这个索引位置都是一个空槽)。所以,比较函数指针(索引)能给出了一个准确的结果,但若是是试图在错误的表中调用函数指针,将会抛出一个错误,由于该索引是空的。

在-o2和更高级别的优化设置下,表被优化,以致于全部函数指针都在顺序索引中。这是一个有用的优化,由于若是没有全部空槽,表就更紧凑,
但它确实意味着函数索引再也不是“全局”的唯一(由于一个函数指针在这张表中的索引位置与在另外一张表中的索引位置不一样了)。此时须要一张特定的表
和在这样表中的特定位置索引才可以惟一索引到一个函数。

所以,高级别的优化编译:
一、因为不一样类型的函数能够有相同的索引(尽管在不一样的表中),函数指针的比较可能会产生错误的结果。
二、函数指针代码中的错误更难于调试,由于它们致使错误的代码被调用,而不是显式的错误(就像在表中的“漏洞”中那样)。

三、结构体按值传递时,老版本的clang会为c和c++代码生成两种不一样的代码,这两种格式的代码不兼容,你可能会收到一个警告。
解决方案是按引用传递结构体,或者不要在有结构体的位置混淆c和c++(好比,重命名.c为.cpp)。

Asm指针转换

如上所述,在asm.js模式下,函数指针必须使用正确的类型调用,不然调用将失败。这是由于在函数声明的时候,每一个函数指针会基于这个函数的签名被存储在一个特定的表中: 将指针转换为另外一个类型会致使调用代码在错误的位置(表)查找函数指针。

NOTE:
对于每种类型的函数指针都有一个单独的表,可让JavaScript引擎知道每一个函数指针调用的确切类型,这样也好进而优化他们。

有三种解决办法,优先选择第二种:

  • 调用者在函数指针被调用以前把指针类型再转回原来的指针类型,这是有问题的由于这须要调用者知道它原来的类型是什么,而调用者实际上并不知道一个指针以前的类型是什么。
  • 建立一个不须要转换的适配器函数,从适配器函数调用原始函数。
  • 使用EMULATE_FUNCTION_POINTER_CASTS。当你使用-s EMULATE_FUNCTION_POINTER_CASTS=1编译时,Emscripten将发出代码来模拟运行时的函数指针类型转换,

添加额外的参数/删除参数/更改参数类型/添加或删除返回类型等。这能够增长显著的运行时开销,所以不推荐,但值得尝试。

四、特定浏览器限制


本页面列出了一些 与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如何调试代码

相关文章
相关标签/搜索