번역 작업 진행중입니다.

 

Emscripten과 같은 도구를 사용하여 다른 언어의 모듈을 컴파일했거나 코드를 직접로드하여 실행 해봤다면 다음 단계에서는 WebAssembly JavaScript API의 다른 기능을 사용하는 방법에 대해 자세히 알아 봅니다. 

Note: 여기에서 언급한 기본 개념에 익숙하지 않거나 더 많은 설명이 필요한 경우 WebAssembly concepts를 먼저 읽어보세요.

Some simple examples

WebAssembly JavaScript API를 사용하는 방법과 웹 페이지에서 wasm 모듈을 로드하는 방법을 설명하는 몇 가지 예제를 실행 해 보겠습니다.

Note: 샘플 코드는 webassembly-examples GitHub repo에서 찾을 수 있습니다.

Preparing the example

  1. 먼저 wasm 모듈이 필요합니다! 우리의 simple.wasm 파일을 잡고 로컬 컴퓨터의 새 디렉토리에 복사본을 저장하세요.
  2. 다음으로, wasm 파일과 동일한 디렉토리에 index.html이라는 간단한 HTML 파일을 작성해보세요 (simple template을 참고하여 쉽게 시작 할 수 있습니다.)
  3. 이제 여기서 무슨 일이 벌어지는지 이해하기 쉽도록 하기위해 wasm 모듈 (Converting WebAssembly format to wasm을 참고)의 텍스트 표현을 살펴 보겠습니다.
     
    (module
      (func $i (import "imports" "imported_func") (param i32))
      (func (export "exported_func")
        i32.const 42
        call $i))
  4. 두 번째 줄에서 2단계의 import 네임스페이스가 있습니다. 즉, 내부 기능 $iimports.imported_func에서 가져옴을 알 수 있습니다. wasm 모듈로 가져올 객체를 작성할 때 JavaScript에서 이 2단계 네임스페이스를 반영해야 합니다.
    HTML 파일에 <script></script>요소를 만들고 다음 코드를 추가합니다.
     
    var importObject = {
      imports: { imported_func: arg => console.log(arg) }
    };

Streaming the WebAssembly module

Firefox 58의 새로운 기능으로 기본 소스에서 직접 WebAssembly 모듈을 컴파일하고 인스턴스화하는 기능이 있습니다. 이는 WebAssembly.compileStreaming() 와 WebAssembly.instantiateStreaming() 메소드를 사용하여 수행됩니다. 이 메소드는 바이트 코드를 직접 Module/Instance 인스턴스로 변환 할 수 있기 때문에 스트리밍이 아닌 방식의 메소드보다 작성이 쉽습니다. 별도로 ResponseArrayBuffer로 호출할 필요성을 업애줍니다.

이 예제는 (GitHub의 instantiate-streaming.html 데모와 view it live를 보세요) instantiateStreaming()을 사용하여 wasm 모듈을 가져오고, JavaScript 함수를 가져오고, 컴파일하고 인스턴스화하며, 내 보낸 함수에 액세스하는 방법을 한번에 보여줍니다.

첫 번째 블록 아래에 다음을 추가하십시오.

WebAssembly.instantiateStreaming(fetch('simple.wasm'), importObject)
.then(obj => obj.instance.exports.exported_func());

그 결과, 내 보낸 WebAssembly 함수 인 export_func를 호출합니다.이 함수는 가져온 JavaScript 함수 imported_func를 호출합니다.이 함수는 WebAssembly 인스턴스 (42) 내부에 제공된 값을 콘솔에 기록합니다. 지금 예제 코드를 저장하고 WebAssembly를 지원하는 브라우저에 로드하면 이를 실제로 볼 수 있습니다!

 

Note: 이것은 매우 난해하고, 길고 지루한 예이지만 웹 응용프로그램에서 JavaScript와 함께 WebAssembly 코드를 사용하여 가능한 것을 설명하는 데 도움이 됩니다. 우리가 다른 곳에서 언급했듯이, WebAssembly는 JavaScript를 대체하려는 것이 아니라, 그 두 개가 상호 작용하여 서로의 강점을 이끌어 낼수 있습니다.

Loading our wasm module without streaming

위에서 설명한 스트리밍 방법을 사용할 수 없거나 사용하지 않으려면 스트리밍하지 않는 메서드WebAssembly.compile / WebAssembly.instantiate를 대신 사용할 수 있습니다.

이 메소드는 바이트 코드에 직접 액세스하지 않으므로 wasm 모듈을 컴파일 / 인스턴스화하기 전에 응답을 ArrayBuffer로 변환하는 추가 단계가 필요합니다.

이와 동등한 코드는 다음과 같습니다.

fetch('simple.wasm').then(response =>
  response.arrayBuffer()
).then(bytes =>
  WebAssembly.instantiate(bytes, importObject)
).then(results => {
  results.instance.exports.exported_func();
});

Viewing wasm in developer tools

Firefox 54+에서 Developer Tool Debugger Panel에는 웹 페이지에 포함된 모든 wasm 코드의 텍스트 표현을 표시하는 기능이 있습니다. Debugger 패널로 이동하여 "wasm://" 항목을 클릭할 수 있습니다.

Firefox에서 WebAssembly를 텍스트로 보는 것 외에도 텍스트 형식을 사용하여 개발자는 WebAssembly를 디버깅할 수 있습니다(breakpoint, callstack 검사, 단일 단계 검사 등). 비디오 미리 보기는 WebAssembly debugging with Firefox DevTools을 참조하십시오.

Memory

WebAssembly의 저수준 메모리 모델에서 메모리는 선형 메모리라고하는 형식이 지정되지 않은 바이트의 연속 범위로 표시되며 load and store instructions에 의해 모듈 내부에서 읽고 쓰여집니다. 이 메모리 모델에서 모든로드 또는 저장소는 Linear Memory의 모든 바이트에 액세스 할 수 있으므로 포인터와 같은 C / C ++ 개념을 충실하게 표현하는 데 필요합니다.

그러나 네이티브 C / C ++ 프로그램과 달리 사용 가능한 메모리 범위가 전체 프로세스에 걸쳐있는 경우 특정 WebAssembly 인스턴스가 액세스 할 수있는 메모리는 WebAssembly 메모리 객체에 포함 된 특정 범위 (잠재적으로 매우 작은 범위)로 제한됩니다. 이를 통해 단일 웹 응용 프로그램은 WebAssembly를 내부적으로 사용하는 여러 독립 라이브러리를 사용하여 서로 완전히 격리 된 별도의 메모리를 가질 수 있습니다.

자바 스크립트에서 Memory 인스턴스는 크기를 조정할 수있는 ArrayBuffer로 생각할 수 있으며, ArrayBuffers와 마찬가지로 하나의 웹 앱에서 많은 독립적 인 Memory 객체를 만들 수 있습니다. WebAssembly.Memory() 생성자를 사용하여 생성 할 수 있습니다. 생성자는 인수로 초기 크기와 (선택적으로) 최대 크기를 인수로 취합니다.

빠르게 예제를 살펴봅시다.

  1. 새로운 간단한 HTML 페이지를 만들고 (simple template을 복사하십시오) memory.html을 호출하십시오. <script></script> 요소를 페이지에 추가하십시오.

  2. 이제 스크립트 맨 위에 다음 행을 추가하여 메모리 인스턴스를 만듭니다.

    var memory = new WebAssembly.Memory({initial:10, maximum:100});

    initialmaximum 단위는 WebAssembly 페이지이며 크기는 64KB로 고정되어 있습니다. 즉, 위 메모리 인스턴스의 초기 크기는 640KB이고 최대 크기는 6.4MB입니다.

    WebAssembly 메모리는 ArrayBuffer를 반환하는 버퍼 getter / setter를 제공함으로써 바이트를 노출합니다. 예를 들어 선형 메모리의 첫 번째 단어에 직접 42를 쓰려면 다음과 같이하면됩니다.

    new Uint32Array(memory.buffer)[0] = 42;

    그런 다음 다음을 사용하여 동일한 값을 반환 할 수 있습니다.

    new Uint32Array(memory.buffer)[0]
  3. 데모에서 지금 사용해보십시오. 지금까지 추가 한 내용은 저장하고 브라우저에로드 한 다음 JavaScript 콘솔에 위의 두 줄을 입력 해보십시오.

Growing memory

메모리 인스턴스는 Memory.prototype.grow()를 호출하여 확장 할 수 있습니다. 여기서 다시 인수는 WebAssembly 페이지 단위로 지정됩니다.

memory.grow(1);

메모리 인스턴스 생성시 최대 값이 제공되면이 최대 값을 초과하여 증가하려고 시도하면 WebAssembly.RangeError 예외가 발생합니다. 엔진은 이 상한값을 이용하여 미리 메모리를 예약하므로 크기를 보다 효율적으로 조정할 수 있습니다.

ArrayBuffer의 byteLength는 불변이므로, Memory.prototype.grow() 오퍼레이션이 성공하면, 버퍼 getter는 (새로운 byteLength로) 새로운 ArrayBuffer 객체를 돌려 주어, 이전의 모든 ArrayBuffer 객체는 「detached」가되거나, 이전에 가리켰던 기본 메모리와의 접속이 끊어집니다.

함수와 마찬가지로 선형 메모리를 모듈 내부에서 정의하거나 가져올 수 있습니다. 마찬가지로 모듈은 메모리를 선택적으로 내보낼수도 있습니다. 즉, JavaScript는 새 WebAssembly.Memory를 만들고 가져 오기로 전달하거나 Instance.prototype.exports를 통해 메모리 내보내기를 수신하여 WebAssembly 인스턴스의 메모리에 액세스 할 수 있습니다.

More involved memory example

앞서 정의한 메모리 인스턴스를 가져 와서 정수 배열로 채운 다음 더 합친 WebAssembly 모듈을 통해 더 많은 관련 메모리 예제를 살펴봄으로써 위의 내용을 보다 자세히 알아 보겠습니다. memory.wasm에서 찾을 수 있습니다.

  1. memory.wasm을 이전과 같이 같은 폴더에 복사합니다.

    Note: memory.wat에서 모듈의 텍스트 표현을 볼 수 있습니다.

  2. memory.html 샘플 파일로 돌아가서 이전처럼 wasm 모듈을 가져 와서 컴파일하고 인스턴스화합니다. - 스크립트의 맨 아래에 다음을 추가하세요.

    WebAssembly.instantiateStreaming(fetch('memory.wasm'), { js: { mem: memory } })
    .then(obj => {
      // add code here
    });
  3. Since this module exports its memory, given an Instance of this module called instance we can use an exported function accumulate() to create and populate an input array directly in the module instance’s linear memory (mem). Add the following into your code, where indicated: 

    var i32 = new Uint32Array(memory.buffer);
    
    for (var i = 0; i < 10; i++) {
      i32[i] = i;
    }
    
    var sum = results.instance.exports.accumulate(0, 10);
    console.log(sum);

Note how we create the Uint32Array view on the Memory object’s buffer (Memory.prototype.buffer), not on the Memory itself.

Memory imports work just like function imports, only Memory objects are passed as values instead of JavaScript functions. Memory imports are useful for two reasons:

  • They allow JavaScript to fetch and create the initial contents of memory before or concurrently with module compilation.
  • They allow a single Memory object to be imported by multiple module instances, which is a critical building block for implementing dynamic linking in WebAssembly.

Note: You can find our complete demo at memory.html (see it live also) — this version uses the fetchAndInstantiate() function.

Tables

A WebAssembly Table is a resizable typed array of references that can be accessed by both JavaScript and WebAssembly code.  While Memory provides a resizable typed array of raw bytes, it is unsafe for references to be stored in a Memory since a reference is an engine-trusted value whose bytes must not be read or written directly by content for safety, portability, and stability reasons.

Tables have an element type, which limits the types of reference that can be stored in the table.  In the current iteration of WebAssembly, there is only one type of reference needed by WebAssembly code — functions — and thus only one valid element type.  In future iterations, more element types will be added.

Function references are necessary to compile languages like C/C++ that have function pointers.  In a native implementation of C/C++, a function pointer is represented by the raw address of the function’s code in the process’s virtual address space and so, for the safety reasons mentioned above, cannot be stored directly in linear memory.  Instead, function references are stored in a table and their indexes, which are integers and can be stored in linear memory, are passed around instead.

When the time comes to call a function pointer, the WebAssembly caller supplies the index, which can then be safety bounds checked against the table before indexing and calling the indexed function reference.  Thus, tables are currently a rather low-level primitive used to compile low-level programming language features safely and portably.

Tables can be mutated via Table.prototype.set(), which updates one of the values in a table, and Table.prototype.grow(), which increases the number of values that can be stored in a table.  This allows the indirectly-callable set of functions to change over time, which is necessary for dynamic linking techniques.  The mutations are immediately accessible via Table.prototype.get() in JavaScript, and to wasm modules.

A table example

Let’s looking at an simple table example — a WebAssembly module that creates and exports a table with two elements: element 0 returns 13 and element 1 returns 42. You can find this at table.wasm.

  1. Make a local copy of table.wasm in a new directory.

    Note: You can see the module’s text representation at table.wat.

  2. Create a new copy of our HTML template in the same directory and call it table.html.

  3. As before, fetch, compile, and instantiate your wasm module — add the following into a <script> element at the bottom of your HTML body:

    WebAssembly.instantiateStreaming(fetch('table.wasm'))
    .then(function(obj) {
      // add code here
    });
  4. Now let’s access the data in the tables — add the following lines to your code in the indicated place:

    var tbl = results.instance.exports.tbl;
    console.log(tbl.get(0)());  // 13
    console.log(tbl.get(1)());  // 42

This code accesses each function reference stored in the table in turn, and instantiates them to print the values they hold to the console — note how each function reference is retrieved with a Table.prototype.get() call, then we add an extra set of parentheses on the end to actually invoke the function.

Note: You can find our complete demo at table.html (see it live also) — this version uses the fetchAndInstantiate() function.

Globals

WebAssembly has the ability to create global variable instances, accessible from both JavaScript and importable/exportable across one or more WebAssembly.Module instances. This is very useful, as it allows dynamic linking of multiple modules.

To create a WebAssembly global instance from inside your JavaScript, you use the WebAssembly.Global() constructor, which looks like this:

const global = new WebAssembly.Global({value:'i32', mutable:true}, 0);

You can see that this takes two parameters:

  • An object that contains two properties describing the global variable:
    • value: its data type, which can be any data type accepted within WebAssembly modules — i32i64f32, or f64.
    • mutable: a boolean defining whether the value is mutable or not.
  • A value containing the variable's actual value. This can be any value, as long as its type matches the specified data type.

So how do we use this? In the following example we define a global as a mutable i32type, with a value of 0.

The value of the global is then changed, first to 42 using the Global.value property, and then to 43 using the incGlobal() function exported out of the global.wasmmodule (this adds 1 to whatever value is given to it and then returns the new value).

const output = document.getElementById('output');

function assertEq(msg, got, expected) {
    output.innerHTML += `Testing ${msg}: `;
    if (got !== expected)
        output.innerHTML += `FAIL!<br>Got: ${got}<br>Expected: ${expected}<br>`;
    else
        output.innerHTML += `SUCCESS! Got: ${got}<br>`;
}

assertEq("WebAssembly.Global exists", typeof WebAssembly.Global, "function");

const global = new WebAssembly.Global({value:'i32', mutable:true}, 0);

WebAssembly.instantiateStreaming(fetch('global.wasm'), { js: { global } })
.then(({instance}) => {
    assertEq("getting initial value from wasm", instance.exports.getGlobal(), 0);
    global.value = 42;
    assertEq("getting JS-updated value from wasm", instance.exports.getGlobal(), 42);
    instance.exports.incGlobal();
    assertEq("getting wasm-updated value from JS", global.value, 43);
});

Note: You can see the example running live on GitHub; see also the source code.

Multiplicity

Now we’ve demonstrated usage of the main key WebAssembly building blocks, this is a good place to mention the concept of multiplicity. This provides WebAssembly with a multitude of advances in terms of architectural efficiency:

  • One module can have N Instances, in the same way that one function literal can produce N closure values.
  • One module instance can use 0–1 memory instances, which provide the "address space" of the instance. Future versions of WebAssembly may allow 0–N memory instances per module instance (see Multiple Tables and Memories).
  • One module instance can use 0–1 table instances — this is the "function address space" of the instance, used to implement C function pointers. Future versions of WebAssembly may allow 0–N table instances per module instance in the future.
  • One memory or table instance can be used by 0–N module instances — these instances all share the same address space, allowing dynamic linking.

You can see multiplicity in action in our Understanding text format article — see the Mutating tables and dynamic linking section.

Summary

This article has taken you through the basics of using the WebAssembly JavaScript API to include a WebAssembly module in a JavaScript context and make use of its functions, and how to use WebAssembly memory and tables in JavaScript. We also touched on the concept of multiplicity.

See also

문서 태그 및 공헌자

이 페이지의 공헌자: limkukhyun
최종 변경자: limkukhyun,