Sign in Join
Wiki copytemporary

WebAssembly/Internals: what changed

Revision 0 to 1. Removed lines have a minus sign; added lines have a plus sign.

Title: WebAssembly/Internals

OldNewLine
1+## Outdated page
2+
3+This page is outdated. It is currently used as a content staging area during refactoring of the various pages.
4+
5+## Assemblers
6+
7+There are different assemblers available, from Wabt, emscripten.org and LLVM. The expected format is a slightly different between those two:
8+
9+### wat2wasm (Wabt)
10+
11+Example:
12+
13+``` lisp
14+ (module
15+ (func $add (param $lhs i32) (param $rhs i32) (result i32)
16+ local.get $lhs
17+ local.get $rhs
18+ i32.add
19+ )
20+ (export "add" (func $add))
21+ )
22+```
23+
24+According to the official site "Wabt" is using it's own format of the Wasm. It's slightly different from the official documentation. The most current version of Wabt matches the specs, as well as supports the old syntax.
25+
26+Online studio, that's using the older version of wabt syntax. <https://webassembly.studio/>
27+
28+For example. instead of
29+
30+`local.get`
31+
32+it's using
33+
34+`get_local`
35+
36+### wasm-as (emscripten)
37+
38+The assembler is recommended for the use in a compiler by the WebAssembly.org
39+
40+``` lisp
41+ (module
42+ (func $add (param $lhs i32) (param $rhs i32) (result i32)
43+ (
44+ local.get $lhs
45+ local.get $rhs
46+ i32.add
47+ )
48+ )
49+ (export "add" (func $add))
50+ )
51+```
52+
53+### llvm-mc
54+
55+This is the assembler from the LLVM project. It uses a GAS-like syntax.
56+
57+## Use on the Wiki
58+
59+WebAssembly is using s-expressions as its textual format (for either Wabt or Emscript) . it's handy to use syntax highlighter for the code and use "lisp" language to set colors.
60+
61+ <source lang="lisp">
62+ ;; web assembly goes here
63+ </source>
64+
65+## Limitations
66+
67+- WebAssembly doesn't have a stand-alone executable format. It's always a "plugin" to some environment. Thus the compiler currently only supports producing a **library** and program should not be used. Using **library** requires exporting a certain functions from the library. Those functions will be the "export" part of Wasm module as well.
68+- WebAssembly format doesn't provide any special information for static linking (for the purpose of combining multiple .wasm files into one). No support is provided at this time, thus the entire Pascal code should be put in a single library file.
69+- Importing functions into Pascal is supported partially; see External.
70+- A memory object must always be provided externally, using the module **env** with the name **memory**. This is hard-coded in the Wasm assembler. (Todo: needs to be configurable.)
71+
72+## Validation
73+
74+There's a certain requirements to the binary code of WebAssembly.
75+
76+The validation is described at the specification <http://webassembly.github.io/spec/core/valid/index.html>
77+
78+A code that satisfies the validation rules, is considered to be well-formed
79+
80+It's possible to generate a binary file with the code that doesn't satisfy the validation steps. However, the browsers would not instantiate such binary file. Neither any other host should.
81+
82+In some cases, there might be an overhead to create well-formed code.
83+
84+## Global Symbols
85+
86+Global symbols are stored in **global** variables. Each variable is of i32 type and stores an offset in memory, where the object is usually stored.
87+
88+In order to get the address of the variable, one should invoke get_global for the desired symbols. The memory offset if predefined and cannot be changed during the runtime. The contents of the memory can be changed.
89+
90+## Code Branching/Flow Control
91+
92+*todo: rewrite to the better explanation*
93+
94+WebAssembly doesn't allow direct jumps to (address/label)/or jump by offset. It only allows to jump "out-of" a code block, where the code block would be identified by a label. I.e. beginning of the loop label (for **continue**) or end of the loop label (for **break**). Loop itself is a single code block, known as "loop" in WebAssembly ).
95+
96+FPC basic implementation is based of the ability of conditional jump instructions to a specified label. For that reason, the default implementation of TCGIfNode doesn't apply. Also, not every conditional jump instructions can have a label assign to it. Only instructions that jump out of the block are should use labels. Other, simply rely on the fact, that the next instruction IS the point they needs to be.
97+
98+### IF-block
99+
100+Wasm IF do return a value (one of the basic WebAsm types). Pascal IF-statement does not return a value.
101+
102+Thus the feature of value returning is not used (**todo:** but should be).
103+
104+A dummy 0 value is pushed on the stack. IF is hard-coded to return i32 at all times.
105+
106+According to the official documentation, IF always comes with "ELSE" block. However, some earlier versions do allow IF without an else statement. (which is also allowed in Binary specification of WASM). Such IF comes without any resulting type, and the execution of IF should leave stack unmodified in the end.
107+
108+At this time "ELSE" branch is always generated for any IF statement. However, if there's no actual ELSE code (in pascal), a single "nop" instruction and a dummy result value produced.
109+
110+``` pascal
111+function cmp(a,b: integer): Integer;
112+begin
113+ if a > b then
114+ cmp := 1
115+ else
116+ cmp := 0;
117+end;
118+```
119+
120+turns into:
121+
122+``` lisp
123+(func $cmp
124+ (param i32)
125+ (param i32)
126+ (result i32)
127+ (local i32) ;; Temp 2,1 allocated
128+ (local i32) ;; Temp 3,1 allocated
129+;; [3] begin
130+;; [4] if a > b then
131+ get_local 0
132+ set_local 3
133+ get_local 3
134+ get_local 1
135+ i32.gt_s
136+ if (result i32) ;; hard-coded i32 type of IF result
137+;; [5] cmp := 1
138+ i32.const 1
139+ set_local 2
140+ i32.const 0 ;; mandatory wasm-IF result
141+ else
142+;; [7] cmp := 0;
143+ i32.const 0
144+ set_local 2
145+ i32.const 0 ;; mandatory wasm-IF result
146+ end
147+ drop ;; dropping IF-result from the stack, as unused
148+;; [8] end;
149+ get_local 2
150+ return
151+)
152+```
153+
154+The code generation of **cifnode** is overridden.
155+
156+### Loop-block
157+
158+A loop is a block of instructions that has the label set to the beginning of the code.
159+
160+Here is an example of a loop. The function tries to sum up a number X amount of times
161+
162+``` pascal
163+function mulbyadd(Num, Cnt: integer): integer;
164+ Result := 0;
165+ while Cnt>0 do begin
166+ dec(Cnt);
167+ Result := Result + Num;
168+ end;
169+end;
170+```
171+
172+\<source lang="lisp\>
173+
174+` (func $mulbyadd (param $Num i32) (param $Cnt i32) (result i32)`\
175+`   (local i32)       ;; local variable (that gets index #2) this is $Result`\
176+`   i32.const 0`\
177+`   set_local 2`
178+
179+`   block`\
180+`   loop `\
181+`     get_local $Cnt  ;; comparing Cnt to 0`\
182+`     i32.const 0     ;; `\
183+`     i32.le_s         `\
184+`     br_if 1         ;; if Cnt is less or equal to 0, then we should exit the loop`
185+
186+`     get_local $Cnt  ;; decreasing Cnt`\
187+`     i32.const 1 `\
188+`     i32.sub  `\
189+`     set_local $Cnt  `
190+
191+`     get_local $Num  ;;`\
192+`     get_local 2     ;; `\
193+`     i32.add         ;;`\
194+`     set_local 2     ;; Result := Result + Num`
195+
196+`     br 0            ;; loop back to the beginning `\
197+`   end `\
198+`   end`\
199+`   get_local 2       ;; pushing the result to the stack (for the return value)`\
200+`   )`
201+
202+### FPC Labels
203+
204+Since branching is **relative** in Wasm (the absolute goto-like is only a proposed feature for Wasm), the use of TAsmLabel is different than other targets. The field **labelnr** is changed to indicate the relative jump. Typically 0 - to return to the loop or 1 - to jump out of the loop - or N to exit the function.
205+
206+## External
207+
208+One can declare an external function. For a WebAssembly module the function must be provided during a module instantiation. Any external function must have a library name specified (Static linking is not supported!). The library name and the "exteral" name will be used during instantiation phase.
209+
210+Only basic types can be used as parameters (integer, floats, memory pointers) and function result.
211+
212+Here's an example:
213+
214+The code tries to use an external function that accepts only 1 integer parameter. (The function should write out the parameter)
215+
216+``` pascal
217+library testext;
218+
219+procedure logint(i: integer); external 'env' name 'logint'; // name of external module and function name
220+
221+procedure run;
222+begin
223+ logint(304);
224+end;
225+
226+exports
227+ run name 'run';
228+end.
229+```
230+
231+In the browser, during instantiation the function must be provided:
232+
233+``` js
234+const importObject = {
235+ // note "env" should match the name of the function's library used in the pascal
236+ // thus it's possible to import functions from different sources.
237+ "env": {
238+
239+ "memory": new WebAssembly.Memory({initial:32, maximum:32})
240+ // this is the implementation of "logint" function
241+ // it accepts exactly 1 parameter, and puts it into a the browser's console
242+ ,"logint": function(k) { console.log(k) }
243+ }
244+
245+}
246+fetch('../out/main.wasm').then(response =>
247+ response.arrayBuffer()
248+).then(bytes => WebAssembly.instantiate(bytes, importObject)).then(results => {
249+ instance = results.instance;
250+
251+ document.getElementById("container").textContent =
252+ // running the export run() function
253+ instance.exports.run();
254+}).catch(console.error);
255+```
256+
257+### PChar Strings
258+
259+Strings would require a more work from the client side. There's no "string" type in Wasm, thus strings are treated as chunks in memory. From Javascript perspective that means, that a reference to a memory is received from a Wasm code and should be converted to JS string (that typically involves a text decoder)
260+
261+``` pascal
262+library testext;
263+
264+type
265+ pchar = ^char;
266+
267+procedure logstr(p: pchar); external 'env' name 'logstr';
268+procedure writedom(p: pchar); external 'dom' name 'writedom';
269+
270+procedure run;
271+begin
272+ logstr('hello');
273+ logstr('world');
274+ writedom('<b>Hello World</b> and <input type="button" value="FPC">');
275+end;
276+
277+exports
278+ run name 'run';
279+
280+end.
281+```
282+
283+On the host side the function needs to read the memory and prepare the string for the output:
284+
285+``` javascript
286+const memory = new WebAssembly.Memory({initial:32, maximum:32});
287+
288+// the function takes, strofs that indicates the start of string
289+// and then tries to get the string out of the memory, by searching for null-terminating character
290+function getStr(strofs)
291+{
292+ var arr = new Uint8Array(memory.buffer,strofs);
293+ for (var i=0; i < arr.length; i++)
294+ if (arr[i]==0) // null terminating char is found
295+ arr = arr.subarray(0, i);
296+ var str = new TextDecoder("utf-8").decode(arr);
297+ return str;
298+}
299+
300+const importObject = {
301+ "env": {
302+ "memory": memory
303+ ,"logstr": function(k)
304+ { console.log(getStr(k)); }
305+ },
306+ "dom": {
307+ "writedom": function(k) { document.write(getStr(k)) }
308+ }
309+}
310+fetch('../out/main.wasm').then(response =>
311+ response.arrayBuffer()
312+).then(bytes => WebAssembly.instantiate(bytes, importObject)).then(results => {
313+ instance = results.instance;
314+ document.getElementById("container").textContent = instance.exports.run();
315+}).catch(console.error);
316+```
317+
318+Obviously pascal Ansi or WideStrings can work in the similar manner, and can be more efficient (as the size of the string is known ahead of time). However, strings can only be passed as constant strings, rather than a string variable for change (as implementing a reference count between Wasm and JS can be a bit more complicated)
319+
320+## Object File Contents
321+
322+There's **wasm-objdump** that's useful for inspection of a generated object file.
323+
324+## Static Linking
325+
326+See LLD linker: <https://lld.llvm.org/WebAssembly.html> <https://github.com/WebAssembly/tool-conventions/blob/master/Linking.md>
327+
328+So far, there's only 1 wasm linker exists: lld (aka wasm-ld). LLVM linked ported for WASM.
329+
330+There's no official specification on object linking in WebAssembly. Tools are typically a common convention with a special "linking" section.
331+
332+One should keep in mind, that "linking" section is binary format specific. There are no way to explicitly define it via textual format. (which imposes some risk, when the implementation of a particular assembler changes)
333+
334+### Linking section
335+
336+For each linkable part of the code (i.e. function) an entry must be created in "linking" section.
337+
338+### Linking Symbols
339+
340+In order for **wasm-ld** to successfully connect two symbols, the following approach must be taken:
341+
342+- a symbol declared in **implementation** section
343+
344+`WASM_SYM_VISIBILITY_HIDDEN `\
345+`WASM_SYM_BINDING_LOCAL `
346+
347+This indicates that the symbol should not be linked anywhere, except for within the object file itself.
348+
349+- the unit declaring a symbol in **interface** section (linkable by other units) should declare the unit with flags set to:
350+
351+`WASM_SYM_VISIBILITY_HIDDEN `
352+
353+This indicates that while the symbol can be linked to other object files, but should not be part of the linked library (executable)
354+
355+- a same symbol that's being used by a different unit, should have a stub function (with **unreachable** in its body) and have the following flags set:
356+
357+`WASM_SYM_BINDING_WEAK`\
358+`WASM_SYM_VISIBILITY_HIDDEN`
359+
360+This indicates to **wasm-ld** linker to get rid of the **unreachable** stub and use the actual implementation.
361+
362+- a symbols that's explicitly declared in **exports** section of a library should not have a specific flags. However, the following flag can be also be used:
363+
364+`WASM_SYM_EXPORTED`
365+
366+- an external symbol (that's imported from the host) should be marked by
367+
368+`WASM_SYM_UNDEFINED `
369+
370+The flag is automatically set by **wat2wasm** for every **import** function.
371+
372+### Tools
373+
374+**wat2wasm** - specifics:
375+
376+- a function must be either called in the unit OR it should be declared as a part of a (function pointer)**table** (via element) in order to be added to "linking" section;
377+- in order to mark a function as **undefined** it should be either "exported" or "imported" function. Note "importing" a function requires declaring a unit name, which is divided by a "dot" (.) character from the function import name.
378+- functions declared as "export" functions, would NOT be put into **linking** section and will be removed by the **wasm-ld** linker
379+
380+**wasm-ld** (LLD 9.0.0) - specifics:
381+
382+- when linking two or more modules for the function with the same name, linker keeps the code that's marked as **defined** (a special flag in linking section). The code that's marked as **weak** or **undefined** would be discarded.
383+- wasm exported functions are ignored by default, if they don't have a symbol assigned in linking section.
384+- the linker would use a symbol name for an exported function, instead of declared export name
385+
386+**wasmtool** <https://github.com/skalogryz/wasmbin>
387+
388+- the utility allows to change export names
389+
390+## See Also
391+
392+- [[WebAssembly]]
393+- [[WebAssembly/Instructions]] - Instructions of the WASM code
394+- <https://webassembly.org/> - the official site
395+- <https://webassembly.github.io/spec/core/text/index.html> - text format (S-Expression) specs
396+- <https://webassembly.github.io/spec/core/binary/index.html> - binary format specs
397+- <https://developer.mozilla.org/en-US/docs/WebAssembly/Understanding_the_text_format>
398+- <https://rsms.me/wasm-intro> - introduction to webassembly
399+- <https://blog.scottlogic.com/2018/04/26/webassembly-by-hand.html>
400+
401+---
402+*From the Free Pascal wiki, page "WebAssembly/Internals", as it stood on 2022-04-05 after a change by PierceNg, out of the wiki's dump of 2024-05-26. Copyright stays with the author who wrote it, according to the old wiki's [[Licensing]] page. If you have concerns about this material, [message the admin](/wiki-contact).*
403+Category: WebAssembly

Back to history