Sign in Join
Wiki copytemporary

WebAssembly/Internals: what changed

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

OldNewLine
11## Outdated page
22
33This page is outdated. It is currently used as a content staging area during refactoring of the various pages.
44
55## Assemblers
66
77There are different assemblers available, from Wabt, emscripten.org and LLVM. The expected format is a slightly different between those two:
88
99### wat2wasm (Wabt)
1010
1111Example:
1212
1313``` lisp
1414 (module
1515 (func $add (param $lhs i32) (param $rhs i32) (result i32)
1616 local.get $lhs
1717 local.get $rhs
1818 i32.add
1919 )
2020 (export "add" (func $add))
2121 )
2222```
2323
2424According 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.
2525
2626Online studio, that's using the older version of wabt syntax. <https://webassembly.studio/>
2727
2828For example. instead of
2929
30-`local.get`
30+```text
31+local.get
32+```
3133
3234it's using
3335
34-`get_local`
36+```text
37+get_local
38+```
3539
3640### wasm-as (emscripten)
3741
3842The assembler is recommended for the use in a compiler by the WebAssembly.org
3943
4044``` lisp
4145 (module
4246 (func $add (param $lhs i32) (param $rhs i32) (result i32)
4347 (
4448 local.get $lhs
4549 local.get $rhs
4650 i32.add
4751 )
4852 )
4953 (export "add" (func $add))
5054 )
5155```
5256
5357### llvm-mc
5458
5559This is the assembler from the LLVM project. It uses a GAS-like syntax.
5660
5761## Use on the Wiki
5862
5963WebAssembly 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.
6064
6165 <source lang="lisp">
6266 ;; web assembly goes here
6367 </source>
6468
6569## Limitations
6670
6771- 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.
6872- 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.
6973- Importing functions into Pascal is supported partially; see External.
7074- 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.)
7175
7276## Validation
7377
7478There's a certain requirements to the binary code of WebAssembly.
7579
7680The validation is described at the specification <http://webassembly.github.io/spec/core/valid/index.html>
7781
7882A code that satisfies the validation rules, is considered to be well-formed
7983
8084It'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.
8185
8286In some cases, there might be an overhead to create well-formed code.
8387
8488## Global Symbols
8589
8690Global symbols are stored in **global** variables. Each variable is of i32 type and stores an offset in memory, where the object is usually stored.
8791
8892In 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.
8993
9094## Code Branching/Flow Control
9195
9296*todo: rewrite to the better explanation*
9397
9498WebAssembly 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 ).
9599
96100FPC 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.
97101
98102### IF-block
99103
100104Wasm IF do return a value (one of the basic WebAsm types). Pascal IF-statement does not return a value.
101105
102106Thus the feature of value returning is not used (**todo:** but should be).
103107
104108A dummy 0 value is pushed on the stack. IF is hard-coded to return i32 at all times.
105109
106110According 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.
107111
108112At 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.
109113
110114``` pascal
111115function cmp(a,b: integer): Integer;
112116begin
113117 if a > b then
114118 cmp := 1
115119 else
116120 cmp := 0;
117121end;
118122```
119123
120124turns into:
121125
122126``` lisp
123127(func $cmp
124128 (param i32)
125129 (param i32)
126130 (result i32)
127131 (local i32) ;; Temp 2,1 allocated
128132 (local i32) ;; Temp 3,1 allocated
129133;; [3] begin
130134;; [4] if a > b then
131135 get_local 0
132136 set_local 3
133137 get_local 3
134138 get_local 1
135139 i32.gt_s
136140 if (result i32) ;; hard-coded i32 type of IF result
137141;; [5] cmp := 1
138142 i32.const 1
139143 set_local 2
140144 i32.const 0 ;; mandatory wasm-IF result
141145 else
142146;; [7] cmp := 0;
143147 i32.const 0
144148 set_local 2
145149 i32.const 0 ;; mandatory wasm-IF result
146150 end
147151 drop ;; dropping IF-result from the stack, as unused
148152;; [8] end;
149153 get_local 2
150154 return
151155)
152156```
153157
154158The code generation of **cifnode** is overridden.
155159
156160### Loop-block
157161
158162A loop is a block of instructions that has the label set to the beginning of the code.
159163
160164Here is an example of a loop. The function tries to sum up a number X amount of times
161165
162166``` pascal
163167function mulbyadd(Num, Cnt: integer): integer;
164168 Result := 0;
165169 while Cnt>0 do begin
166170 dec(Cnt);
167171 Result := Result + Num;
168172 end;
169173end;
170174```
171175
172176\<source lang="lisp\>
173177
174178` (func $mulbyadd (param $Num i32) (param $Cnt i32) (result i32)`\
175179`   (local i32)       ;; local variable (that gets index #2) this is $Result`\
176180`   i32.const 0`\
177181`   set_local 2`
178182
179183`   block`\
180184`   loop `\
181185`     get_local $Cnt  ;; comparing Cnt to 0`\
182186`     i32.const 0     ;; `\
183187`     i32.le_s         `\
184188`     br_if 1         ;; if Cnt is less or equal to 0, then we should exit the loop`
185189
186190`     get_local $Cnt  ;; decreasing Cnt`\
187191`     i32.const 1 `\
188192`     i32.sub  `\
189193`     set_local $Cnt  `
190194
191195`     get_local $Num  ;;`\
192196`     get_local 2     ;; `\
193197`     i32.add         ;;`\
194198`     set_local 2     ;; Result := Result + Num`
195199
196200`     br 0            ;; loop back to the beginning `\
197201`   end `\
198202`   end`\
199203`   get_local 2       ;; pushing the result to the stack (for the return value)`\
200204`   )`
201205
202206### FPC Labels
203207
204208Since 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.
205209
206210## External
207211
208212One 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.
209213
210214Only basic types can be used as parameters (integer, floats, memory pointers) and function result.
211215
212216Here's an example:
213217
214218The code tries to use an external function that accepts only 1 integer parameter. (The function should write out the parameter)
215219
216220``` pascal
217221library testext;
218222
219223procedure logint(i: integer); external 'env' name 'logint'; // name of external module and function name
220224
221225procedure run;
222226begin
223227 logint(304);
224228end;
225229
226230exports
227231 run name 'run';
228232end.
229233```
230234
231235In the browser, during instantiation the function must be provided:
232236
233237``` js
234238const importObject = {
235239 // note "env" should match the name of the function's library used in the pascal
236240 // thus it's possible to import functions from different sources.
237241 "env": {
238242
239243 "memory": new WebAssembly.Memory({initial:32, maximum:32})
240244 // this is the implementation of "logint" function
241245 // it accepts exactly 1 parameter, and puts it into a the browser's console
242246 ,"logint": function(k) { console.log(k) }
243247 }
244248
245249}
246250fetch('../out/main.wasm').then(response =>
247251 response.arrayBuffer()
248252).then(bytes => WebAssembly.instantiate(bytes, importObject)).then(results => {
249253 instance = results.instance;
250254
251255 document.getElementById("container").textContent =
252256 // running the export run() function
253257 instance.exports.run();
254258}).catch(console.error);
255259```
256260
257261### PChar Strings
258262
259263Strings 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)
260264
261265``` pascal
262266library testext;
263267
264268type
265269 pchar = ^char;
266270
267271procedure logstr(p: pchar); external 'env' name 'logstr';
268272procedure writedom(p: pchar); external 'dom' name 'writedom';
269273
270274procedure run;
271275begin
272276 logstr('hello');
273277 logstr('world');
274278 writedom('<b>Hello World</b> and <input type="button" value="FPC">');
275279end;
276280
277281exports
278282 run name 'run';
279283
280284end.
281285```
282286
283287On the host side the function needs to read the memory and prepare the string for the output:
284288
285289``` javascript
286290const memory = new WebAssembly.Memory({initial:32, maximum:32});
287291
288292// the function takes, strofs that indicates the start of string
289293// and then tries to get the string out of the memory, by searching for null-terminating character
290294function getStr(strofs)
291295{
292296 var arr = new Uint8Array(memory.buffer,strofs);
293297 for (var i=0; i < arr.length; i++)
294298 if (arr[i]==0) // null terminating char is found
295299 arr = arr.subarray(0, i);
296300 var str = new TextDecoder("utf-8").decode(arr);
297301 return str;
298302}
299303
300304const importObject = {
301305 "env": {
302306 "memory": memory
303307 ,"logstr": function(k)
304308 { console.log(getStr(k)); }
305309 },
306310 "dom": {
307311 "writedom": function(k) { document.write(getStr(k)) }
308312 }
309313}
310314fetch('../out/main.wasm').then(response =>
311315 response.arrayBuffer()
312316).then(bytes => WebAssembly.instantiate(bytes, importObject)).then(results => {
313317 instance = results.instance;
314318 document.getElementById("container").textContent = instance.exports.run();
315319}).catch(console.error);
316320```
317321
318322Obviously 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)
319323
320324## Object File Contents
321325
322326There's **wasm-objdump** that's useful for inspection of a generated object file.
323327
324328## Static Linking
325329
326330See LLD linker: <https://lld.llvm.org/WebAssembly.html> <https://github.com/WebAssembly/tool-conventions/blob/master/Linking.md>
327331
328332So far, there's only 1 wasm linker exists: lld (aka wasm-ld). LLVM linked ported for WASM.
329333
330334There's no official specification on object linking in WebAssembly. Tools are typically a common convention with a special "linking" section.
331335
332336One 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)
333337
334338### Linking section
335339
336340For each linkable part of the code (i.e. function) an entry must be created in "linking" section.
337341
338342### Linking Symbols
339343
340344In order for **wasm-ld** to successfully connect two symbols, the following approach must be taken:
341345
342346- a symbol declared in **implementation** section
343347
344-`WASM_SYM_VISIBILITY_HIDDEN `\
345-`WASM_SYM_BINDING_LOCAL `
348+```text
349+WASM_SYM_VISIBILITY_HIDDEN
350+WASM_SYM_BINDING_LOCAL
351+```
346352
347353This indicates that the symbol should not be linked anywhere, except for within the object file itself.
348354
349355- the unit declaring a symbol in **interface** section (linkable by other units) should declare the unit with flags set to:
350356
351-`WASM_SYM_VISIBILITY_HIDDEN `
357+```text
358+WASM_SYM_VISIBILITY_HIDDEN
359+```
352360
353361This indicates that while the symbol can be linked to other object files, but should not be part of the linked library (executable)
354362
355363- 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:
356364
357-`WASM_SYM_BINDING_WEAK`\
358-`WASM_SYM_VISIBILITY_HIDDEN`
365+```text
366+WASM_SYM_BINDING_WEAK
367+WASM_SYM_VISIBILITY_HIDDEN
368+```
359369
360370This indicates to **wasm-ld** linker to get rid of the **unreachable** stub and use the actual implementation.
361371
362372- 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:
363373
364-`WASM_SYM_EXPORTED`
374+```text
375+WASM_SYM_EXPORTED
376+```
365377
366378- an external symbol (that's imported from the host) should be marked by
367379
368-`WASM_SYM_UNDEFINED `
380+```text
381+WASM_SYM_UNDEFINED
382+```
369383
370384The flag is automatically set by **wat2wasm** for every **import** function.
371385
372386### Tools
373387
374388**wat2wasm** - specifics:
375389
376390- 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;
377391- 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.
378392- functions declared as "export" functions, would NOT be put into **linking** section and will be removed by the **wasm-ld** linker
379393
380394**wasm-ld** (LLD 9.0.0) - specifics:
381395
382396- 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.
383397- wasm exported functions are ignored by default, if they don't have a symbol assigned in linking section.
384398- the linker would use a symbol name for an exported function, instead of declared export name
385399
386400**wasmtool** <https://github.com/skalogryz/wasmbin>
387401
388402- the utility allows to change export names
389403
390404## See Also
391405
392406- [[WebAssembly]]
393407- [[WebAssembly/Instructions]] - Instructions of the WASM code
394408- <https://webassembly.org/> - the official site
395409- <https://webassembly.github.io/spec/core/text/index.html> - text format (S-Expression) specs
396410- <https://webassembly.github.io/spec/core/binary/index.html> - binary format specs
397411- <https://developer.mozilla.org/en-US/docs/WebAssembly/Understanding_the_text_format>
398412- <https://rsms.me/wasm-intro> - introduction to webassembly
399413- <https://blog.scottlogic.com/2018/04/26/webassembly-by-hand.html>
400414
401415---
402416*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).*
403417Category: WebAssembly

Back to history