From ad6eb23b750569b41a77e1c9bc5b295e725ed74a Mon Sep 17 00:00:00 2001
From: Anthony Version 2.3.0 Version 2.3.1 twr-wasm is a simple, lightweight and easy to use library for building C/C++ WebAssembly code directly with clang. It solves some common use cases with less work than the more feature rich emscripten. twr-wasm is easy to understand, and has some great features. You can call blocking functions. You can input and print streaming character i/o to a twr-wasm allows you to run C/C++ code in a web browser. Legacy code, libraries, full applications, or single functions can be integrated with JavaScript and TypeScript. Version 2.3.0 twr-wasm is a simple, lightweight and easy to use library for building C/C++ WebAssembly code directly with clang. It solves some common use cases with less work than the more feature rich emscripten. twr-wasm is easy to understand, and has some great features. You can call blocking functions. You can input and print streaming character i/o to a twr-wasm allows you to run C/C++ code in a web browser. Legacy code, libraries, full applications, or single functions can be integrated with JavaScript and TypeScript. twr-wasm is designed to be used with the standard llvm clang compiler and tools. twr-wasm was previously named tiny-wasm-runtime. Here is the simplest https://github.com/twiddlingbits/twr-wasm The Wasm Runtime Limitations section explains why a library like twr-wasm is needed to use WebAssembly. Please post feedback (it worked for you, didn't work, requests, questions, etc) at https://github.com/twiddlingbits/twr-wasm/ twr-wasm for WebAssembly provides a console API for abstracting I/O. This console API is used by stdin, stdout, and stderr, as well as the ANSI Terminal. Streaming and Windowed I/O is supported. This section describes the C character based input/output console API this is abstracted by Consoles can be \"tty\" aka \"streamed\", or they can be \"windowed\" (aka a \"terminal\"). Also see stdio stdio.h defines stdio.h also defines Learn WebAssembly with twr-wasm
Documentation and ExamplesEasier C/C++ WebAssembly
-<div>
tag, use a <canvas>
element as an ANSI terminal, and use 2D drawing apis (that are compatible with JavaScript Canvas APIs) to draw to a <canvas>
element. <div>
tag, use a <canvas>
element as an ANSI terminal, and use 2D drawing apis (that are compatible with JavaScript Canvas APIs) to draw to a <canvas>
element. <div>
View square demo Source Mini-Terminal (hello world using <canvas>
) View demo Source CLI using libc++ and <canvas>
) View console Source"},{"location":"#key-features","title":"Key Features","text":"
"},{"location":"#hello-world","title":"Hello World","text":"<div>
tags in your HTML page<canvas>
based \"terminal\"twr-wasm
example.
index.html#include <stdio.h>\n\nvoid hello() {\n printf(\"hello, world!\\n\");\n}\n
"},{"location":"#on-github","title":"On Github","text":"<head>\n <title>Hello World</title>\n</head>\n<body>\n <div id=\"twr_iodiv\"></div>\n\n <script type=\"module\">\n import {twrWasmModule} from \"twr-wasm\";\n\n const mod = new twrWasmModule();\n await mod.loadWasm(\"./helloworld.wasm\");\n await mod.callC([\"hello\"]);\n </script>\n</body>\n
"},{"location":"#version-2-limitations","title":"Version 2 Limitations","text":"
"},{"location":"#post-feedback","title":"Post Feedback","text":"
struct IoConsole
. <canvas>
View mini-term demo Source"},{"location":"api/api-c-con/#getting-stderr-stdin-stdout","title":"Getting stderr, stdin, stdout","text":"stdin
, stdout
, stderr
as explained here: stdioFILE
like this: typedef struct IoConsole FILE; \n
from <stdio.h>
:
#define stderr (FILE *)(twr_get_stderr_con())\n#define stdin (FILE *)(twr_get_stdio_con())\n#define stdout (FILE *)(twr_get_stdio_con())\n
"},{"location":"api/api-c-con/#getting-a-new-console","title":"Getting a new console","text":"stdin and stdout are set as explaind here. However, in unusual cases you might want to access the various consoles directly, regardless of how stdin, stdout, or stderr are set. You can do so like this:
"},{"location":"api/api-c-con/#io_nullcon","title":"io_nullcon","text":"Returns an IoConsole that goes to the bit bucket. io_getc32 will return 0.
#include \"twr-io.h\"\n\nstruct IoConsole* io_nullcon(void);\n
"},{"location":"api/api-c-con/#twr_debugcon","title":"twr_debugcon","text":"Returns an IoConsole that goes to the browser's debug console.
#include \"twr-crt.h\"\n\nstruct IoConsole* twr_debugcon(void);\n
"},{"location":"api/api-c-con/#twr_divcon","title":"twr_divcon","text":"Returns an IoConsole that goes to <div id=\"twr_iodiv\">
, if it exists.
#include \"twr-crt.h\"\n\nstruct IoConsole* twr_divcon(void);\n
"},{"location":"api/api-c-con/#twr_windowcon","title":"twr_windowcon","text":"Returns an IoConsole that goes to <canvas id=\"twr_iocanvas\">
, if it exists.
NOTE: Only one call can be made to this function, and it is usually made by the twr-wasm C runtime, so you likely won't call this function.
#include \"twr-crt.h\"\n\nstruct IoConsole* twr_windowcon(void);\n
"},{"location":"api/api-c-con/#io-console-functions","title":"IO Console Functions","text":""},{"location":"api/api-c-con/#io_putc","title":"io_putc","text":"Sends a byte to an IoConsole and supports the current locale's character encoding. This function will \"stream\" using the current code page. In other words, if you io_putc
ASCII, it will work as \"normal\". If the current locale is set to 1252, then you can send windows-1252 encoded characters. If the current locale is UTF-8, then you can stream UTF-8 (that is, call io_putc
once for each byte of the multi-byte UTF-8 character).
Note that when characters are sent to the browser console using stderr
they will not render to the console until a newline, return, or ASCII 03 (End-of-Text) is sent.
#include \"twr-io.h\"\n\nvoid io_putc(struct IoConsole* io, unsigned char c);\n
"},{"location":"api/api-c-con/#io_putstr","title":"io_putstr","text":"Calls io_putc
for each byte in the passed string.
#include \"twr-io.h\"\n\nvoid io_putstr(struct IoConsole* io, const char* s);\n
"},{"location":"api/api-c-con/#io_printf","title":"io_printf","text":"Identical to fprintf
, however io_printf will call io_begin_draw
and io_end_draw
around its drawing activities -- resulting in snapper performance.
For example:
#include \"twr-io.h\"\n\nio_printf(twr_debugcon(), \"hello over there in browser debug console land\\n\");\n
or
#include <stdio.h>\n#include <twr_io.h>\n\nio_printf(stdout, \"hello world\\n\");\n
#include <twr_io.h>\n\nvoid io_printf(struct IoConsole *io, const char *format, ...);\n
"},{"location":"api/api-c-con/#io_getc32","title":"io_getc32","text":"Waits for the user to enter and then returns a unicode code point. Currently only really works with an IoConsole that is stdin.
To return characters encoded with the current locale, see io_mbgetc
#include <twr_io.h>\n\nint io_getc32(struct IoConsole* io);\n
"},{"location":"api/api-c-con/#io_mbgetc","title":"io_mbgetc","text":"io_mbgetc
will get a character from stdin and encode it using the character encoding of the LC_CTYPE category of the current locale. \"C\" will use ASCII. UTF-8 and windows-1252 are also supported.
#include <twr_io.h>\n\nvoid io_mbgetc(struct IoConsole* io, char* strout);\n
"},{"location":"api/api-c-con/#io_mbgets","title":"io_mbgets","text":"Gets a string from an IoConsole (which needs to be stdin). Returns when the user presses \"Enter\". Displays a cursor character and echos the inputted characters, at the current cursor position. Uses character encoding of LC_TYPE of current locale.
#include <twr_io.h>\n\nchar *io_mbgets(struct IoConsole* io, char *buffer );\n
"},{"location":"api/api-c-con/#io_get_cursor","title":"io_get_cursor","text":"Returns an integer of the current cursor position. The cursor is where the next io_putc is going to go.
For windowed consoles, the cursor position ranges from [0, width*height-1], inclusive.
#include <twr_io.h>\n\nint io_get_cursor(struct IoConsole* io);\n
"},{"location":"api/api-c-con/#io_set_colors","title":"io_set_colors","text":"For windowed consoles only.
Sets a 24 bit RGB default color for the foreground and background. The prior default colors are changed (lost). For example, if you set the default colors when you created the window (see stdio), the defaults will no longer be active. Use io_get_colors
to save existing colors for later restoration using io_set_colors
.
A call to io_set_colors
doesn't actually cause any on screen changes. Instead, these new default colors are used in future draw and text calls. A foreground and background color is set for each cell in the console window. The cell's colors are set to these default foreground/background colors when a call to io_setc
, io_setreset
, etc is made.
#include <twr_io.h>\n\nvoid io_set_colors(struct IoConsole* io, unsigned long foreground, unsigned long background);\n
"},{"location":"api/api-c-con/#io_get_colors","title":"io_get_colors","text":"For windowed consoles only.
Gets the current default colors.
#include <twr_io.h>\n\nvoid io_get_colors(struct IoConsole* io, unsigned long *foreground, unsigned long *background);\n
"},{"location":"api/api-c-con/#io_cls","title":"io_cls","text":"For windowed consoles only.
Clears the screen. That is, all character cells in the window are set to a space, their colors are reset to the current default colors (see io_set_colors
).
#include <twr_io.h>\n\nvoid io_cls(struct IoConsoleWindow* iow);\n
"},{"location":"api/api-c-con/#io_setc","title":"io_setc","text":"For windowed consoles only.
Sets a window cell to a character. Sends a byte to an IoConsole and supports the current locale's character encoding. This function will \"stream\" using the current code page. In other words, if you io_setc
ASCII, it will work as \"normal\". If the current locale is set to 1252, then you can send windows-1252 encoded characters. If the current locale is UTF-8, then you can stream UTF-8 (that is, call io_setc
once for each byte of the multi-byte UTF-8 character).
#include <twr_io.h>\n\nbool io_setc(struct IoConsoleWindow* iow, int location, unsigned char c);\n
"},{"location":"api/api-c-con/#io_setc32","title":"io_setc32","text":"For windowed consoles only.
Sets a window cell to a unicode code point. The colors are set to the defaults (see io_set_colors
).
#include <twr_io.h>\n\nvoid io_setc32(struct IoConsoleWindow* iow, int location, int c);\n
"},{"location":"api/api-c-con/#io_setreset","title":"io_setreset","text":"For windowed consoles only.
Sets or resets (clears) a chunky graphics \"pixel\". Each character cell can also be a 2x3 grid of graphic \"pixels\". In other words, the terminal window has pixel dimensions of width2 x height3.
The color will be set to the defaults if the impacted cell is not a graphics cell. If it is an existing graphics cell, the colors don't change.
See the stdio-canvas
example.
#include <twr_io.h>\n\nbool io_setreset(struct IoConsoleWindow* iow, int x, int y, bool isset);\n
"},{"location":"api/api-c-con/#io_point","title":"io_point","text":"For windowed consoles only.
Checks if a chunky graphics \"pixel\" is set or clear. See io_setreset
.
#include <twr_io.h>\n\nbool io_point(struct IoConsoleWindow* iow, int x, int y);\n
"},{"location":"api/api-c-con/#io_set_cursor","title":"io_set_cursor","text":"Moves the cursor. See io_get_cursor
.
#include <twr_io.h>\n\nvoid io_set_cursor(struct IoConsoleWindow* iow, int loc);\n
"},{"location":"api/api-c-con/#io_begin_draw","title":"io_begin_draw","text":"For windowed consoles only.
This call (and its matching io_end_draw) are not required. But if you bracket any call sequence that draws to the terminal window with an io_begin_draw
and io_end_draw
, the updates will be batched into one update. This will increase performance and usually prevents the user from seeing partial updates.
io_begin_draw
can be nested.
See the stdio-canvas example.
#include <twr_io.h>\n\nvoid io_begin_draw(struct IoConsole* io);\n
"},{"location":"api/api-c-con/#io_end_draw","title":"io_end_draw","text":"For windowed consoles only.
See io_begin_draw
.
#include <twr_io.h>\n\nvoid io_end_draw(struct IoConsole* io);\n
"},{"location":"api/api-c-d2d/","title":"2D Draw C API for WebAssembly","text":"This section describes twr-wasm's C D2D API, which allows your WebAssembly module to call many of the JavaScript Canvas APIs. There is also a C++ canvas wrapper class in source/twr-cpp
used by the balls and pong examples.
To create a canvas surface that you can draw to using the twr-wasm 2D C drawing APIs, add a canvas tag to your HTML named twr_d2dcanvas
like this example (you can use any width/height you like):
<canvas id=\"twr_d2dcanvas\" width=\"600\" height=\"600\"></canvas>\n
To draw using the C 2D Draw API:
d2d_start_draw_sequence
d2d_fillrect
d2d_end_draw_sequence
Commands are queued until flushed -- which will take the batch of queued draw commands, and execute them. The 2D draw APIs will work with either twrWasmModule
or twrWasmModuleAsync
. With twrWasmModuleAsync
, the batch of commands is sent from the worker thread over to the JavaScript main thread for execution. By batching the calls between calls to d2d_start_draw_sequence
and d2d_end_draw_sequence
, performance is improved.
d2d_flush
waits for the commands to finish execution before returning. d2d_flush
is called automatically by d2d_end_draw_sequence
and so you generally don't need to call it manually.
You pass an argument to d2d_start_draw_sequence
specifying how many instructions will trigger an automatic call to d2d_flush
. You can make this larger for efficiency, or smaller if you want to see the render progress more frequently. There is no limit on the size of the queue, except memory used in the Wasm module. The d2d_flush
function can be called manually call, but this is not normally needed, unless you would like to ensure a sequence renders before your d2d_end_draw_sequence
is called, or before the count passed d2d_start_draw_sequence
is met.
If you are using twrWasmModuleAsync
, or if you are re-rendering the entire frame for each animation update, you should ensure that all of your draws for a complete frame are made without an explicit or implicit call to d2d_flush
in the middle of the draw sequence, as this may cause flashing.
These are the Canvas APIs currently available in C:
struct d2d_draw_seq* d2d_start_draw_sequence(int flush_at_ins_count);\nvoid d2d_end_draw_sequence(struct d2d_draw_seq* ds);\nvoid d2d_flush(struct d2d_draw_seq* ds);\nint d2d_get_canvas_prop(const char* prop);\n\nvoid d2d_fillrect(struct d2d_draw_seq* ds, double x, double y, double w, double h);\nvoid d2d_strokerect(struct d2d_draw_seq* ds, double x, double y, double w, double h);\nvoid d2d_filltext(struct d2d_draw_seq* ds, const char* str, double x, double y);\nvoid d2d_fillcodepoint(struct d2d_draw_seq* ds, char c, double x, double y);\n\nvoid d2d_measuretext(struct d2d_draw_seq* ds, const char* str, struct d2d_text_metrics *tm);\nvoid d2d_save(struct d2d_draw_seq* ds);\nvoid d2d_restore(struct d2d_draw_seq* ds);\n\nvoid d2d_setlinewidth(struct d2d_draw_seq* ds, double width);\nvoid d2d_setfillstylergba(struct d2d_draw_seq* ds, unsigned long color);\nvoid d2d_setstrokestylergba(struct d2d_draw_seq* ds, unsigned long color);\nvoid d2d_setfillstyle(struct d2d_draw_seq* ds, const char* css_color);\nvoid d2d_setstrokestyle(struct d2d_draw_seq* ds, const char* css_color);\nvoid d2d_setfont(struct d2d_draw_seq* ds, const char* font);\n\nvoid d2d_createlineargradient(struct d2d_draw_seq* ds, long id, double x0, double y0, double x1, double y1);\nvoid d2d_createradialgradient(struct d2d_draw_seq* ds, long id, double x0, double y0, double radius0, double x1, double y1, double radius1);\nvoid d2d_addcolorstop(struct d2d_draw_seq* ds, long gradID, long position, const char* csscolor);\nvoid d2d_setfillstylegradient(struct d2d_draw_seq* ds, long gradID);\nvoid d2d_releaseid(struct d2d_draw_seq* ds, long id);\n\nvoid d2d_beginpath(struct d2d_draw_seq* ds);\nvoid d2d_fill(struct d2d_draw_seq* ds);\nvoid d2d_stroke(struct d2d_draw_seq* ds);\nvoid d2d_moveto(struct d2d_draw_seq* ds, double x, double y);\nvoid d2d_lineto(struct d2d_draw_seq* ds, double x, double y);\nvoid d2d_arc(struct d2d_draw_seq* ds, double x, double y, double radius, double start_angle, double end_angle, bool counterclockwise);\nvoid d2d_bezierto(struct d2d_draw_seq* ds, double cp1x, double cp1y, double cp2x, double cp2y, double x, double y);\n\nvoid d2d_imagedata(struct d2d_draw_seq* ds, long id, void* mem, unsigned long length, unsigned long width, unsigned long height);\nvoid d2d_putimagedata(struct d2d_draw_seq* ds, long id, unsigned long dx, unsigned long dy);\nvoid d2d_putimagedatadirty(struct d2d_draw_seq* ds, long id, unsigned long dx, unsigned long dy, unsigned long dirtyX, unsigned long dirtyY, unsigned long dirtyWidth, unsigned long dirtyHeight);\n
d2d_measuretext() returns this structure:
struct d2d_text_metrics {\n double actualBoundingBoxAscent;\n double actualBoundingBoxDescent;\n double actualBoundingBoxLeft;\n double actualBoundingBoxRight;\n double fontBoundingBoxAscent;\n double fontBoundingBoxDescent;\n double width;\n};\n
d2d_get_canvas_prop() returns a value of:
export interface ICanvasProps {\n charWidth: number,\n charHeight: number,\n foreColor: number,\n backColor: number,\n widthInChars: number,\n heightInChars: number,\n canvasWidth:number,\n canvasHeight:number\n}\n
"},{"location":"api/api-c-general/","title":"General C API for Wasm","text":""},{"location":"api/api-c-general/#overview","title":"Overview","text":"This sections describes the \"general\" twr-wasm functions available that don't fit neatly into another category (such as standard C library functions, Draw 2D functions, etc.)
These functions often start with \"twr_\" and are generally found in this include file:
\\twr-wasm\\include\\twr-crt.h
Set a block of memory to zeros. Calls memset(to, 0, count)
.
#include <string.h>\n\nvoid bzero (void *to, size_t count);\n
"},{"location":"api/api-c-general/#getc","title":"getc","text":"This is the standard c library function (see the the standard library docs available on the internet).
Of note this function will return extended ASCII (128-255 inclusive). The extend ASCII are always encoded with Windows-1252 encoding.
See twr_getc32
for a list of related functions.
Note that C character input is blocking and you must use twrWasmModuleAsync -- see stdin for details on how to enable blocking character input.
"},{"location":"api/api-c-general/#twr_atod","title":"twr_atod","text":"Similar to stdlib atof
.
#include \"twr-crt.h\"\n\ndouble twr_atod(const char* str);\n
"},{"location":"api/api-c-general/#twr_atou64","title":"twr_atou64","text":"Convert a string to a 64 bit unsigned integer, stopping when the first non-valid character is encountered. If len is provided, it will be set to the number of characters read. Radix should be >=2 and <=36 -- for example, 10 is a normal base 10 number and 16 is hexadecimal.
#include \"twr-crt.h\"\n\nint64_t twr_atou64(const char *str, int* len, int radix);\n
"},{"location":"api/api-c-general/#twr_dtoa","title":"twr_dtoa","text":"The functions to convert double to text are snprintf
, fcvt_s
,twr_dtoa
, twr_toexponential
, and twr_tofixed
#include \"twr-crt.h\"\n\nvoid twr_dtoa(char* buffer, int sizeInBytes, double value, int max_precision);\n
"},{"location":"api/api-c-general/#twr_cache_mallocfree","title":"twr_cache_malloc/free","text":"These functions keep allocated memory in a cache for much faster re-access than the standard malloc/free.
#include \"twr-crt.h\"\n\nvoid *twr_cache_malloc(twr_size_t size);\nvoid twr_cache_free(void* mem);\n
"},{"location":"api/api-c-general/#twr_code_page_to_utf32_streamed","title":"twr_code_page_to_utf32_streamed","text":"Return a unicode code point (aka utf-32 value) when passed a byte stream that represents an encoded character using the current local's LC_CTYPE code page. A zero is returned if the byte stream has not yet completed a decode.
For example:
int cp\n\nsetlocale(LC_ALL, \"\"); // set to default locale, which will be UTF-8 encoding with local language/region\n\n// turn a UTF-8 Euro into a UTF-32 value\ncp==twr_code_page_to_utf32_streamed(0xE2);\nassert (cp==0);\ncp=twr_code_page_to_utf32_streamed(0x82);\nassert (cp==0);\ncp=twr_code_page_to_utf32_streamed(0xAC);\nassert (cp==0x000020AC); // Euro Code points\n
#include <locale.h>\n\nint twr_code_page_to_utf32_streamed(unsigned char byte) \n
"},{"location":"api/api-c-general/#twr_conlog","title":"twr_conlog","text":"twr_conlog
prints debug messages to the browser console from your C code.
#include \"twr-crt.h\"\n\nvoid twr_conlog(char* format, ...);\n
Each call to twr_conlog() will generate a single call to console.log() in JavaScript to ensure that you see debug prints. This call is identical to printf, except that it adds a newline.
The current implementation does not wait for the debug string to output to the console before returning from twr_conlog, when using twrWasmModuleAsync. In this case, it can take a small bit of time for the string to make its way across the Worker Thread boundary. This is normally not a problem and results in faster performance. But if your code crashes soon after the debug print, the print might not appear. If you think this is an issue, you can call twr_sleep(1)
after your twr_conlog call. This will force a blocking wait for the print to print.
Prior to 1.0, this function was called twr_dbg_printf
, and operated slightly differently.
Returns the number of milliseconds since the start of the epoch.
#include \"twr-wasm.h\"\n\nuint64_t twr_epoch_timems();\n
"},{"location":"api/api-c-general/#twr_getc32","title":"twr_getc32","text":"Gets a 32 bit unicode code point character from stdin. Unlike the standard C library function getchar
, twr_getc32
does not buffer a line (that is, twr_getc32
will return a character before the user presses Enter).
twr_getc32
is implemented as:
int twr_getc32() {\n return io_getc32(twr_get_stdio_con());\n}\n
Note that stdlib getchar
and ungetc
are not currently implemented.
Note that C character input is blocking and you must use twrWasmModuleAsync -- see stdin for details on how to enable blocking character input.
Also see:
io_mbgets
- get a multibyte string from a console using the current locale character encodingtwr_mbgets
- similar to io_mbgets
, except always gets a multibyte locale format string from stdin.io_mbgetc
- get a multibyte character from an IoConsole (like stdin
) using the current locale character encodinggetc
(sames as fgetc
) - get a single byte from a FILE * (IoConsole) -- returning ASCII or extended ASCII (window-1252 encoding)io_getc32
- gets a 32 bit unicode code point from an IoConsole (which currently needs to be stdin)#include \"twr-crt.h\"\n\nint twr_getc32();\n
"},{"location":"api/api-c-general/#twr_get_navlang","title":"twr_get_navlang","text":"Returns the BCP 47 language tag as found in javacript navigator.language
. If len is not null, it will be filled in with the string length of the language tag.
#include \"twr-crt.h\"\n\nconst char* twr_get_navlang(int *len);\n
"},{"location":"api/api-c-general/#twr_get_current_locale","title":"twr_get_current_locale","text":"extern inline locale_t twr_get_current_locale(void);\n
twr_get_current_locale
will return the locale that has been set by setlocale
. It can be used to pass to a function that takes a locale_t.
Functions like twr_dtoa
do not localize the decimal point. To get a localized decimal point, you can use printf
, or alternately twr_localize_numeric_string
to post process a string. For example:
char b[10];\nstrcpy(b, \"1.23\");\ntwr_localize_numeric_string(b, twr_get_current_locale());\n// if locale was set to french, then b is now 1,23\n
#include <locale.h>\n\nvoid twr_localize_numeric_string(char* str, locale_t locale);\n
"},{"location":"api/api-c-general/#twr_mem_debug_stats","title":"twr_mem_debug_stats","text":"Print memory map and malloc stats to stderr or stdout.
(note FILE * is the same as struct IoConsole*)
#include <stdio.h>\n\nvoid twr_mem_debug_stats(struct IoConsole* outcon);\n
"},{"location":"api/api-c-general/#twr_mbgets","title":"twr_mbgets","text":"Gets a string from stdin. The string will be in the current locale's character encoding -- ASCII for \"C\", and either UTF-8 or windows-1252 for \"\". See localization.
#include \"twr-crt.h\"\n\nchar* twr_mbgets(char* buffer);\n
Internally this function uses the stdio IoConsole -- see the IoConsole section for more advanced input/output.
This function will encode characters as specified by the LC_CTYPE category of the current locale. ASCII is used for \"C\", and UTF-8 and Windows-1252 are also supported (see localization)
Note that C character input is blocking and you must use twrWasmModuleAsync -- see stdin for details on how to enable blocking character input.
"},{"location":"api/api-c-general/#twr_mbslen_l","title":"twr_mbslen_l","text":"Returns the number of characters in a string using the character encoding of the passed locale (ASCII for \"C\", UTF-8, or windows-1252 for \"\"). You can use twr_get_current_locale
to find the current locale.
#include <string.h>\n\nsize_t twr_mbslen_l(const char *str, locale_t locale);\n
"},{"location":"api/api-c-general/#twr_sleep","title":"twr_sleep","text":"twr_sleep
is a traditional blocking sleep function. This function is blocking, and so is only available if you use twrWasmModuleAsync
.
#include \"twr-wasm.h\"\n\nvoid twr_sleep(int ms);\n
"},{"location":"api/api-c-general/#twr_tofixed","title":"twr_tofixed","text":"This function is identical to its JavaScript version.
#include \"twr-wasm.h\"\n\nvoid twr_tofixed(char* buffer, int buffer_size, double value, int dec_digits);\n
The functions to convert double to text are snprintf
, fcvt_s
,twr_dtoa
, twr_toexponential
, and twr_tofixed
This function is identical to its JavaScript version.
#include \"twr-wasm.h\"\n\nvoid twr_toexponential(char* buffer, int buffer_size, double value, int dec_digits);\n
The functions to convert double to text are snprintf
, fcvt_s
,twr_dtoa
, twr_toexponential
, and twr_tofixed
Mirror image the passed in string.
#include \"twr-crt.h\"\n\nvoid twr_strhorizflip(char * buffer, int n);\n
"},{"location":"api/api-c-general/#twr_utf8_char_len","title":"twr_utf8_char_len","text":"Returns the number of bytes in a UTF-8 character (passed as a string pointer). UTF-8 characters can be 1 to 4 bytes in length.
#include <string.h>\n\nint twr_utf8_char_len(const char *str);\n
"},{"location":"api/api-c-general/#twr_utf32_to_code_page","title":"twr_utf32_to_code_page","text":"Takes a utf32 value (aka unicode code point value), and fills in the passed character array buffer with the character encoding of the utf32 value, using the current locale's LC_CTYPE code page. The buffer is 0 terminated.
Also see c32rtomb
and c16rtomb
.
For example:
char strbuf[6]; // max size of utf-8 is 4+terminating zero. Max size of ASCII or windows 1252 is 1 + terminating zero\nsetlocale(LC_ALL, \"\"); // set to default locale, which will be UTF-8 encoding with local language/region\ntwr_utf32_to_code_page(strbuf, 0x000020AC); // encode a Euro code point \nprintf(\"%s\", strbuf); \nassert ( strcmp(strbuf,\"\\xE2\\x82\\xAC\")==0 ); // utf-8 encoding of euro\nassert ( strcmp(strbuf,\"\u20ac\")==0 ); // clang string literals default to utf-8 encoding\n
include <locale.h>\n\nvoid twr_utf32_to_code_page(char* out, int utf32)\n
"},{"location":"api/api-c-general/#twr_vprintf","title":"twr_vprintf","text":"Performs a printf by calling the callback with cbdata for each character.
#include \"twr-crt.h\"\n\nvoid twr_vprintf(twr_cbprintf_callback out, void* cbdata, const char *format, va_list* args);\n
"},{"location":"api/api-c-general/#floating-math-helpers","title":"floating math helpers","text":"int twr_isnan(double v);\nint twr_isinf(double v);\ndouble twr_nanval();\ndouble twr_infval();\n
"},{"location":"api/api-c-stdlib/","title":"Standard C library for WebAssembly","text":"This section describes twr-wasm's support for the Standard C Library. twr-wasm includes its own implementation of the standard C library optimized for WebAssembly and Wasm running in a web browser. This is a core feature of twr-wasm.
For documentation of these functions, see the many standard C library documentation web sites.
The following subset of the standard C library is available. Also see twr-wasm/include
folder for include files.
* fprintf will only work with these -- stderr, stdin, stdout */\n/* these return 'struct IoConsole *' which is same as 'FILE *' */\n#define stderr (FILE *)(twr_get_stderr_con())\n#define stdin (FILE *)(twr_get_stdio_con())\n#define stdout (FILE *)(twr_get_stdio_con())\n\nint snprintf(char *buffer, size_t bufsz, const char *format, ... );\nint sprintf( char *buffer, const char *format, ... );\nint vsnprintf(char *buffer, size_t bufsz, const char *format, va_list vlist);\nint vasprintf(char **strp, const char* format, va_list vlist );\nint printf(const char* format, ...);\nint vprintf(const char* format, va_list vlist );\nint puts(const char *str);\nint putchar(int c);\n\ntypedef struct IoConsole FILE; \nint vfprintf(FILE *stream, const char *format, va_list vlist);\nint fprintf(FILE *stream, const char* format, ...);\nsize_t fwrite(const void* buffer, size_t size, size_t count, FILE* stream);\nint ferror(FILE *stream);\nint feof(FILE *stream);\nint fflush(FILE *stream);\nint is_terminal(FILE *stream);\nint fputc(int ch, FILE* stream);\nint putc(int ch, FILE* stream);\nint fgetc(FILE *stream );\nint getc(FILE *stream);\n
"},{"location":"api/api-c-stdlib/#stdlibh","title":"stdlib.h","text":"void *malloc(size_t size);\nvoid free(void *mem);\nsize_t avail(void);\nvoid *realloc( void *ptr, size_t new_size );\nvoid* calloc( size_t num, size_t size );\nvoid *aligned_alloc( size_t alignment, size_t size );\n\nint rand(void);\nvoid srand(int seed);\n\n#define __min(a,b) (((a) < (b)) ? (a) : (b))\n#define __max(a,b) (((a) > (b)) ? (a) : (b))\n\nint _fcvt_s(\n char* buffer,\n size_t sizeInBytes,\n double value,\n int fracpart_numdigits,\n int *dec,\n int *sign\n);\ndouble atof(const char* str);\nint atoi(const char *str);\nlong atol( const char *str );\nlong long atoll( const char *str );\nlong strtol(const char *str, char **str_end, int base);\nlong long strtoll(const char *str, char **str_end, int base);\nlong long strtoll_l(const char *str, char **str_end, int base, locale_t loc);\nunsigned long long strtoull(const char *str, char **str_end, int base);\nunsigned long long strtoull_l(const char *str, char **str_end, int base, locale_t loc);\nunsigned long strtoul(const char *str, char ** str_end, int base);\nfloat strtof(const char *str, char ** str_end);\nfloat strtof_l(const char *str, char ** str_end, locale_t locale);\ndouble strtod(const char *str, char **str_end);\ndouble strtod_l(const char *str, char **str_end, locale_t locale);\nlong double strtold(const char *str, char **str_end);\nlong double strtold_l(const char *str, char **str_end, locale_t locale);\nint _itoa_s(int64_t value, char * buffer, size_t size, int radix);\n\ndiv_t div( int x, int y );\nldiv_t ldiv( long x, long y );\nlldiv_t lldiv( long long x, long long y );\n\n_Noreturn void abort(void);\nint atexit(void (*func)(void));\n
Note that _fcvt_s as currently enabled has these limitations: - fractional digits <=100 - values must be less than 1e+21 - values negative exponents must be smaller than 1e-99
There is a full featured version of _fcvt_s in the source code, but is not currently enabled, since the version enabled is smaller and works in most use cases.
"},{"location":"api/api-c-stdlib/#asserth","title":"assert.h","text":"void assert(int expression);\n
"},{"location":"api/api-c-stdlib/#mathh","title":"math.h","text":"int abs(int n);\ndouble acos(double arg);\ndouble asin(double arg);\ndouble atan(double arg);\ndouble ceil(double arg);\ndouble cos(double arg);\ndouble exp(double arg);\ndouble fabs(double arg);\ndouble floor(double arg);\ndouble fmod(double x, double y);\ndouble log(double arg);\ndouble pow(double base, double exp);\ndouble sin(double arg);\ndouble sqrt(double arg);\ndouble tan(double arg);\ndouble trunc(double arg);\n
"},{"location":"api/api-c-stdlib/#stdargh","title":"stdarg.h","text":"#define va_start(v,l) __builtin_va_start(v,l)\n#define va_end(v) __builtin_va_end(v)\n#define va_arg(v,l) __builtin_va_arg(v,l)\n#define va_copy(d,s) __builtin_va_copy(d,s)\ntypedef __builtin_va_list va_list;\n
"},{"location":"api/api-c-stdlib/#ctypeh","title":"ctype.h","text":"int isascii(int);\nint toascii(int);\nint isalnum(int c);\nint isalpha(int c);\nint isblank(int);\nint iscntrl(int);\nint isdigit(int c);\nint isgraph(int c);\nint islower(int);\nint isprint(int);\nint ispunct(int);\nint isspace(int c);\nint isupper(int);\nint isxdigit(int);\nint tolower(int c);\nint toupper(int c);\n\nint isalnum_l(int c, locale_t loc);\nint isalpha_l(int c, locale_t loc);\nint isblank_l(int c, locale_t loc);\nint iscntrl_l(int c, locale_t loc);\nint isdigit_l(int c, locale_t loc);\nint isgraph_l(int c, locale_t loc);\nint islower_l(int c, locale_t loc);\nint isprint_l(int c, locale_t loc);\nint ispunct_l(int c, locale_t loc);\nint isspace_l(int c, locale_t loc);\nint isupper_l(int c, locale_t loc);\nint isxdigit_l(int c, locale_t loc);\nint tolower_l(int c, locale_t loc);\nint toupper_l(int c, locale_t loc);\n
"},{"location":"api/api-c-stdlib/#stddefh","title":"stddef.h","text":"#define offsetof(TYPE, MEMBER) __builtin_offsetof (TYPE, MEMBER)\ntypedef __PTRDIFF_TYPE__ ptrdiff_t;\ntypedef double max_align_t;\n
"},{"location":"api/api-c-stdlib/#stringh","title":"string.h","text":"size_t strlen(const char * str);\nchar *strdup(const char * source);\nchar *strcpy(char *dest, const char *source);\nint strcat_s(char *dest, size_t destsz, const char *src);\nchar* strcat(char *dest, const char *src);\nchar *strncpy(char *dest, const char *source, size_t count);\nint strcmp(const char* string1, const char* string2);\nint strncmp(const char* lhs, const char* rhs, size_t count);\nint stricmp(const char* string1, const char* string2);\nint strnicmp(const char* string1, const char* string2, size_t count);\nint strcoll(const char* lhs, const char* rhs);\nint strcoll_l(const char* lhs, const char* rhs, locale_t loc);\nchar *strchr(const char *str, int ch);\nvoid *memchr(const void *ptr, int ch, size_t count);\nchar *strstr(const char *haystack, const char *needle);\nchar * strerror(int errnum );\nchar * _strerror(const char *strErrMsg);\nvoid *memmove(void *dest, const void *src, size_t n);\nint memcmp( const void* lhs, const void* rhs, size_t count );\nvoid bzero (void *to, size_t count);\n\n// implemented in memcpy.wat\nvoid *memcpy(void *dest, const void * src, size_t n);\nvoid *memset(void *mem, int c, size_t n);\n
"},{"location":"api/api-c-stdlib/#timeh","title":"time.h","text":"typedef unsigned long time_t;\nunsigned long time(unsigned long *time);\nsize_t strftime(char *s, size_t maxsize, const char *format, const struct tm *timeptr);\nsize_t strftime_l(char *s, size_t maxsize, const char *format, const struct tm *timeptr, locale_t locale);\nstruct tm *localtime(const time_t *timer);\nint gettimeofday(struct timeval *tv, void* notused);\n#define timerisset(tvp) ((tvp)->tv_sec || (tvp)->tv_usec)\n#define timercmp(tvp,uvp,cmp) \\\n ((tvp)->tv_sec cmp (uvp)->tv_sec || \\\n ((tvp)->tv_sec == (uvp)->tv_sec && (tvp)->tv_usec cmp (uvp)->tv_usec))\n#define timerclear(tvp) (tvp)->tv_sec = (tvp)->tv_usec = 0\n
"},{"location":"api/api-c-stdlib/#localeh","title":"locale.h","text":"#define LC_GLOBAL_LOCALE twr_get_current_locale()\nchar* setlocale(int category, const char* locale);\nstruct lconv *localeconv(void);\nlocale_t newlocale(int category_mask, const char *locale, locale_t base);\nlocale_t uselocale(locale_t);\nvoid freelocale(locale_t);\nlocale_t duplocale(locale_t);\nextern inline locale_t twr_get_current_locale(void);\n
"},{"location":"api/api-c-stdlib/#ucharh","title":"uchar.h","text":"typedef uint_least32_t char32_t;\ntypedef uint_least16_t char16_t;\n\nsize_t c32rtomb( char* s, char32_t c32, mbstate_t* ps );\n
"},{"location":"api/api-c-stdlib/#errnoh","title":"errno.h","text":"typedef int errno_t;\n\nextern int * _errno(void);\n#define errno (*_errno())\n\nerrno_t _set_errno(int _Value);\nerrno_t _get_errno(int *_Value);\n
"},{"location":"api/api-c-stdlib/#_stdtypesh","title":"_stdtypes.h","text":"// don't include directly -- included by various .h files
typedef unsigned long size_t;\n#define MAX_SIZE_T 2147483647 \n\n#ifdef __cplusplus\n#define NULL __null\n#else\n#define NULL ((void*)0)\n#endif\n\ntypedef struct __locale_t_struct * locale_t;\n
"},{"location":"api/api-c-stdlib/#other-include-files-available","title":"Other include files available","text":"float.h\nlimits.h\nstdbool.h\nstdint.h\n
"},{"location":"api/api-libc%2B%2B/","title":"libc++ for WebAssembly","text":"This section describes twr-wasm's support for using the standard c++ library libc++ with WebAssembly.
twr-wasm includes libc++ built for WebAssembly in the twr-wasm/lib-c
folder.
For C++ the use of libc++ is optional. That is you can build twr-wasm projects in C++ with or without libc++.
See the examples tests-libcx and tests-user for examples of using libc++.
See the balls example for how to create a C++ WebAssembly program without the standard C++ library. The primary advantage to this approach is a bit smaller code size. You don't need to staticly link libc++.
Some of the key options twr-wasm's libc++ for WebAssembly was built with are these:
DLIBCXX_ENABLE_LOCALIZATION=ON \nDLIBCXX_ENABLE_UNICODE=ON \nDLIBCXX_ENABLE_RTTI=ON \nDLIBCXX_ENABLE_STATIC_ABI_LIBRARY=ON \n\nDCMAKE_BUILD_TYPE=Release \nDCMAKE_CXX_STANDARD=20 \n\nDLIBCXX_ENABLE_EXCEPTIONS=OFF \nDLIBCXX_ENABLE_THREADS=OFF \nDLIBCXX_ENABLE_SHARED=OFF \nDLIBCXX_ENABLE_WIDE_CHARACTERS=OFF \nDLIBCXX_ENABLE_FILESYSTEM=OFF \nDLIBCXX_ENABLE_TIME_ZONE_DATABASE=OFF \nDLIBCXX_ENABLE_MONOTONIC_CLOCK=OFF \nDLIBCXX_ENABLE_RANDOM_DEVICE=OFF\n
"},{"location":"api/api-localization/","title":"Localization Reference for twr-wasm","text":"This section details twr-wasm's WebAssembly localization support.
Also see Introduction to Character Encoding Support with twr-wasm
"},{"location":"api/api-localization/#using-c","title":"Using C:","text":"Standard C locale functions are supported by twr-wasm. ASCII, UTF-8 and windows-1252 encoding is supported by the twr-wasm standard C library locale. twr-wasm also includes C functions for UTF-32 support.
"},{"location":"api/api-localization/#using-c_1","title":"Using C++:","text":"twr-wasm C locales support ASCII, UTF-8 or windows-1252 encoding. UTF-16/32 are not supported as a std c lib locale setting, but functions are provided to convert utf-32 (unicode code points) to and from ASCII, UTF-8, and windows-1252 \"code pages\" (there are other miscellaneous utf-32 based functions as well.)
"},{"location":"api/api-localization/#locales-standard-c-library","title":"Locales (Standard C Library)","text":""},{"location":"api/api-localization/#c","title":"\"C\"","text":"\"C\" is the default locale, as usual. When \"C\" is selected, the functions operate as usual. One subtly is that console i/o functions (such as printf
) will generally function as expected with UTF-8, since the div
and window
consoles correctly handle UTF-8 character encoding. This is normal on some OSs, such as linux, but not the default on Windows (which often defaults to windows-1252 for backward compatibility).
isgraph
style functions will only recognize ASCII characters, as is normal. Functions such as strcmp
operate on the byte sequence, which will typically results in UTF-8 codes being compared lexically. strcoll
will use lexical ordering.
\"POSIX\" is the same as \"C\"
"},{"location":"api/api-localization/#_1","title":"\"\"","text":"\"\" is the locale to specify the users default setting (this selects the setting used by the browser). This will also enable UTF-8 in functions such as strcoll
. For example, if your browser is set to \"en-US\" as its default locale, setlocale(LC_ALL, \"\")
will return en-US.UTF-8
.
isgraph
style functions will still only recognize ASCII characters (since UTF-8 doesn't encode any single bytes greater than 127). strcoll
uses locale specific ordering, and printf
will use locale specific decimal points. strcmp
still compares two strings lexicographically (byte-by-byte) without considering locale-specific rules, per the spec.
\".UTF-8\" is the same as \"\" with twr-wasm.
"},{"location":"api/api-localization/#1252","title":"\".1252\"","text":"\".1252\" will select the current default locale, but use windows-1252 character encoding (instead of UTF-8). Windows-1252 is a super set of ISO-8859-1 and is the most commonly used encoding for many european languages when unicode is not used. This mode is primarily for legacy software, backwards compatibly, and windows compatibility.
"},{"location":"api/api-localization/#others","title":"Others","text":"Setting arbitrary locales, such as \"fr-FR\" when the browser is defaulted to another locale, is not supported.
"},{"location":"api/api-localization/#select-the-default-locale","title":"Select the default locale","text":"To select the user's browser's default locale using the C language, and enable consistent utf-8 support, use a call like this:
setlocale(LC_ALL, \"\")\n
"},{"location":"api/api-localization/#c-and-libc-functions","title":"C and libc++ functions","text":"If you are using twr-wasm's build of libc++, libc++ locale and unicode functions work as normal.
The usual standard C library locale support is available, along with some POSIX extensions. In addition, some locale useful twr-wasm specific functions are documented in C API, such as twr_get_current_locale
,twr_mbgets
, twr_getc32
, twr_utf8_char_len
, twr_mbslen_l
, twr_utf32_to_code_page
, twr_code_page_to_utf32_streamed
, twr_get_navlang
, twr_localize_numeric_string
.
Note that io_getc32
, getc(stdin)
, fgetc(stdin)
do not look at the current locale. io_getc32
returns a 32 bit unicode code point, and getc
/fgetc
return extended ASCII.
For a locale aware character input, use io_mbgetc()
or twr_mbgets()
. Both use the locale category LC_CTYPE. See C API.
Note that when the locale is not set (or whenever the \"C\" locale is set) functions that get character(s) from stdin that are locale aware, like twr_mbgets()
, behave different than functions that output characters to stdout (like puts
, io_putstr
, io_putc
, putchar
). Characters to stdout in \"C\" locale will handle UTF-8 characters. For stdin, \"C\" locale uses ASCII.
For consistent UTF-8 (or windows-1252) behavior, set the locale as discussed above ( use setlocale
)
The primary standard C library locale functions are:
char* setlocale(int category, const char* locale);\nstruct lconv *localeconv(void);\n
As well as the two standard library functions above, appropriate functions take into account the current locale (printf, strcoll, etc).
Note that setlocale
returns a string using BCP 47 format (like a web browser). Locale strings look like \"en-US.UTF-8\", instead of \"en_US.UTF-8\". A dash, not an underscore, is used as a separator.
POSIX functions These are the extended POSIX style functions provided that are related to locale:
locale_t newlocale(int category_mask, const char *locale, locale_t base);\nlocale_t uselocale(locale_t);\nvoid freelocale(locale_t);\nlocale_t duplocale(locale_t);\n\nint isalnum_l(int c, locale_t loc);\nint isalpha_l(int c, locale_t loc);\nint isblank_l(int c, locale_t loc);\nint iscntrl_l(int c, locale_t loc);\nint isdigit_l(int c, locale_t loc);\nint isgraph_l(int c, locale_t loc);\nint islower_l(int c, locale_t loc);\nint isprint_l(int c, locale_t loc);\nint ispunct_l(int c, locale_t loc);\nint isspace_l(int c, locale_t loc);\nint isupper_l(int c, locale_t loc);\nint isxdigit_l(int c, locale_t loc);\nint tolower_l(int c, locale_t loc);\nint toupper_l(int c, locale_t loc);\n\nlong long strtoll_l(const char *str, char **str_end, int base, locale_t loc);\nunsigned long long strtoull_l(const char *str, char **str_end, int base, locale_t loc);\nfloat strtof_l(const char *str, char ** str_end, locale_t locale);\ndouble strtod_l(const char *str, char **str_end, locale_t locale);\nlong double strtold_l(const char *str, char **str_end, locale_t locale);\n\nint strcoll_l(const char* lhs, const char* rhs, locale_t loc);\n\nsize_t strftime_l(char *s, size_t maxsize, const char *format, const struct tm *timeptr, locale_t locale);\n
"},{"location":"api/api-typescript/","title":"TypeScript-JavaScript API to load and call Wasm","text":"This section describes the twr-wasm TypeScript/JavaScript classes that you use to load your Wasm modules, and to call C functions in your Wasm modules.
class twrWasmModule
and class twrWasmModuleAsync
have similar APIs. The primary difference is that class twrWasmModuleAsync
proxies functionality through a Web Worker thread, which allows blocking C functions to be called in your WebAssembly Module. The Async
part of twrWasmModuleAsync
refers to the ability to await
on a blocking callC
in your JavaScript main thread, when using twrWasmModuleAsync
.
import {twrWasmModule} from \"twr-wasm\";\n\nconst mod = new twrWasmModule();\n
twrWasmModule
provides the two core JavaScript APIs for access to a WebAssembly Module: loadWasm
to load your .wasm
module (your compiled C code).callC
to call a C functionUse loadWasm
to load your compiled C/C++ code (the .wasm
file).
await mod.loadWasm(\"./mycode.wasm\")\n
"},{"location":"api/api-typescript/#callc","title":"callC","text":"After your .wasm
module is loaded with loadWasm
, you call functions in your C/C++ from TypeScript/JavaScript like this:
let result=await mod.callC([\"function_name\", param1, param2])\n
If you are calling into C++, you need to use extern \"C\" like this in your C++ function:
extern \"C\" int function_name() {}\n
Each C/C++ function that you wish to call from TypeScript/JavaScript needs to be exported in your wasm-ld
command line with an option like this:
--export=function_name\n
Or like this in your source file: __attribute__((export_name(\"function_name\")))\nvoid function_name() {\n ...\n}\n
Fo more details, see the Compiler Options.
callC
takes an array where:
and the next optional entries are a variable number of arguments to pass to the C function, of type:
number
- will be converted to a signed or unsigned long
, int32_t
, int
, float
or double
as needed to match the C function declaration.bigint
- will be converted into an int64_t
or equivalentstring
- converted to a char *
of malloc'd module memory where string is copied intoArrayBuffer
- the array is copied into malloc'd module memory. If you need to pass the length, pass it as a separate argument. Any modifications to the memory made by your C code will be reflected back into the JavaScript ArrayBuffer.URL
- the url contents are copied into malloc'd module Memory, and two C arguments are generated - index (pointer) to the memory, and lengthcallC
returns the value returned by the C function. long
, int32_t
, int
, float
or double
and the like are returned as a number
, int64_t
is returned as a bigint
, and pointers are returned as a number
. The contents of the pointer will need to be extracted using the functions listed below in the section \"Accessing Data in the WebAssembly Memory\". The callC example also illustrates this.
More details can be found in this article: Passing Function Arguments to WebAssembly and in this example. The FFT example demonstrates passing and modifying a Float32Array
view of an ArrayBuffer
.
import {twrWasmModuleAsync} from \"twr-wasm\";\n\nconst amod = new twrWasmModuleAsync();\n
twrWasmModuleAsync
implements all of the same functions as twrWasmModule
, plus allows blocking inputs, and blocking code generally. This is achieved by proxying all the calls through a Web Worker thread.
For example, with this C function in your Wasm module:
void mysleep() {\n twr_sleep(5000); // sleep 5 seconds\n}\n
can be called from your JavaScript main loop like this:
await amod.callC([\"mysleep\"]);\n
This is useful for inputting from stdin
, or for traditional blocking loops. The example stdio-div - Printf and Input Using a div Tag demos this.
You must use twrWasmModuleAsync
in order to:
twr_mbgets
)twr_sleep
See stdio section for information on enabling blocking character input, as well as this Example.
When linking your C/C++ code, twrWasmModule
and twrWasmModuleAsync
use slightly different wasm-ld
options since twrWasmModuleAsync
uses shared memory. twrWasmModule
will operate with shared memory, so technically you could just use the same share memory options with either module, but you don't need the overhead of shared memory when using twrWasmModule, and so better to not enable it.
See Compiler Options.
twrWasmModuleAsync
uses SharedArrayBuffers which require certain HTTP headers to be set. Note that twrWasmModule
has an advantage in that it does not use SharedArrayBuffers.
Github pages doesn't support the needed CORS headers for SharedArrayBuffers. But other web serving sites do have options to enable the needed CORS headers. For example, the azure static web site config file staticwebapp.config.json
looks like this:
{\n \"globalHeaders\": {\n \"Access-Control-Allow-Origin\": \"*\",\n \"Cross-Origin-Embedder-Policy\": \"require-corp\",\n \"Cross-Origin-Opener-Policy\": \"same-origin\"\n }\n}\n
server.py in the examples folder will launch a local server with the correct headers. To use Chrome without a web server, see the Hello World walk through.
"},{"location":"api/api-typescript/#class-options","title":"Class Options","text":"The twrWasmModule
and twrWasmModuleAsync
constructor both take optional options.
For example:
let amod=new twrWasmModuleAsync();\n\nlet amod=new twrWasmModuleAsync({\n windim:[50,20], \n forecolor:\"beige\", \n backcolor:\"DarkOliveGreen\", \n fontsize:18\n });\n
For a <div id=\"twr_iodiv\">
it is simpler to set the color and font in the div tag per the normal HTML method. But for <div id=\"twr_iocanvas\">
, that method won't work and you need to use the constructor options for color and fontsize.
These are the options:
export type TStdioVals=\"div\"|\"canvas\"|\"null\"|\"debug\";\n\nexport interface IModOpts {\n stdio?:TStdioVals, \n windim?:[number, number],\n forecolor?:string,\n backcolor?:string,\n fontsize?:number,\n imports?:{},\n}\n
"},{"location":"api/api-typescript/#stdio","title":"stdio","text":"You can explicitly set your stdio source (for C/C++ printf, etc) with the stdio option, but typically you don't set it. Instead, it will auto set as described here
"},{"location":"api/api-typescript/#windim","title":"windim","text":"This options is used with a terminal console ( <canvas id=\"twr_iocanvas\">
) to set the width and height, in characters.
The canvas width and height, in pixels, will be set based on your fontsize and the width and height (in characters) of the terminal.
"},{"location":"api/api-typescript/#forecolor-and-backcolor","title":"forecolor and backcolor","text":"These can be set to a CSS color (like '#FFFFFF' or 'white') to change the default background and foreground colors.
"},{"location":"api/api-typescript/#fonsize","title":"fonsize","text":"Changes the default fontsize for div or canvas based I/O. The size is in pixels.
"},{"location":"api/api-typescript/#divlog","title":"divLog","text":"If stdio
is set to twr_iodiv
, you can use the divLog
twrWasmModule/Async function like this:
import {twrWasmModule} from \"twr-wasm\";\n\nconst mod = new twrWasmModule();\nawait mod.loadWasm(\"./tests.wasm\");\nawait mod.callC([\"tests\"]);\n\nmod.divLog(\"\\nsin() speed test\");\nlet sumA=0;\nconst start=Date.now();\n\nfor (let i=0; i<2000000;i++)\n sumA=sumA+Math.sin(i);\n\nconst endA=Date.now();\n\nlet sumB=await mod.callC([\"sin_test\"]);\nconst endB=Date.now();\n\nmod.divLog(\"sum A: \", sumA, \" in ms: \", endA-start);\nmod.divLog(\"sum B: \", sumB, \" in ms: \", endB-endA);\n
"},{"location":"api/api-typescript/#accessing-data-in-the-webassembly-memory","title":"Accessing Data in the WebAssembly Memory","text":"callC()
will convert your JavaScript arguments into a form suitable for use by your C code. However, if you return or want to access struct values inside TypeScript you will find the following twrWasmModule
and twrWasmModuleAsync
functions handy. See the callc example and Passing Function Arguments from JavaScript to C/C++ with WebAssembly for an explanation of how these functions work.
async putString(sin:string, codePage=codePageUTF8) // returns index into WebAssembly.Memory\nasync putU8(u8a:Uint8Array) // returns index into WebAssembly.Memory\nasync putArrayBuffer(ab:ArrayBuffer) // returns index into WebAssembly.Memory\nasync fetchAndPutURL(fnin:URL) // returns index into WebAssembly.Memory\nasync malloc(size:number) // returns index in WebAssembly.Memory. \n\nstringToU8(sin:string, codePage=codePageUTF8)\ncopyString(buffer:number, buffer_size:number, sin:string, codePage=codePageUTF8):void\ngetLong(idx:number): number\nsetLong(idx:number, value:number)\ngetDouble(idx:number): number\nsetDouble(idx:number, value:number)\ngetShort(idx:number): number\ngetString(strIndex:number, len?:number, codePage=codePageUTF8): string\ngetU8Arr(idx:number): Uint8Array\ngetU32Arr(idx:number): Uint32Array\n\nmemory?:WebAssembly.Memory;\nmem8:Uint8Array;\nmem32:Uint32Array;\nmemD:Float64Array;\n
"},{"location":"examples/examples-balls/","title":"Bouncing Balls - 2D Draw API Wasm Example","text":"This example uses twr-wasm's 2D Draw API and a C++ Canvas class with WebAssembly and C++ to bounce balls around your HTML page.
The bouncing balls example demonstrates
This example does not use libc++, which results in smaller code size. For an example that uses libc++ see tests-libcxx.
"},{"location":"examples/examples-balls/#screen-grab-of-balls-example","title":"Screen Grab of Balls Example","text":""},{"location":"examples/examples-callc/","title":"callC - Calling WebAssembly Functions Example","text":"This example demonstrates how to pass and return values between TypeScript/JavaScript and C/C++ when you are using WebAssembly with twr-wasm.
This article explains the key concepts to pass arguments between JavaScript/TypeScript and Wasm C/C++.
This example is a demo of integrating the popular KISS FFT C library with TypeScript/JavaScript/HTML using WebAssembly. The FFT C library is compiled into a Wasm (WebAssembly) module using clang, with the help of twr-wasm. The FFT Wasm module is used by the HTML page to calculate the FFT. The FFT input and output is drawn to the web page using JavaScript canvas functions.
The FFT library exposes APIs to process data, and doesn't use stdio.
The FFT APIs use float32 arrays for complex-number input and output data, and a configuration C struct. In the example I generate the input data by adding a 1K and 5K sine waves, call the kiss FFT API to perform the FFT on the generated sine waves, and then graph the input and output data using a JavaScript Canvas.
Here is part of the code. The rest can be found on github.
index.html
<head>\n <title>Fast Fourier transform (FFT)</title>\n</head>\n<body style=\"background-color:white\">\n\n <br>\n\n <div style=\"font:24px arial\">Input Signal</div>\n <canvas id=\"c-input\" width=\"1024\" height=\"300\" style=\"background-color:lightgray\"></canvas>\n\n <br><br><br>\n\n <div style=\"font:24px arial\">FFT Output</div>\n <canvas id=\"c-output\" width=\"1024\" height=\"300\" style=\"background-color:lightgray\"></canvas>\n\n <script type=\"module\">\n import {fftDemo} from \"./fft-script.js\";\n\n fftDemo();\n\n </script>\n</body>\n
fft-script.jsimport {twrWasmModule} from \"twr-wasm\";\n\nexport async function fftDemo() {\n\n const mod=new twrWasmModule();\n\n // load the kiss_fft C code as is, unmodified\n await mod.loadWasm('kiss_fft.wasm');\n\n // kissFFTData stores and graphs the input and output data\n // in this example the fft has 1024 bins, and I am using a 48K sampling rate\n let fft=new kissFFTData(1024, 48000);\n fft.genSin(1000)\n fft.addSin(5000)\n fft.graphIn(\"c-input\");\n\n // see kiss_fft README, but in summary you: (a) alloc config, (b) compute the FFT, (c) free the config\n // kiss_fft_alloc() returns a malloced structure. Pointers are numbers (index into Wasm module memory) in JS land \n //\n //kiss_fft_cfg cfg = kiss_fft_alloc( nfft ,is_inverse_fft ,0,0 );\n let cfg:number = await mod.callC([\"kiss_fft_alloc\", fft.nfft, 0, 0, 0 ]);\n\n // The FFT input and output data are C arrays of complex numbers.\n // typedef struct {\n // kiss_fft_scalar r;\n // kiss_fft_scalar i;\n // } kiss_fft_cpx;\n //\n // /* default is float */\n // define kiss_fft_scalar float\n\n // So if the FFT data has 1024 bins, then 1024 * 2 floats (r & i) * 4 bytes per float are needed.\n // I use a JS Float32Array view on the ArrayBuffer to access the floats\n\n // When an arrayBuffer is passed in as an argument to mod.callC,\n // callC will malloc memory in the Wasm module of a size that matches the array buffer, then\n // copy the arraybuffer into the malloc'd memory prior to the function call, \n // then copy the malloc'd memory contents back into the arrayBuffer post call.\n // The malloc'd memory is free'd post call. \n\n // void kiss_fft(kiss_fft_cfg cfg,const kiss_fft_cpx *fin,kiss_fft_cpx *fout);\n await mod.callC([\"kiss_fft\", cfg, fft.inArrayBuf, fft.outArrayBuf]);\n\n fft.graphOut(\"c-output\");\n\n await mod.callC([\"free\", cfg]); // not much point to this since all the module memory is about to disappear\n}\n
"},{"location":"examples/examples-helloworld/","title":"Hello World - WebAssembly C Example","text":"This example is an very simple twr-wasm program. It uses WebAssembly and C to print \"hello, world!\" to an HTML <div>
tag.
Also see: Hello World - Step-by-Step C to Wasm.
This is a simple test of various libc++ functions using WebAssembly with twr-wasm. The C++ program links with libc++. An example makefile is provided.
Also see this WebAssembly program that uses libc++ with twr-wasm to implement a CLI console.
This example is a port to Wasm of a 20 year old Win32 C Maze creator, with the help of twr-wasm 2D Draw APIs.
This Maze generator uses the twr-wasm \"d2d\" (Draw 2D) C APIs. These allow drawing onto an HTML canvas from C/C++. (Also see the balls C++ example).
This C code is interesting in that it is a combination of blocking and non blocking functions. The CalcMaze
function is blocking when the \"slow draw\" flag is set. It uses Sleep
in this case. For this reason, I use twrWasmModuleAsync. The solve section uses repeated calls to SolveStep
, which works well with a JavaScript main loop. I used a javascript interval timer to make repeated calls to the C SolveStep
function. If all the C code was structured this way, twrWasmModule
could have been used (instead of the Async version)
To port this code to twr-wasm I wrote a (very tiny) Win32 compatible API (in winemu.c/winemu.h). It only implements the features needed to port Maze, but it might be useful to use as a starting point for porting your Win32 code to the web.
index.html<head>\n <title>Maze</title>\n</head>\n<body style=\"background-color:powderblue\">\n <canvas id=\"twr_d2dcanvas\" width=\"600\" height=\"600\"></canvas>\n\n <script type=\"module\">\n import {mazeRunner} from \"./maze-script.js\";\n\n mazeRunner();\n </script>\n</body>\n
maze-script.jsimport {twrWasmModuleAsync} from \"twr-wasm\";\n\nexport async function mazeRunner() {\n\n const amod=new twrWasmModuleAsync();\n\n await amod.loadWasm('maze.wasm');\n\n //void CalcMaze(HWND hWnd, LONG cell_size, LONG is_black_bg, LONG isd - slow draw)\n await amod.callC([\"CalcMaze\", 0, 7, 0, 1]);\n await amod.callC([\"SolveBegin\"]);\n\n let timer = setInterval(async ()=>{\n let isdone=await amod.callC([\"SolveStep\", 0]); //SolveStep(hwnd))\n if (isdone) clearInterval(timer);\n }, 50);\n}\n
"},{"location":"examples/examples-overview/","title":"WebAssembly C/C++ Examples","text":""},{"location":"examples/examples-overview/#overview","title":"Overview","text":"These C and C++ examples demonstrate how to create different types of WebAssembly (wasm) programs with the twr-wasm library.
These are good examples to use as starting points for your own Wasm projects.
These examples are a good place to learn how to configure clang and wasm-ld to compile and link C/C++ code for use with WebAssembly (wasm).
"},{"location":"examples/examples-overview/#example-quick-links","title":"Example Quick Links","text":"Each of these examples are designed to illustrate how to use a feature of twr-wasm.
Name Description Link helloworld A very simple C Wasm example to get you started helloworld stdio-div This simple C program demos inputting andprinting characters to adiv
tag stdio-div stdio-canvas This simple C program demos writing and inputtingfrom a <canvas>
tag that twr-wasm configuresas a windowed \"mini-terminal\" stdio-canvas balls These fun Bouncing Balls are written in C++ and demo the2D drawing APIs with a C++ Canvas wrapper class balls pong A simple game of Pong written in C++ to demo 2D drawing APIs with a C++ canvas wrapper class and taking user input from JS pong maze This is an old Win32 program ported to wasmand demos the 2D Draw APIs maze fft A demo of calling a C library to perform an FFTthat is graphed in TypeScript fft callC A demo of passing and returning values betweenJavaScript and Wasm module callc tests twr-wasm unit tests tests tests-user \"cli\" for tests using libc++ and <canvas>
tests-user tests-libcxx Smoke test for libc++. Shows how to use libc++. tests-libcxx"},{"location":"examples/examples-overview/#building-the-examples","title":"Building the Examples","text":"See Example Readme for more information on building and running the examples.
"},{"location":"examples/examples-pong/","title":"Pong - 2D Game Example","text":"Similar to the balls example, this example uses twr-wasm's 2D Draw API and a C++ canvas class to run a simple game of singleplayer Pong.
The Pong example demonstrates
This example does not use libc++, which results in smaller code size. For an example that uses libc++ see tests-libcxx.
"},{"location":"examples/examples-pong/#screen-grab-of-pong-example","title":"Screen Grab of Pong Example","text":""},{"location":"examples/examples-stdio-canvas/","title":"stdio-canvas - Terminal Using a Canvas Tag","text":"A simple ANSI WebAssembly C \"terminal\" is created with input and output directed to an HTML <canvas>
tag.
This example will move a string up or down in the terminal window when you press the u or d or arrow keys.
This example also draws a graphic box around the terminal window.
#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#include <locale.h>\n#include \"twr-crt.h\"\n\n/* this twr-wasm C example draws a utf-8 string in the middle of a windowed console, */\n/* and allows the user to move the string up or down with the u, d or arrow keys */\n\n/* see include/twr-io.h for available functions to draw chars to windowed console */\n\nvoid draw_outline(struct IoConsoleWindow* iow);\nvoid show_str_centered(struct IoConsoleWindow* iow, int h, const char* str);\n\nvoid stdio_canvas() {\n struct IoConsoleWindow* iow=(struct IoConsoleWindow*)twr_get_stdio_con();\n\n assert(iow->con.header.type&IO_TYPE_WINDOW);\n\n setlocale(LC_ALL, \"\"); // set user default locale, which is always UTF-8. This is here to turn on UTF-8.\n\n int h;\n const char* str=\"Hello World (press u, d, \u2191, or \u2193)\"; // arrows are UTF-8 multibyte\n const char* spc=\" \";\n char inbuf[6]; // UTF-8 should be max 4 bytes plus string ending 0\n\n h=iow->display.height/2;\n\n draw_outline(iow);\n\n while (1) {\n show_str_centered(iow, h, str);\n io_mbgetc(stdin, inbuf); // also see twr_getc32 documentation\n show_str_centered(iow, h, spc); // erase old string\n\n if (strcmp(inbuf,\"u\")==0 || strcmp(inbuf,\"\u2191\")==0) { // arrows are multibyte UTF-8.\n h=h-1;\n if (h<1) h=1; // border I drew is in the 0 position\n }\n if (strcmp(inbuf,\"d\")==0 || strcmp(inbuf,\"\u2193\")==0) {\n h=h+1;\n if (h>=(iow->display.height-1)) h=iow->display.height-2; // border I drew is in the height-1 position\n }\n }\n}\n\nvoid show_str_centered(struct IoConsoleWindow* iow, int h, const char* str) {\n int len=twr_mbslen_l(str, twr_get_current_locale());\n int x=(iow->display.width-len)/2;\n io_set_cursorxy(iow, x, h);\n io_putstr(&iow->con, str);\n}\n\nvoid draw_outline(struct IoConsoleWindow* iow) {\n const int w=iow->display.width*2; // graphic cells are 2x3\n const int h=iow->display.height*3;\n unsigned long fgcolor, bgcolor;\n\n io_begin_draw(&iow->con);\n\n io_get_colors(&iow->con, &fgcolor, &bgcolor);\n io_set_colors(&iow->con, 0x000000, bgcolor); // draw in black\n\n for (int i=0; i<w; i++) {\n io_setreset(iow, i, 0, true);\n io_setreset(iow, i, h-1, true);\n }\n\n for (int i=0; i<h; i++) {\n io_setreset(iow, 0, i, true);\n io_setreset(iow, w-1, i, true);\n }\n\n io_set_colors(&iow->con, fgcolor, bgcolor); // restore\n\n io_end_draw(&iow->con);\n\n}\n
"},{"location":"examples/examples-stdio-canvas/#html-code","title":"HTML Code","text":"index.html<!doctype html>\n<html>\n<head>\n <title>stdio-canvas example</title>\n</head>\n<body>\n <canvas id=\"twr_iocanvas\" tabindex=\"0\"></canvas>\n\n <!-- importmap used when un-bundled -->\n <script type=\"importmap\">\n {\n \"imports\": {\n \"twr-wasm\": \"../../lib-js/index.js\"\n }\n }\n </script>\n\n <script type=\"module\">\n import {twrWasmModuleAsync} from \"twr-wasm\";\n\n try {\n const amod = new twrWasmModuleAsync({windim:[50,20], forecolor:\"beige\", backcolor:\"DarkOliveGreen\", fontsize:18});\n\n document.getElementById(\"twr_iocanvas\").focus();\n document.getElementById(\"twr_iocanvas\").addEventListener(\"keydown\",(ev)=>{amod.keyDownCanvas(ev)});\n\n await amod.loadWasm(\"./stdio-canvas.wasm\");\n const r=await amod.callC([\"stdio_canvas\"]);\n console.log(\"callC returned: \"+r);\n }\n catch(ex) {\n console.log(\"unexpected exception\");\n throw ex;\n }\n\n </script>\n</body>\n</html>\n
"},{"location":"examples/examples-stdio-div/","title":"stdio-div - Printf and Input Using a div Tag","text":"This simple WebAssembly C program demos inputting and printing characters with a div
tag.
#include <stdio.h>\n#include <stdlib.h>\n#include \"twr-crt.h\"\n\nvoid stdio_div() {\n char inbuf[64];\n int i;\n\n printf(\"Square Calculator\\n\");\n\n while (1) {\n printf(\"Enter an integer: \");\n twr_mbgets(inbuf);\n i=atoi(inbuf);\n printf(\"%d squared is %d\\n\\n\",i,i*i);\n }\n}\n
"},{"location":"examples/examples-stdio-div/#html-code","title":"HTML Code","text":"We are using twrWasmModuleAsync
which integrates blocking C code into JavaScript. twrWasmModuleAsync
can also be used to receive key input from a <div>
or <canvas>
tag.
<head>\n <title>stdio-div example</title>\n</head>\n<body>\n <div id=\"twr_iodiv\" style=\"background-color:LightGray;color:DarkGreen\" tabindex=\"0\">Loading... <br></div>\n\n <script type=\"module\">\n import {twrWasmModuleAsync} from \"twr-wasm\";\n\n let amod;\n\n try {\n amod = new twrWasmModuleAsync();\n\n document.getElementById(\"twr_iodiv\").innerHTML =\"<br>\";\n document.getElementById(\"twr_iodiv\").addEventListener(\"keydown\",(ev)=>{amod.keyDownDiv(ev)});\n\n await amod.loadWasm(\"./stdio-div.wasm\");\n await amod.callC([\"stdio_div\"]);\n }\n catch(ex) {\n amod.divLog(\"unexpected exception\");\n throw ex;\n }\n\n </script>\n</body>\n
"},{"location":"gettingstarted/basicsteps/","title":"Basic Steps To Create Your Wasm Project","text":"This section describes the basic steps to integrate your TypeScript/JavaScript with C/C++ WebAssembly code.
"},{"location":"gettingstarted/basicsteps/#overview-of-webassembly-project","title":"Overview of WebAssembly Project","text":"Your C/C++ WebAssembly project will consist of HTML (and related JavaScript or TypeScript) and C or C++ source files that are compiled into a \".wasm
\" binary file that is loaded as a WebAssembly module by your JavaScript.
On the JavaScript side of your WebAssembly project you will use the twr-wasm JavaScript/TypeScript class twrWasmModule
or twrWasmModuleAsync
to load the .wasm
module, and then call C functions in it using callC
(more details are in the TypeScript/Javascript API section).
You will call C functions (or C++ with ' extern \"C\" ' linkage) in the .wasm
module from your JavaScript. You can also call JavaScript functions from your C/C++ code, but this is less common.
There is no direct equivalent to a C \"main\". Instead, a Wasm module provides exported C functions that you can call from JavaScript/TypeScript. A Wasm module is more like a runtime loaded dynamic library.
You're C/C++ code can be non-blocking or blocking. Blocking means that it \"takes a long time\" to return. For example, if you want to send mouse events to C code, have the code process them then return, this would be non-blocking. Alternately, if your C code is a big loop that never returns, that would be very blocking. You can use the twr-wasm class twrWasmModuleAsync
to execute blocking code from JavaScript. The example maze demonstrates both non-blocking and blocking C calls.
Here are some examples of different types of C/C++ code:
<div>
or <canvas>
tag. This is explained in the stdio section.<div>
or <canvas>
using stdio type C/C++ functions, or it could render to a <canvas>
using 2D drawing APIs that correspond to JavaScript canvas 2D draw operations. (Balls) is an example.Here are the general steps to integrate your C with your JavaScript:
.wasm
file.import
. <div id=twr_iodiv>
or <canvas id=twr_iocanvas>
to your HTML (see stdio)new twrWasmModule()
, followed by a call to loadWasm()
, then one or more callC()
.twrWasmModuleAsync()
-- which is interchangeable with twrWasmModule, but proxies through a worker thread, and adds blocking support, including blocking char input.This section explains twr-wasm's WebAssembly support for ASCII, UTF-8, windows-1252, and UTF-32 character encoding.
"},{"location":"gettingstarted/charencoding/#getting-started","title":"Getting Started","text":"When using C with twr-wasm, you will likely want to add this line to the start of your code:
setlocale(LC_ALL, \"\")\n
This will change the C locale language to the one selected in the browser, and will enable consistent UTF-8 character encoding support.
Without this line, the standard C runtime will (mostly) default character encoding to ASCII, as per the standard. The exception is that just as with gcc, twr-wasm consoles support outputting UTF-8.
"},{"location":"gettingstarted/charencoding/#character-encodings","title":"Character Encodings","text":"twr-wasm supports ASCII, UNICODE, and extended-ASCII (in the form of Windows-1252).
These days UNICODE with UTF-8 encoding is the most popular method of displaying and encoding text. UTF-8 is popular because it has the deep character glyph definitions of UNICODE with an encoding that provides (a) the best backwards compatibility with ASCII, and (b) a compact memory footprint. It does this at the expense of some multibyte complexities.
UTF-8 is variable length, and uses between one to four bytes to represent any unicode code point, with ASCII compatibility in the first 128 bytes. It is also the standard for the web, and the default for clang. But because UTF-8 uses a variable number of bytes per character it can make string manipulation in C a bit harder than ASCII, Windows-1252 or UTF-32.
In this document you will see the term \"locale\". This term originated (at least as its commonly used in programming) in the standard C library, and is also used in the standard C++ library (libc++ in twr-wasm). A locale refers to a region of the world, along with a specific character encoding. The twr-wasm standard c runtime uses a label akin to this to define a locale: en-US.UTF-8
. Of note is that libc++ and the standard C runtime have different domains for their locales (ie, they don't directly impact each other). You can learn more about locales by searching the internet.
twr-wasm C locales support ASCII, UTF-8 or windows-1252 character encoding. UTF-16/32 are not supported as a std c lib locale setting, but functions are provided to convert utf-32 (unicode code points) to and from ASCII, UTF-8, and windows-1252 \"code pages\" (there are other miscellaneous utf-32 based functions as well.)
Although twr-wasm's standard c library locale doesn't support utf-32 directly, you can use int arrays (instead of byte arrays) to hold utf-32 strings, and then convert them to/from utf-8 with the help of the provided functions for this purpose. Alternately, you can use libc++, which has classes that directly support utf-16 and utf-32.
"},{"location":"gettingstarted/charencoding/#windows-compatibility-with-windows-1252","title":"Windows Compatibility with Windows-1252","text":"Windows-1252 is the default character encoding on Windows computers in many countries - particularly the Americas and western Europe -- and particularly when using MSVC. Linux, clang, gcc, and the web commonly default in some way to UTF-8 character encoding. Windows-1252 is an extension of ASCII that uses a single byte per character. This makes it easier than UTF-8 from a programmers perspective, but it doesn't represent as many characters. It is supported by twr-wasm to make it easier to port legacy C code, windows code, as well as a simpler alternative to UTF-8.
twr-wasm supports Windows-1252, and you can enable it like this:
setlocale(LC_ALL, \".1252\")\n
This will set the locale to the default browser language, and character encoding to Windows-1252.
1252 String Literals These days text editors generally default to UTF-8. In order to use windows-1252 source code and/or string literals, such as const char * str=\"\u20ac100\"
you may need to:
--finput-charset
and -fexec-charset
By default, the Microsoft Visual Studio C compiler (MSVC) does not treat string literals as UTF-8. Instead, it treats them as being encoded in the current code page of the system, which is typically Windows-1252 on western european language Windows systems. twr-wasm is designed to work with clang, which does default to utf-8, so if you are compiling code written for MSVC, and you use extend character sets (non ASCII), you may need to adjust your compiler settings with the flags mentioned above.
"},{"location":"gettingstarted/charencoding/#more","title":"More","text":"For more details see Localization Reference for twr-wasm
"},{"location":"gettingstarted/compiler-opts/","title":"Compiling, Linking, and Memory Options","text":"This section described how to use clang
to compile C/C++ code for WebAssembly, and how to use wasm-ld
to link your files into a .wasm module, when using twr-wasm.
twr-wasm lets you use clang directly, without a wrapper. This section describes the needed clang compile options and the wasm-ld link options. You can also take a look at the example makefiles.
"},{"location":"gettingstarted/compiler-opts/#compiler-notes","title":"Compiler Notes","text":"twr-wasm has been tested with clang 17.0.6 and wasm-ld 17.0.6.
If you are using nix, the default clang packages are wrapped with flags that break compilation. The following packages don't have this issue:
When compiling C code with clang for use with Wasm and twr-wasm, use these clang options:
--target=wasm32 -nostdinc -nostdlib -isystem ../../include\n
Here is an example of a compile command:
clang --target=wasm32 -nostdinc -nostdlib -isystem ./node_modules/twr-wasm/include -c helloworld.c -o helloworld.o\n
-isystem
should be adjusted to point to where the folder twr-wasm/include
is installed. For example:
../../include
is a relative link to include
that works if your project is a sub folder in the examples
folder. ./node_modules/twr-wasm/include
assumes you installed with npm
into your project folder. (see the Hello World Walk Through).When compiling C++ code with clang for use with Wasm and twr-wasm, use these clang options:
--target=wasm32 -fno-exceptions -nostdlibinc -nostdinc -nostdlib -isystem ../../include\n
"},{"location":"gettingstarted/compiler-opts/#wasm-ld-linker-options","title":"wasm-ld Linker Options","text":"Use the wasm-ld linker directly with twr-wasm.
For example:
wasm-ld helloworld.o ./node_modules/twr-wasm/lib-c/twr.a -o helloworld.wasm --no-entry --initial-memory=131072 --max-memory=131072 --export=hello \n
For C and C++ link to twr.a
to link to the twr-wasm library.
For C++ link to libc++.a
if you are using libc++. (see the tests-libcxx example makefile).
Be sure to adjust the path to twr.a
and libc++.a
as needed to the location where twr-wasm/lib-c/
is installed.
All of the twr-wasm functions are staticly linked from the library lib-c/twr.a
. There is also a version ( lib-c/twrd.a
) of twr-wasm library available with debug symbols. One of these two static libraries should be added to the list of files to link (normally this is twr.a
). Both versions are built with asserts enabled. twr.a
is built with -O3
. twrd.a
is built with -g -O0
.
C functions that you wish to call from JavaScript should either have an -export
option passed to wasm-ld
, or you can use the __attribute__((export_name(\"function_name\")))
option in your C function definition.
All exported functions to JavaScript should be C linkage (extern \"C\"
if using C++).
wasm-ld should be passed the following options:
If Using twrWasmModule:
--no-entry --initial-memory=<size> --max-memory=<size>\n
If Using twrWasmModuleAsync:
--no-entry --shared-memory --no-check-features --initial-memory=<size> --max-memory=<size>\n
"},{"location":"gettingstarted/compiler-opts/#memory-options-memory-size-stack-size-etc","title":"Memory Options (Memory Size, Stack Size, etc)","text":"WebAssembly.Memory
contains all the data used by your code (including the data needs of staticly linked libraries such as twr-wasm or libc++), but it does not store your actual code. It provides a contiguous, mutable array of raw bytes. Code execution and storage in WebAssembly are handled separately using the WebAssembly.Module
and WebAssembly.Instance
objects. The code (compiled WebAssembly instructions) is stored in the WebAssembly.Module
, while WebAssembly.Memory
is used to manage the linear memory accessible to the WebAssembly instance for storing data. Examples of data include your static data (.bss section or the .data section), the heap (used by malloc
and free
), and the stack (used for function calls and local variables).
The memory size should be a multiple of 64*1024 (64K) chunks. \"initial-memory\" and \"max-memory\" should be set to the same number since there is no support for automatically growing memory in twr-wasm. The memory is an export out of the .wasm
into the JavaScript code -- you should not create or set the size of WebAssembly.Memory
in JavaScript when using twr-wasm.
You set the memory size for your module (WebAssembly.Memory
) using wasm-ld
options as follows (this examples sets your Wasm memory to 1MB).
if using twrWasmModule
:
--initial-memory=1048576 --max-memory=1048576\n
"},{"location":"gettingstarted/compiler-opts/#twrwasmmoduleasync","title":"twrWasmModuleAsync","text":"If you are using twrWasmModuleAsync
, shared memory must also be enabled. Like this:
--shared-memory --no-check-features --initial-memory=1048576 --max-memory=1048576\n
See this production note on using shared memory.
"},{"location":"gettingstarted/compiler-opts/#stack-size","title":"Stack Size","text":"You can change your C/C++ stack size from the default 64K with the following wasm-ld
option. This example sets the stack at 128K
-z stack-size=131072\n
"},{"location":"gettingstarted/compiler-opts/#print-memory-map","title":"Print Memory Map","text":"You can print your module memory map, heap stats, and stack size using the function from C:
void twr_mem_debug_stats(struct IoConsole* outcon);\n
You can call it from Javascript with the output sent to the debug console (stderr) like this: twrWasmModule/Async.callC([\"twr_wasm_print_mem_debug_stats\"])\n
"},{"location":"gettingstarted/compiler-opts/#typescriptjavascript-malloc-and-memory-access","title":"TypeScript/JavaScript malloc and Memory Access","text":"twrWasmModule
and twrWasmModuleAsync
expose malloc
as an async function, as well as the WebAssembly Module memory as:
async malloc(size:number);\n\nmemory?:WebAssembly.Memory;\nmem8:Uint8Array;\nmem32:Uint32Array;\nmemD:Float64Array;\n
to call free
from JavaScript (you probably won't need to), you can use: twrWasmModule/Async.callC([\"twr_free\", index]); // index to memory to free, as returned by malloc\n
more information on these functions and module public variables can be found in the examples in this section: Passing Function Arguments to WebAssembly.
"},{"location":"gettingstarted/debugging/","title":"Debugging WebAssembly","text":"This section describes tips for debugging your WebAssembly (Wasm) program. Some of these techniques are WebAssembly generic, some are specific to using twr-wasm.
"},{"location":"gettingstarted/debugging/#debug-and-release-libraries","title":"Debug and Release libraries","text":"There are release (twr.a) and debug (twrd.a) versions of the twr-wasm C library. The \"debug\" version has debug symbols enabled with -g
and is built with optimizations disabled via -O0
. The \"release\" version has no debug symbols and optimization is set to -O3
. Both have asserts enabled. In general, you should use the \"release\" version unless you wish to step through the twr-wasm source -- in which case use the \"debug\" version.
libc++.a is not built with debug symbols.
"},{"location":"gettingstarted/debugging/#source-level-debugging-webassembly-cc","title":"Source Level Debugging WebAssembly C/C++","text":"In order to enable C/C++ source debugging with Wasm and clang, do the following:
-O
flag or set to -O0
) twrWasmModuleAsync
-- see the server.py example.Use twr_conlog
to print to the JavaScript console from C (see API ref section).
#include \"twr-wasm.h\"\n\ntwr_conlog(\"hello 99 in hex: %x\",99);\n
Use twrWasmModule.divLog()
to print to a div inside JavaScript code (see API ref section).
Note: If you use this technique, you will not be able to get the C/C++ DevTool chrome extension to run, and so source level debugging won't work. (If you know how to fix this, please contact me on github.)
You can execute and debug JavaScript with Wasm from local files without an HTTP server. It might be helpful to download the twr-wasm source code from github when you do this (so you can step through the twr-wasm typescript code as needed).
See the examples and Example Readme for more detail on how this works.
In general, you will need to add a clip of code similar to this to your HTML:
<script type=\"importmap\">\n {\n \"imports\": {\n \"twr-wasm\": \"./../../lib-js/index.js\"\n }\n }\n</script>\n
Make sure the paths to twr-wasm/lib-js/index.js
are correct for where your source is located. The above is correct for the provided examples.
You will need to set the following flags when running chrome from the shell or VS Code (the first is only strictly required if using twrWasmModuleAsync).
--enable-features=SharedArrayBuffer\n--allow-file-access-from-files\n
If you are using VS Code, You can create a launch.json entry similar to this:
launch.json{\n \"configurations\": [\n {\n \"name\": \"Launch Chrome\",\n \"request\": \"launch\",\n \"type\": \"chrome\",\n \"runtimeArgs\": [\n \"--allow-file-access-from-files\",\n \"--autoplay-policy=no-user-gesture-required\",\n \"--enable-features=SharedArrayBuffer\"\n ],\n \"file\": \"${workspaceFolder}/index.html\",\n \"cwd\": \"${workspaceFolder}/\",\n }\n ]\n}\n
"},{"location":"gettingstarted/helloworld/","title":"Create and Run WebAssembly Hello World","text":"This section shows you, step by step, how to to create a C \"hello world\" program for WebAssembly (Wasm) with twr-wasm, C, HTML, and JavaScript.
You will learn how to:
clang
wasm-ld
to create a helloworld.wasm fileYou can find code for a hello world example in the folder examples\\helloworld. It is similar, but not identical to this walk through. The primary differences are the paths for lib-c, lib-js, and include.
"},{"location":"gettingstarted/helloworld/#step-0-installation","title":"Step 0: Installation","text":"hello-proj
cd
into hello-proj
npm install twr-wasm
hello-proj\\\n\u251c\u2500\u2500package.json\n\u2514\u2500\u2500node_modules\\\n \u2514\u2500\u2500twr-wasm\\\n \u251c\u2500\u2500examples\\\n \u2514\u2500\u2500include\\\n \u2514\u2500\u2500lib-c\\\n \u2514\u2500\u2500lib-js\\\n \u2514\u2500\u2500LICENSE\n \u2514\u2500\u2500package.json\n \u2514\u2500\u2500readme.md\n
Create a file helloworld.c
in hello-proj
helloworld.c
#include <stdio.h>\n\nvoid hello() {\n printf(\"hello world\\n\");\n}\n
"},{"location":"gettingstarted/helloworld/#step-2-create-the-html","title":"Step 2: Create the HTML","text":"Create a file index.html
in hello-proj
index.html
<!doctype html>\n<html>\n<head>\n <title>Hello World</title>\n\n <script type=\"importmap\">\n {\n \"imports\": {\n \"twr-wasm\": \"./node_modules/twr-wasm/lib-js/index.js\"\n }\n }\n </script>\n\n</head>\n<body>\n <div id=\"twr_iodiv\"></div>\n\n <script type=\"module\">\n import {twrWasmModule} from \"twr-wasm\";\n\n const mod = new twrWasmModule();\n await mod.loadWasm(\"./helloworld.wasm\");\n await mod.callC([\"hello\"]);\n </script>\n</body>\n</html>\n
This example uses Import Maps, which are used when not using a bundler like WebPack or Parcel. For smaller projects, this can be simpler with a more clear debugging and development environment. This is the approach we will use for this example (no bundler).
The path in the importmap
section of index.html
should point to the location where you installed twr-wasm/lib-js
. The path above is correct for this project example with the indicated folder structure.
cd hello-proj\nclang --target=wasm32 -nostdinc -nostdlib -isystem ./node_modules/twr-wasm/include -c helloworld.c -o helloworld.o\nwasm-ld helloworld.o ./node_modules/twr-wasm/lib-c/twr.a -o helloworld.wasm --no-entry --initial-memory=131072 --max-memory=131072 --export=hello \n
The path to twr.a
and to include
should match your installation. The above path is correct for this example.
As an alternate to executing clang and wasm-ld from the shell, here is a Makefile that will work for this example:
MakefileCC := clang\nTWRCFLAGS := --target=wasm32 -nostdinc -nostdlib -isystem ./node_modules/twr-wasm/include\nCFLAGS := -c -Wall -O3 $(TWRCFLAGS)\nCFLAGS_DEBUG := -c -Wall -g -O0 $(TWRCFLAGS)\n\n.PHONY: default\n\ndefault: helloworld.wasm\n\nhelloworld.o: helloworld.c\n $(CC) $(CFLAGS) $< -o $@\n\nhelloworld.wasm: helloworld.o \n wasm-ld helloworld.o ./node_modules/twr-wasm/lib-c/twr.a -o helloworld.wasm \\\n --no-entry --initial-memory=131072 --max-memory=131072 \\\n --export=hello \n
Copy the above into a file named Makefile
and execute with make
(or mingw32-make
in windows).
The two easiest ways to load and execute your index.html
web page locally are:
You can run a local server to view your helloworld program.
hello-proj
folder (where your index.html
resides). python server.py
.http://localhost:8000/index.html
At this pont your folder structure should look like this:
hello-proj\\\n\u2514\u2500\u2500node_modules\\\n\u2514\u2500\u2500helloworld.c\n\u2514\u2500\u2500helloworld.o\n\u2514\u2500\u2500helloworld.wasm\n\u2514\u2500\u2500index.html\n\u2514\u2500\u2500Makefile\n\u2514\u2500\u2500package.json\n\u2514\u2500\u2500server.py\n
"},{"location":"gettingstarted/helloworld/#option-b-vs-code-launchjson","title":"Option B: VS Code launch.json","text":"Alternately, you can launch chrome without a local web server. Add an entry similar to the following to hello-proj\\.vscode\\launch.json
. This assumes your workspaceFolder is hello-proj
.
{\n \"configurations\": [\n {\n \"name\": \"Launch Chrome Hello, World!\",\n \"request\": \"launch\",\n \"type\": \"chrome\",\n \"runtimeArgs\": [\n \"--allow-file-access-from-files\",\n \"--autoplay-policy=no-user-gesture-required\",\n \"--enable-features=SharedArrayBuffer\"\n ],\n \"file\": \"${workspaceFolder}/index.html\",\n \"cwd\": \"${workspaceFolder}/\",\n }\n ]\n}\n
Once you have created this file, you:
--autoplay-policy=no-user-gesture-required
and --enable-features=SharedArrayBuffer
are not required for this simple \"hello world\" example, but will be needed if you request user input or you are using twrWasModuleAsync
.
You can find a live link to hello world on this page.
"},{"location":"gettingstarted/helloworld/#next-steps-after-hello-world","title":"Next steps after hello world","text":"A good way to get your own code up and running is to copy one of the examples, get it to build and run, then start modifying it. Note you will need to modify the paths for include
, lib-js
, lib-c
, etc. based on your project structure. The examples are all setup with relative paths assuming the folder structure twr-wasm\\examples\\<example>
The examples include MakeFiles.
\"Hello World\" uses the twr-wasm class twrWasmModule
. If you wish to use C blocking functions, such as twr_getc32
or twr_sleep
, you should use twrWasmModuleAsync
. This square calculator example shows how to do this.
If you wish to build an app that makes non-block calls into C, the balls example shows how to do this. The maze example uses a combination of blocking and non-blocking C functions.
"},{"location":"gettingstarted/helloworld/#debugging","title":"Debugging","text":"See the debugging section for debugging tips, including setting up Wasm source level debugging.
"},{"location":"gettingstarted/installation/","title":"Installing twr-wasm","text":"A simple way to install twr-wasm is:
npm install twr-wasm\n
See the \"Hello World walk through\" in the following section for more specifics.
There are actually two methods of installation with different pros and cons:
npm install
will install everything necessary to build your software: built libraries (lib-js, lib-c) and includes. In addition the examples are installed.git clone
will copy the above as well as the source and VS Code settings.When using twr-wasm
your applications needs to access both JavaScript and C twr-wasm libraries. This is explained in the installation sections below, as well as in the Hello World walk through.
npm install twr-wasm\n
After installation from npm, you will have a folder structure like this: node_modules\\\n twr-wasm\\\n examples\\\n include\\\n lib-c\\\n lib-js\\\n LICENSE\n package.json\n readme.md\n
The JavaScript and TypeScript exports are in lib-js
and should be found by VS Code, TypeScript or your bundler as usual when using a statement like import {twrWasmModule} from \"twr-wasm\"
. The C library (twr.a
) that you will need to link your C/C++ program to is found in the libs-c
folder, and the C/C++ include files that you will need to use in your C/C++ program are found in the include
folder. You will need to use paths to to these folders in your makefile. See the Hello World walk through for details.
There is no real downside to this installation method, except possibly: (1) it does not include source code (use git clone for that), and (b) the C libraries are buried inside your node_modules.
"},{"location":"gettingstarted/installation/#git-install","title":"git install","text":" git clone https://github.com/twiddlingbits/twr-wasm\n
This method of installation installs the complete code base, including source and built binaries.
The primary downside to this method is that the JavaScript side of twr-wasm will not be placed in a node_modules folder. This will create a little extra work to configure a bundler, TypeScript or VS Code to find the location of imports.
There are a few solutions to this. For example, in the provided Hello World example, a package.json
file with an alias
entry is used. This syntax is supported by the Parcel bundler:
{\n \"@parcel/resolver-default\": {\n \"packageExports\": true\n },\n \"alias\": {\n \"twr-wasm\": \"../../lib-js/index.js\"\n },\n \"dependencies\": {\n \"twr-wasm\": \"^2.0.0\"\n }\n}\n
The FFT example uses the paths
entry in the tsconfig.json
file. This is found by TypeScript, VS Code and the Parcel bundler. This is probably the best solution if you are using TypeScript.
\"paths\": {\n \"twr-wasm\": [\"./../../lib-js/index\"]\n}\n
The paths for alias
and paths
shown above are correct for the included examples, but will likely need to be adjust for your project.
All of the examples have makefiles that use a relative path for twr.a
and includes
. These paths will work fine if your code is in an examples sub-folder as a peer to the other examples. But assuming your code is in your own project folder elsewhere, you will need to determine the correct path to twr.a
and includes
for your project's makefile. Details on how to do this can be found in the following sections: Hello World walk through and the Compiler and Linker Options section.
To build C/C++ code for use in your Wasm project, you will need to install clang and the wasm-ld linker. If you are using Windows, more details can be found at the end of the Building Source section.
"},{"location":"gettingstarted/installation/#python","title":"python","text":"To use the included examples\\server.py
you will need to install python. server.py is a simple HTTP server for local testing that sets the correct CORS headers for twrWasmModuleAsync
. As explained in the following Hello World walk through, you can alternately execute HTML files directly using VS Code and Chrome.
This article describes techniques to transfer data between JavaScript/TypeScript and C/C++ when using WebAssembly. It delves a bit \u201cunder the covers\u201d to explain how this works when you use a library like twr-wasm or Emscripten. In this article, I am using twr-wasm for the examples. Emscripten does something similar.
For an example that illustrates the concepts discussed here, see: the callC example.
"},{"location":"gettingstarted/parameters/#webassembly-virtual-machine-intrinsic-capabilities","title":"WebAssembly Virtual Machine Intrinsic Capabilities","text":"The WebAssembly VM (often referred to as a Wasm \u201cRuntime\u201d) is limited to passing numbers between C functions and the Wasm host (I\u2019ll assume that\u2019s JavaScript for this document). In other words, if you are using the most basic WebAssembly capabilities provided by JavaScript, such as WebAssembly.Module
, WebAssembly.Instance
, and instance.exports
, your function calls and return types can only be:
These correspond to the WebAssembly spec support for: i32, i64, f32, and f64.
Note that a JavaScript number
is of type Float 64 (known as a double
in C/C++.). If you are storing an integer into a JavaScript number
, it is converted to a Float 64, and its maximum \"integer\" precision is significantly less than 64 bits (its about 52 bits, but this is a simplification). As a result, to use a 64-bit integers with JavaScript the bigint
type is used.
When using 32-bit WebAssembly (by far the most common default), and you call a C function from JavaScript without using any \u201chelper\u201d libraries (like twr-wasm), the following argument types can be passed:
number
type is converted to an Integer 32 and passed to C when the C function prototype specifies a signed or unsigned int
, long
, int32_t
, or a pointer type. All of these are 32 bits in length in wasm32.bigint
type is converted to an Integer 64 and passed to C when the C function prototype specifies signed or unsigned int64_t
(or equivalent). Attempting to pass a JavaScript number
to a C int64_t
will fail with a JavaScript runtime error.number
type is converted to a Float 32 when the C function prototype specifies a float
.number
type is passed as a Float 64 when the C function prototype specifies a double
.The same rules apply to the return types.
"},{"location":"gettingstarted/parameters/#c-structs-javascript-c","title":"C Structs: JavaScript <--> C","text":"This section shows how to create a C struct
in JavaScript, then pase it to a C function, and then read the modified C struct
in JavaScript.
Although the techniques described here are explained with a struct
example, the basic techniques are used with other data types as well (such as strings). For common data types, like a string, libraries like twr-wasm will handle these details for you automatically.
To create and pass a C struct
from JavaScript to C, the technique is to call the WebAssembly C malloc
from JavaScript to allocate WebAssembly memory and then manipulating the memory in JavaScript. One complexity is that each struct entry\u2019s memory address needs to be calculated. And when calculating the WebAssembly Memory indices for the struct entries, C structure padding must be accounted for.
Before we delve into the actual code, lets review C struct entry padding.
In clang, if you declare this structure in your C code:
struct test_struct {\n int a;\n char b;\n int *c;\n};\n
int a
, will be at offset 0 in memory (from the start of the struct
in memory).char b
, will be at offset 4 in memory. This is expected since the length of an int is 4 bytes.int *c
, will be at offset 8 in memory, not at offset 5 as you might expect. The compiler adds three bytes of padding to align the pointer to a 4-byte boundary.This behavior is dependent on your compiler, cpu, and whether you are using 32 or 64-bit architecture. For wasm32 with clang:
If you are not familiar with structure padding, there are many articles on the web.
Alignment requirements are why twr-wasm malloc
(and GCC malloc
for that matter) aligns new memory allocations on an 8-byte boundary.
We can create and initialize the above struct test_struct
like this in JavaScript:
//...\nconst mod = new twrWasmModule();\n//...\nconst structSize=12;\nconst structIndexA=0;\nconst structIndexB=4;\nconst structIndexC=8; // compiler allocates pointer on 4 byte boundaries\nlet structMem=await mod.malloc(structSize);\nlet intMem=await mod.malloc(4);\nmod.setLong(structMem+structIndexA, 1);\nmod.mem8[structMem+structIndexB]=2; // you can access the memory directly with the mem8, mem32, and memD (float64 aka double) byte arrays.\nmod.setLong(structMem+structIndexC, intMem);\nmod.setLong(intMem, 200000);\n
note that:
await mod.malloc(structSize)
is a shortcut for: await mod.callC([\"malloc\", structSize])
mod.malloc
returns a C pointer as a number
. This pointer is also an index into WebAssembly.Memory
-- which is exposed as the byte array (Uint8Array
) via mod.mem8
by twr-wasm.struct
in JavaScript/TypeScript, you have to do a bit of arithmetic to find the correct structure entry.int *c
is a pointer to an int
. So a separate malloc
to hold the int
is needed. setLong
to set a byte. Instead you access the byte array view of the WebAssembly memory with mod.mem8
. Functions like mod.setLong
manipulate this byte array for you.mod.mem8
(Uint8Array), you can also access WebAssembly.Memory directly via mod.mem32
(Uint32Array), and mod.memD
(Float64Array).Assume we have C code that adds 2 to each entry of the test_struct
:
__attribute__((export_name(\"do_struct\")))\nvoid do_struct(struct test_struct *p) {\n p->a=p->a+2;\n p->b=p->b+2;\n (*p->c)++;\n (*p->c)++;\n}\n
Once the struct
has been created in JavaScript, you can call the C function do_struct
that adds 2 to each entry like this in twr-wasm:
await mod.callC([\"do_struct\", structMem]); // will add two to each value\n
"},{"location":"gettingstarted/parameters/#accessing-returned-c-struct-in-javascript","title":"Accessing returned C struct in JavaScript","text":"You access the returned elements like this using JavaScript:
success=mod.getLong(structMem+structIndexA)==3;\nsuccess=success && mod.mem8[structMem+structIndexB]==4;\nconst intValPtr=mod.getLong(structMem+structIndexC);\nsuccess=success && intValPtr==intMem;\nsuccess=success && mod.getLong(intValPtr)==200002;\n
You can see the additional complexity of de-referencing the int *
.
You can free the malloced memory like this:
await mod.callC([\"free\", intMem]); // unlike malloc, there is no short cut for free, yet\nawait mod.callC([\"free\", structMem]);\n
The complete code for this example is here.
"},{"location":"gettingstarted/parameters/#passing-strings-from-javascript-to-cc-webassembly","title":"Passing Strings from JavaScript to C/C++ WebAssembly","text":"Although you can use the technique I am about to describe here directly (by writing your own code), it is generally accomplished by using a third-party library such as twr-wasm or Emscripten. These libraries handle the nitty-gritty for you.
To pass a string from JavaScript/TypeScript to a WebAssembly module, the general approach is to:
malloc
from JavaScript. malloc
returns a pointer, which is an index into the WebAssembly Memory.In the case of twr-wasm, the above steps are handled automatically for you by the callC
function:
mod.callC([\"my_function\", \"this is my string\"]); // mod is instance of twrWasmModule\n
Under the covers, to pass \"this is my string\" from JavaScript to the C Web Assembly function, callC
will execute code like this:
// twrWasmModule member function\nasync putString(sin:string, codePage = codePageUTF8) {\n const ru8 = this.stringToU8(sin, codePage); // convert a string to UTF8 encoded characters stored in a Uint8Array\n const strIndex = await this.malloc(ru8.length + 1); // shortcut for: await this.callC([\"malloc\", ru8.length + 1]);\n this.mem8.set(ru8, strIndex); // mem8 is of type Uint8Array and is the Wasm Module\u2019s Memory\n this.mem8[strIndex + ru8.length] = 0;\n return strIndex;\n}\n
this.malloc
is the standard C runtime malloc
function, provided by twr-wasm, and linked into your .wasm
code that is loaded into the WebAssembly Module. Likewise, twr-wasm will call free
after the function call is executed."},{"location":"gettingstarted/parameters/#returning-a-string-from-cc-webassembly-to-javascript","title":"Returning a String from C/C++ WebAssembly to JavaScript","text":"Returning a string from C to JavaScript is the reverse of passing in a string from JavaScript to C. When the \u201craw\u201d WebAssembly capabilities are used (WebAssembly.Module
, etc.) and your C code looks like this:
return(\"my string\");\n
The WebAssembly VM and JavaScript host will cause your JavaScript to receive an unsigned 32-bit integer. This is the pointer to the string, cast to an unsigned 32-bit integer. This integer is an index into the WebAssembly Memory.
twr-wasm provides a function to pull the string out of WebAssembly Memory and convert the character encoding to a JavaScript string. JavaScript strings are Unicode 16, but twr-wasm supports ASCII, UTF-8, and windows-1252 string encoding. When extracted and converted, a copy of the string is made.
const retStringPtr = await mod.callC([\"ret_string_function\"]);\nconsole.log(mod.getString(retStringPtr));\n
The retStringPtr
is an integer 32 (but converted to a JavaScript number
, which is Float 64). This integer is an index into the WebAssembly Memory.
When callC
in twr-wasm is used to pass an ArrayBuffer to and from C/C++, some details are handled for you. The technique is similar to that used for a string
or as performed manually for a struct
above, with the following differences:
ArrayBuffers
have entries of all the same length, so the index math is straight forward and now struct
padding is needed.ArrayBuffer
is passed to a function, the function receives a pointer to the malloc
memory. If the length is not known by the function, the length needs to be passed as a separate argument.callC
returns, any modifications made to the memory by the C code are reflected back into the ArrayBuffer
.Here is an example:
let ba = new Uint8Array(4);\nba[0] = 99; ba[1] = 98; ba[2] = 97; ba[3] = 6;\nconst ret_sum = await mod.callC([\"param_bytearray\", ba.buffer, ba.length]);\n
See this example for the complete example.
"},{"location":"gettingstarted/parameters/#passing-a-javascript-object-to-webassembly","title":"Passing a JavaScript Object to WebAssembly","text":""},{"location":"gettingstarted/parameters/#simple-case-use-c-struct","title":"Simple Case - use C struct","text":"For a simple object like this:
const a = 'foo';\nconst b = 42;\n\nconst obj = {\n a: a,\n b: b\n};\n
It is straightforward to convert to a C struct like this:
struct obj {\n const char* a;\n int b;\n};\n
To pass this JavaScript object to WebAssembly, a C struct is created (using the struct
techniques described above). Each object entry is then copied into the corresponding C struct
entry (using the struct
and string techniques described above)."},{"location":"gettingstarted/parameters/#more-complicated-object","title":"More Complicated Object","text":"A JavaScript object can contain entries that are of more complexity than simple C data types. For example:
const a = 'foo';\nconst b = 42;\nconst map = new Map();\nmap1.set('a', 1);\nmap1.set('b', 2);\nmap1.set('c', 3);\nconst object2 = { a: a, b: b, c: map };\n
In this case, you are going to have to do more work. An approach is to use the libc++ map
class, which is similar to the JavaScript Map
. You could also perhaps use the libc++ vector
.
To handle this more complicated JavaScript object with a Map
entry, an approach is to export functions from WebAssembly to create and add entries to the libc++ map
(you need to use extern 'C'
to export these C++ access functions as C functions). In otherworld, you might export from your Wasm Module C functions like this:
void* createMap(); // return an unsigned long Map ID\nvoid addIntToMap(void* mapID, int newInt);\n
You would then use these functions in JavaScript to build your C++ map
. JavaScript would access this map
using the unsigned long
identifier (the void *
returned by createMap
). After creating and adding entries to the map
, you would set this MapID to object2.c
.
There are alternative approaches. For example, you could convert the JavaScript Map
to a C struct, by enumerating every entry in the Map
. Your C struct might look like: `
struct entry {\n char* name;\n int value;\n};\n\nstruct mapUnroll {\n int MapLen;\n struct entry* entries[];\n};\n
This approach is probably even more work, less general purpose, and less efficient.
"},{"location":"gettingstarted/parameters/#summary","title":"Summary","text":"I hope this has demystified how JavaScript values are passed to and from WebAssembly. In many cases, functions like twr-wasm's mod.callC
will handle the work for you. But in more bespoke cases, you will have to handle some of the work yourself.
This section describes how you can direct C/C++ standard input or output to or from a div or canvas tag in a twr-wasm C/C++ Wasm project.
"},{"location":"gettingstarted/stdio/#use-div-or-canvas-tag","title":"Use div or canvas tag","text":"Standard input and output can be directed to a <div>
or to a <canvas>
HTML tag. Using a <div>
is a simple way to display the output of a printf
, or to get input from getc
(using traditional standard library blocking input).
A <canvas>
tag can be used by twr-wasm to create a simple ANSI style terminal or console. This windowed terminal supports the same streamed output and input features as a does a div tag, but also supports x,y coordinates, colors, and other features. The window console supports chunky (low res) graphics (each character cell can be used as a 2x3 graphic array).
Another difference between a div stream and a canvas stream, is that a div tag will grow as more text is added. On the other hand, a canvas tag has a fixed width and height, and additional text will cause a scroll as it fills up the window.
Unicode characters are supported by stdout
and stdin
(see Character Encoding Support with twr-wasm).
If you wish to use a div or canvas tag for stdio when using twr-wasm, in your HTML file add a <div id=\"twr_iodiv\">
or alternately a <canvas id=\"twr_iocanvas\">
tag.
<div id=\"twr_iodiv\">
will be used for stdin
and stdout
if found.<canvas id=\"twr_iocanvas\">
will be used for stdin
and stdout
if it exists and no div found. <div>
or <canvas>
is defined in your HTML, then stdout
is sent to the debug console in your browser. And stdin
is not available.Note that you can also add a <canvas id=\"twr_d2dcanvas\">
to your HTML to define a canvas to be used by twr-wasm's 2D drawing APIs.
stderr
streams to the browser's debug console.
<div>
View square demo Source simple \"terminal\" via <canvas>
View hello world demo Source \"cli\" with a <canvas>
stdio View CLI demo using libc++ Source"},{"location":"gettingstarted/stdio/#io-console-docs","title":"IO Console Docs","text":"stdin
, stdout
, and stderr
are abstracted by a twr-wasm IO Consoles.
Stdin
and stdout
can support UTF-8 or Windows-1252 character encodings (see Character Encoding Support with twr-wasm).
#include <stdio.h>
to access stdout
, stdin
, stderr
, and FILE
.
FILE is supported for user input and output, and for stderr. File i/o (to a filesystem) is not currently supported.
"},{"location":"gettingstarted/stdio/#stdout-and-stderr-functions","title":"stdout and stderr functions","text":"You can use these functions to output to the standard library defines stderr
or stdout
:
fputc, putc, vfprintf, fprintf, fwrite\n
You can also use the IO Console functions referenced above to send to stdout
and stderr
.
These functions go to stdout:
printf, vprintf, puts, putchar\n
Note that when characters are sent to the browser console using stderr
they will not render to the console until a newline, return, or ASCII 03 (End-of-Text) is sent.
For example:
#include <stdio.h>\n\nfprintf(stderr, \"hello over there in browser debug console land\\n\");\n
A more common method to send output to the debug console is to use twr_conlog
. See General C API Section.
Reading from stdin
is blocking, and so twrWasmModuleAsync
must be used to receive keys from stdin. See the next section for the needed JavaScript line.
You can get characters from stdin
with any of these functions:
io_mbgets
- get a multibyte string from the stdin IoConsole using the current locale character encoding io_getc32
- gets a 32 bit unicode code point the stdin IoConsoleio_mbgetc
- get a single multibyte character from the stdin IoConsole using the current locale character encodingtwr_mbgets
- similar to io_mbgets
, but assumes stdin
is the input sourcegetc
(sames as fgetc
) - get a single byte from a FILE * (struct IoConsole *) -- which should be stdin. Returning ASCII or extended ASCII (window-1252 encoding).twrWasmModuleAsync
must be used to receive keys from stdin. In addtion, you should add a line like the following to your JavaScript for stdin to work:
If using twr_iodiv
document.getElementById(\"twr_iodiv\").addEventListener(\"keydown\",(ev)=>{amod.keyDownDiv(ev)});\n
If using twr_iocanvas
document.getElementById(\"twr_iocanvas\").addEventListener(\"keydown\",(ev)=>{amod.keyDownCanvas(ev)});\n
You likely want a line like this to automatically set the focus to the div or canvas element (so the user doesn't have to click on the element to manually set focus. Key events are sent to the element with focus.):
document.getElementById(\"twr_iocanvas\").focus();\n
You will also need to set the tabindex attribute in your div tag like this:
<div id=\"twr_iodiv\" tabindex=\"0\"></div>\n
See the stdio-div and stdio-canvas examples.
"},{"location":"gettingstarted/stdio/#sending-asyncronous-events-to-wasm-functions","title":"Sending asyncronous events to Wasm functions","text":"Note that this section describes blocking input using stdin. As an alternative, you can send events (keyboard, mouse, timer, etc) to a non-blocking C function from JavaScript using callC
. See the balls
example.
The source can be found at:
https://github.com/twiddlingbits/twr-wasm\n
The main
branch contains the latest release. The dev
branch is work in progress.
You will need these core tools, versions used in release are in ():
In addition, you might need:
There is a deprecated gcc build that I used to use for testing, but now the tests are executed in wasm.
"},{"location":"more/building/#to-build-the-libraries-lib-c-lib-js","title":"To Build the Libraries (lib-c, lib-js)","text":"cd source\nmake\n
or on windows cd source\nmingw32-make\n
"},{"location":"more/building/#to-build-the-examples","title":"To Build the Examples","text":"See examples/readme.md for more information.
To build the examples, but not bundle them.
cd examples\nsh buildall.sh\n
To build bundles:
sh buildbundles.sh\n
"},{"location":"more/building/#to-build-the-docs","title":"To Build the docs","text":"The docs are created using the material theme for mkdocs.
In twr-wasm root folder:
mkdocs build\n
The destination of the build is found in the mkdocs.yml
file (site_dir: azure/docsite/
).
Usually the docs are built as part of building the static web site that hosts the docs and examples. This is accomplished using this shell script (found in examples folder):
buildazure.sh\n
"},{"location":"more/building/#to-build-libc-for-wasm-and-twr-wasm","title":"To Build libc++ for Wasm and twr-wasm","text":"See the instructions in the comments in the shell script source\\libcxx\\buildlibcxx.sh
Here is how I installed the tools for windows:
install MSYS2 \n 1. https://www.msys2.org/\n 2. After the install completes, run UCRT64 terminal by clicking on the MSYS2 UCRT64 in the Start menu\n 3. pacman -Syuu\n\n install gcc using MSYS2 UCRT64\n 1. Use MSYS2 UCRT64 terminal (per above)\n 1. pacman -S mingw-w64-ucrt-x86_64-toolchain\n\n install clang and wasm-ld using MSYS2 UCRT64\n 2. Use MSYS2 UCRT64 (per above)\n 1. pacman -S mingw-w64-ucrt-x86_64-clang\n 2. pacman -S mingw-w64-x86_64-lld\n\nupdate PATH env variable using the windows control panel (search for path)\n 2. added C:\\msys64\\ucrt64\\bin \n 3. added C:\\msys64\\mingw64\\bin \n 4. added C:\\msys64\\usr\\bin (for sh.exe used by mingw32-make)\n
wabt tools: can be found here https://github.com/WebAssembly/wabt/releases
"},{"location":"more/production/","title":"CORS headers needed to use twrWasmModuleAsync","text":"Important Production Note
twr-wasm class twrWasmModuleAsync
uses SharedArrayBuffers
, and there are special CORS headers that need to be configured to use SharedArrayBuffers
, that are not widely enabled by default on web servers. The server.py or staticwebapp.config.json examples show which headers to set (also see the SharedArrayBuffer
documentation online).
HTML browsers can load a WebAssembly module, and execute it's bytecode in a virtual machine. To create this bytecode (.wasm file) from C/C++, you compile your code using clang with the target code format being WebAssembly (aka Wasm) byte code. If you are not using a support library like twr-wasm or emscripten, there are a few issues that one immediately encounters trying to execute code that is more complicated than squaring a number.
The Wasm virtual machine simply executes the instructions that are generated by the clang compiler and linked by the linker into the .wasm file. The first issue encountered is that some code that is generated by a compiler assumes a compiler support library will be linked to your code. That is, clang code generation will produce calls to compiler support routines for floating point, memcpy
, and the like. In clang, these support routines are in the \"compile-rt\" support library. Typically clang handles this behind to scenes for you. But support for a WebAssembly version of this compiler support library is not (as of this writing) included in a clang distribution.
The next level up the library stack is the standard C runtime library. This library provides functions like malloc
and printf
. And then built on the standard C runtime library is the standard c++ library - like libc++. WebAssembly versions of these libraries are also not part of a clang distribution.
To get access to WebAssembly versions of these libraries you need to use emscripten or twr-wasm.
The second problem is that all the function calls between your Wasm module and your javascript are limited to parameters and return values that are numbers (integer and float). No strings, arrays, struct pointers, etc. (for more on this see this doc).
The third problem is that legacy C code or games often block, and when written this way they don't naturally integrate with the JavaScript asynchronous programming model.
twr-wasm is a static C library (twr.a) that you can link to your clang C/C++ Wasm code, as well as a set of JavaScript/TypeScript modules that solve these issues.
In addition, twr-wasm provides APIs that you can use in your WebAssembly code - such as Canvas compatible 2D drawing APIs, a simple ANSI terminal emulator, character encoding support, and more.
"}]} \ No newline at end of file +{"config":{"lang":["en"],"separator":"[\\s\\-]+","pipeline":["stopWordFilter"]},"docs":[{"location":"","title":"Learn WebAssembly with twr-wasmDocumentation and Examples","text":""},{"location":"#easier-cc-webassembly","title":"Easier C/C++ WebAssembly","text":"Version 2.3.1
twr-wasm is a simple, lightweight and easy to use library for building C/C++ WebAssembly code directly with clang. It solves some common use cases with less work than the more feature rich emscripten.
twr-wasm is easy to understand, and has some great features. You can call blocking functions. You can input and print streaming character i/o to a <div>
tag, use a <canvas>
element as an ANSI terminal, and use 2D drawing apis (that are compatible with JavaScript Canvas APIs) to draw to a <canvas>
element.
twr-wasm allows you to run C/C++ code in a web browser. Legacy code, libraries, full applications, or single functions can be integrated with JavaScript and TypeScript.
twr-wasm is designed to be used with the standard llvm clang compiler and tools.
twr-wasm was previously named tiny-wasm-runtime.
"},{"location":"#live-webassembly-examples-and-source","title":"Live WebAssembly Examples and Source","text":"Name View Live Link Source Link Bouncing Balls (C++) View bouncing balls Source for balls Pong (C++) View Pong Source for Pong Maze Gen/Solve (Win32 C Port) View live maze Source for maze Input/Output with<div>
View square demo Source Mini-Terminal (hello world using <canvas>
) View demo Source CLI using libc++ and <canvas>
) View console Source"},{"location":"#key-features","title":"Key Features","text":"<div>
tags in your HTML page<canvas>
based \"terminal\"Here is the simplest twr-wasm
example.
#include <stdio.h>\n\nvoid hello() {\n printf(\"hello, world!\\n\");\n}\n
index.html<head>\n <title>Hello World</title>\n</head>\n<body>\n <div id=\"twr_iodiv\"></div>\n\n <script type=\"module\">\n import {twrWasmModule} from \"twr-wasm\";\n\n const mod = new twrWasmModule();\n await mod.loadWasm(\"./helloworld.wasm\");\n await mod.callC([\"hello\"]);\n </script>\n</body>\n
"},{"location":"#on-github","title":"On Github","text":"https://github.com/twiddlingbits/twr-wasm
"},{"location":"#why","title":"Why?","text":"The Wasm Runtime Limitations section explains why a library like twr-wasm is needed to use WebAssembly.
"},{"location":"#version-2-vs-1","title":"Version 2 vs. 1","text":"Please post feedback (it worked for you, didn't work, requests, questions, etc) at https://github.com/twiddlingbits/twr-wasm/
"},{"location":"api/api-c-con/","title":"WebAssembly Console API","text":"twr-wasm for WebAssembly provides a console API for abstracting I/O. This console API is used by stdin, stdout, and stderr, as well as the ANSI Terminal. Streaming and Windowed I/O is supported.
This section describes the C character based input/output console API this is abstracted by struct IoConsole
.
Consoles can be \"tty\" aka \"streamed\", or they can be \"windowed\" (aka a \"terminal\").
Also see stdio
"},{"location":"api/api-c-con/#examples","title":"Examples","text":"Name View Live Link Source Link \"terminal\" in/out with a<canvas>
View mini-term demo Source"},{"location":"api/api-c-con/#getting-stderr-stdin-stdout","title":"Getting stderr, stdin, stdout","text":"stdio.h defines stdin
, stdout
, stderr
as explained here: stdio
stdio.h also defines FILE
like this:
typedef struct IoConsole FILE; \n
from <stdio.h>
:
#define stderr (FILE *)(twr_get_stderr_con())\n#define stdin (FILE *)(twr_get_stdio_con())\n#define stdout (FILE *)(twr_get_stdio_con())\n
"},{"location":"api/api-c-con/#getting-a-new-console","title":"Getting a new console","text":"stdin and stdout are set as explaind here. However, in unusual cases you might want to access the various consoles directly, regardless of how stdin, stdout, or stderr are set. You can do so like this:
"},{"location":"api/api-c-con/#io_nullcon","title":"io_nullcon","text":"Returns an IoConsole that goes to the bit bucket. io_getc32 will return 0.
#include \"twr-io.h\"\n\nstruct IoConsole* io_nullcon(void);\n
"},{"location":"api/api-c-con/#twr_debugcon","title":"twr_debugcon","text":"Returns an IoConsole that goes to the browser's debug console.
#include \"twr-crt.h\"\n\nstruct IoConsole* twr_debugcon(void);\n
"},{"location":"api/api-c-con/#twr_divcon","title":"twr_divcon","text":"Returns an IoConsole that goes to <div id=\"twr_iodiv\">
, if it exists.
#include \"twr-crt.h\"\n\nstruct IoConsole* twr_divcon(void);\n
"},{"location":"api/api-c-con/#twr_windowcon","title":"twr_windowcon","text":"Returns an IoConsole that goes to <canvas id=\"twr_iocanvas\">
, if it exists.
NOTE: Only one call can be made to this function, and it is usually made by the twr-wasm C runtime, so you likely won't call this function.
#include \"twr-crt.h\"\n\nstruct IoConsole* twr_windowcon(void);\n
"},{"location":"api/api-c-con/#io-console-functions","title":"IO Console Functions","text":""},{"location":"api/api-c-con/#io_putc","title":"io_putc","text":"Sends a byte to an IoConsole and supports the current locale's character encoding. This function will \"stream\" using the current code page. In other words, if you io_putc
ASCII, it will work as \"normal\". If the current locale is set to 1252, then you can send windows-1252 encoded characters. If the current locale is UTF-8, then you can stream UTF-8 (that is, call io_putc
once for each byte of the multi-byte UTF-8 character).
Note that when characters are sent to the browser console using stderr
they will not render to the console until a newline, return, or ASCII 03 (End-of-Text) is sent.
#include \"twr-io.h\"\n\nvoid io_putc(struct IoConsole* io, unsigned char c);\n
"},{"location":"api/api-c-con/#io_putstr","title":"io_putstr","text":"Calls io_putc
for each byte in the passed string.
#include \"twr-io.h\"\n\nvoid io_putstr(struct IoConsole* io, const char* s);\n
"},{"location":"api/api-c-con/#io_printf","title":"io_printf","text":"Identical to fprintf
, however io_printf will call io_begin_draw
and io_end_draw
around its drawing activities -- resulting in snapper performance.
For example:
#include \"twr-io.h\"\n\nio_printf(twr_debugcon(), \"hello over there in browser debug console land\\n\");\n
or
#include <stdio.h>\n#include <twr_io.h>\n\nio_printf(stdout, \"hello world\\n\");\n
#include <twr_io.h>\n\nvoid io_printf(struct IoConsole *io, const char *format, ...);\n
"},{"location":"api/api-c-con/#io_getc32","title":"io_getc32","text":"Waits for the user to enter and then returns a unicode code point. Currently only really works with an IoConsole that is stdin.
To return characters encoded with the current locale, see io_mbgetc
#include <twr_io.h>\n\nint io_getc32(struct IoConsole* io);\n
"},{"location":"api/api-c-con/#io_mbgetc","title":"io_mbgetc","text":"io_mbgetc
will get a character from stdin and encode it using the character encoding of the LC_CTYPE category of the current locale. \"C\" will use ASCII. UTF-8 and windows-1252 are also supported.
#include <twr_io.h>\n\nvoid io_mbgetc(struct IoConsole* io, char* strout);\n
"},{"location":"api/api-c-con/#io_mbgets","title":"io_mbgets","text":"Gets a string from an IoConsole (which needs to be stdin). Returns when the user presses \"Enter\". Displays a cursor character and echos the inputted characters, at the current cursor position. Uses character encoding of LC_TYPE of current locale.
#include <twr_io.h>\n\nchar *io_mbgets(struct IoConsole* io, char *buffer );\n
"},{"location":"api/api-c-con/#io_get_cursor","title":"io_get_cursor","text":"Returns an integer of the current cursor position. The cursor is where the next io_putc is going to go.
For windowed consoles, the cursor position ranges from [0, width*height-1], inclusive.
#include <twr_io.h>\n\nint io_get_cursor(struct IoConsole* io);\n
"},{"location":"api/api-c-con/#io_set_colors","title":"io_set_colors","text":"For windowed consoles only.
Sets a 24 bit RGB default color for the foreground and background. The prior default colors are changed (lost). For example, if you set the default colors when you created the window (see stdio), the defaults will no longer be active. Use io_get_colors
to save existing colors for later restoration using io_set_colors
.
A call to io_set_colors
doesn't actually cause any on screen changes. Instead, these new default colors are used in future draw and text calls. A foreground and background color is set for each cell in the console window. The cell's colors are set to these default foreground/background colors when a call to io_setc
, io_setreset
, etc is made.
#include <twr_io.h>\n\nvoid io_set_colors(struct IoConsole* io, unsigned long foreground, unsigned long background);\n
"},{"location":"api/api-c-con/#io_get_colors","title":"io_get_colors","text":"For windowed consoles only.
Gets the current default colors.
#include <twr_io.h>\n\nvoid io_get_colors(struct IoConsole* io, unsigned long *foreground, unsigned long *background);\n
"},{"location":"api/api-c-con/#io_cls","title":"io_cls","text":"For windowed consoles only.
Clears the screen. That is, all character cells in the window are set to a space, their colors are reset to the current default colors (see io_set_colors
).
#include <twr_io.h>\n\nvoid io_cls(struct IoConsoleWindow* iow);\n
"},{"location":"api/api-c-con/#io_setc","title":"io_setc","text":"For windowed consoles only.
Sets a window cell to a character. Sends a byte to an IoConsole and supports the current locale's character encoding. This function will \"stream\" using the current code page. In other words, if you io_setc
ASCII, it will work as \"normal\". If the current locale is set to 1252, then you can send windows-1252 encoded characters. If the current locale is UTF-8, then you can stream UTF-8 (that is, call io_setc
once for each byte of the multi-byte UTF-8 character).
#include <twr_io.h>\n\nbool io_setc(struct IoConsoleWindow* iow, int location, unsigned char c);\n
"},{"location":"api/api-c-con/#io_setc32","title":"io_setc32","text":"For windowed consoles only.
Sets a window cell to a unicode code point. The colors are set to the defaults (see io_set_colors
).
#include <twr_io.h>\n\nvoid io_setc32(struct IoConsoleWindow* iow, int location, int c);\n
"},{"location":"api/api-c-con/#io_setreset","title":"io_setreset","text":"For windowed consoles only.
Sets or resets (clears) a chunky graphics \"pixel\". Each character cell can also be a 2x3 grid of graphic \"pixels\". In other words, the terminal window has pixel dimensions of width2 x height3.
The color will be set to the defaults if the impacted cell is not a graphics cell. If it is an existing graphics cell, the colors don't change.
See the stdio-canvas
example.
#include <twr_io.h>\n\nbool io_setreset(struct IoConsoleWindow* iow, int x, int y, bool isset);\n
"},{"location":"api/api-c-con/#io_point","title":"io_point","text":"For windowed consoles only.
Checks if a chunky graphics \"pixel\" is set or clear. See io_setreset
.
#include <twr_io.h>\n\nbool io_point(struct IoConsoleWindow* iow, int x, int y);\n
"},{"location":"api/api-c-con/#io_set_cursor","title":"io_set_cursor","text":"Moves the cursor. See io_get_cursor
.
#include <twr_io.h>\n\nvoid io_set_cursor(struct IoConsoleWindow* iow, int loc);\n
"},{"location":"api/api-c-con/#io_begin_draw","title":"io_begin_draw","text":"For windowed consoles only.
This call (and its matching io_end_draw) are not required. But if you bracket any call sequence that draws to the terminal window with an io_begin_draw
and io_end_draw
, the updates will be batched into one update. This will increase performance and usually prevents the user from seeing partial updates.
io_begin_draw
can be nested.
See the stdio-canvas example.
#include <twr_io.h>\n\nvoid io_begin_draw(struct IoConsole* io);\n
"},{"location":"api/api-c-con/#io_end_draw","title":"io_end_draw","text":"For windowed consoles only.
See io_begin_draw
.
#include <twr_io.h>\n\nvoid io_end_draw(struct IoConsole* io);\n
"},{"location":"api/api-c-d2d/","title":"2D Draw C API for WebAssembly","text":"This section describes twr-wasm's C D2D API, which allows your WebAssembly module to call many of the JavaScript Canvas APIs. There is also a C++ canvas wrapper class in source/twr-cpp
used by the balls and pong examples.
To create a canvas surface that you can draw to using the twr-wasm 2D C drawing APIs, add a canvas tag to your HTML named twr_d2dcanvas
like this example (you can use any width/height you like):
<canvas id=\"twr_d2dcanvas\" width=\"600\" height=\"600\"></canvas>\n
To draw using the C 2D Draw API:
d2d_start_draw_sequence
d2d_fillrect
d2d_end_draw_sequence
Commands are queued until flushed -- which will take the batch of queued draw commands, and execute them. The 2D draw APIs will work with either twrWasmModule
or twrWasmModuleAsync
. With twrWasmModuleAsync
, the batch of commands is sent from the worker thread over to the JavaScript main thread for execution. By batching the calls between calls to d2d_start_draw_sequence
and d2d_end_draw_sequence
, performance is improved.
d2d_flush
waits for the commands to finish execution before returning. d2d_flush
is called automatically by d2d_end_draw_sequence
and so you generally don't need to call it manually.
You pass an argument to d2d_start_draw_sequence
specifying how many instructions will trigger an automatic call to d2d_flush
. You can make this larger for efficiency, or smaller if you want to see the render progress more frequently. There is no limit on the size of the queue, except memory used in the Wasm module. The d2d_flush
function can be called manually call, but this is not normally needed, unless you would like to ensure a sequence renders before your d2d_end_draw_sequence
is called, or before the count passed d2d_start_draw_sequence
is met.
If you are using twrWasmModuleAsync
, or if you are re-rendering the entire frame for each animation update, you should ensure that all of your draws for a complete frame are made without an explicit or implicit call to d2d_flush
in the middle of the draw sequence, as this may cause flashing.
These are the Canvas APIs currently available in C:
struct d2d_draw_seq* d2d_start_draw_sequence(int flush_at_ins_count);\nvoid d2d_end_draw_sequence(struct d2d_draw_seq* ds);\nvoid d2d_flush(struct d2d_draw_seq* ds);\nint d2d_get_canvas_prop(const char* prop);\n\nvoid d2d_fillrect(struct d2d_draw_seq* ds, double x, double y, double w, double h);\nvoid d2d_strokerect(struct d2d_draw_seq* ds, double x, double y, double w, double h);\nvoid d2d_filltext(struct d2d_draw_seq* ds, const char* str, double x, double y);\nvoid d2d_fillcodepoint(struct d2d_draw_seq* ds, char c, double x, double y);\n\nvoid d2d_measuretext(struct d2d_draw_seq* ds, const char* str, struct d2d_text_metrics *tm);\nvoid d2d_save(struct d2d_draw_seq* ds);\nvoid d2d_restore(struct d2d_draw_seq* ds);\n\nvoid d2d_setlinewidth(struct d2d_draw_seq* ds, double width);\nvoid d2d_setfillstylergba(struct d2d_draw_seq* ds, unsigned long color);\nvoid d2d_setstrokestylergba(struct d2d_draw_seq* ds, unsigned long color);\nvoid d2d_setfillstyle(struct d2d_draw_seq* ds, const char* css_color);\nvoid d2d_setstrokestyle(struct d2d_draw_seq* ds, const char* css_color);\nvoid d2d_setfont(struct d2d_draw_seq* ds, const char* font);\n\nvoid d2d_createlineargradient(struct d2d_draw_seq* ds, long id, double x0, double y0, double x1, double y1);\nvoid d2d_createradialgradient(struct d2d_draw_seq* ds, long id, double x0, double y0, double radius0, double x1, double y1, double radius1);\nvoid d2d_addcolorstop(struct d2d_draw_seq* ds, long gradID, long position, const char* csscolor);\nvoid d2d_setfillstylegradient(struct d2d_draw_seq* ds, long gradID);\nvoid d2d_releaseid(struct d2d_draw_seq* ds, long id);\n\nvoid d2d_beginpath(struct d2d_draw_seq* ds);\nvoid d2d_fill(struct d2d_draw_seq* ds);\nvoid d2d_stroke(struct d2d_draw_seq* ds);\nvoid d2d_moveto(struct d2d_draw_seq* ds, double x, double y);\nvoid d2d_lineto(struct d2d_draw_seq* ds, double x, double y);\nvoid d2d_arc(struct d2d_draw_seq* ds, double x, double y, double radius, double start_angle, double end_angle, bool counterclockwise);\nvoid d2d_bezierto(struct d2d_draw_seq* ds, double cp1x, double cp1y, double cp2x, double cp2y, double x, double y);\n\nvoid d2d_imagedata(struct d2d_draw_seq* ds, long id, void* mem, unsigned long length, unsigned long width, unsigned long height);\nvoid d2d_putimagedata(struct d2d_draw_seq* ds, long id, unsigned long dx, unsigned long dy);\nvoid d2d_putimagedatadirty(struct d2d_draw_seq* ds, long id, unsigned long dx, unsigned long dy, unsigned long dirtyX, unsigned long dirtyY, unsigned long dirtyWidth, unsigned long dirtyHeight);\n
d2d_measuretext() returns this structure:
struct d2d_text_metrics {\n double actualBoundingBoxAscent;\n double actualBoundingBoxDescent;\n double actualBoundingBoxLeft;\n double actualBoundingBoxRight;\n double fontBoundingBoxAscent;\n double fontBoundingBoxDescent;\n double width;\n};\n
d2d_get_canvas_prop() returns a value of:
export interface ICanvasProps {\n charWidth: number,\n charHeight: number,\n foreColor: number,\n backColor: number,\n widthInChars: number,\n heightInChars: number,\n canvasWidth:number,\n canvasHeight:number\n}\n
"},{"location":"api/api-c-general/","title":"General C API for Wasm","text":""},{"location":"api/api-c-general/#overview","title":"Overview","text":"This sections describes the \"general\" twr-wasm functions available that don't fit neatly into another category (such as standard C library functions, Draw 2D functions, etc.)
These functions often start with \"twr_\" and are generally found in this include file:
\\twr-wasm\\include\\twr-crt.h
Set a block of memory to zeros. Calls memset(to, 0, count)
.
#include <string.h>\n\nvoid bzero (void *to, size_t count);\n
"},{"location":"api/api-c-general/#getc","title":"getc","text":"This is the standard c library function (see the the standard library docs available on the internet).
Of note this function will return extended ASCII (128-255 inclusive). The extend ASCII are always encoded with Windows-1252 encoding.
See twr_getc32
for a list of related functions.
Note that C character input is blocking and you must use twrWasmModuleAsync -- see stdin for details on how to enable blocking character input.
"},{"location":"api/api-c-general/#twr_atod","title":"twr_atod","text":"Similar to stdlib atof
.
#include \"twr-crt.h\"\n\ndouble twr_atod(const char* str);\n
"},{"location":"api/api-c-general/#twr_atou64","title":"twr_atou64","text":"Convert a string to a 64 bit unsigned integer, stopping when the first non-valid character is encountered. If len is provided, it will be set to the number of characters read. Radix should be >=2 and <=36 -- for example, 10 is a normal base 10 number and 16 is hexadecimal.
#include \"twr-crt.h\"\n\nint64_t twr_atou64(const char *str, int* len, int radix);\n
"},{"location":"api/api-c-general/#twr_dtoa","title":"twr_dtoa","text":"The functions to convert double to text are snprintf
, fcvt_s
,twr_dtoa
, twr_toexponential
, and twr_tofixed
#include \"twr-crt.h\"\n\nvoid twr_dtoa(char* buffer, int sizeInBytes, double value, int max_precision);\n
"},{"location":"api/api-c-general/#twr_cache_mallocfree","title":"twr_cache_malloc/free","text":"These functions keep allocated memory in a cache for much faster re-access than the standard malloc/free.
#include \"twr-crt.h\"\n\nvoid *twr_cache_malloc(twr_size_t size);\nvoid twr_cache_free(void* mem);\n
"},{"location":"api/api-c-general/#twr_code_page_to_utf32_streamed","title":"twr_code_page_to_utf32_streamed","text":"Return a unicode code point (aka utf-32 value) when passed a byte stream that represents an encoded character using the current local's LC_CTYPE code page. A zero is returned if the byte stream has not yet completed a decode.
For example:
int cp\n\nsetlocale(LC_ALL, \"\"); // set to default locale, which will be UTF-8 encoding with local language/region\n\n// turn a UTF-8 Euro into a UTF-32 value\ncp==twr_code_page_to_utf32_streamed(0xE2);\nassert (cp==0);\ncp=twr_code_page_to_utf32_streamed(0x82);\nassert (cp==0);\ncp=twr_code_page_to_utf32_streamed(0xAC);\nassert (cp==0x000020AC); // Euro Code points\n
#include <locale.h>\n\nint twr_code_page_to_utf32_streamed(unsigned char byte) \n
"},{"location":"api/api-c-general/#twr_conlog","title":"twr_conlog","text":"twr_conlog
prints debug messages to the browser console from your C code.
#include \"twr-crt.h\"\n\nvoid twr_conlog(char* format, ...);\n
Each call to twr_conlog() will generate a single call to console.log() in JavaScript to ensure that you see debug prints. This call is identical to printf, except that it adds a newline.
The current implementation does not wait for the debug string to output to the console before returning from twr_conlog, when using twrWasmModuleAsync. In this case, it can take a small bit of time for the string to make its way across the Worker Thread boundary. This is normally not a problem and results in faster performance. But if your code crashes soon after the debug print, the print might not appear. If you think this is an issue, you can call twr_sleep(1)
after your twr_conlog call. This will force a blocking wait for the print to print.
Prior to 1.0, this function was called twr_dbg_printf
, and operated slightly differently.
Returns the number of milliseconds since the start of the epoch.
#include \"twr-wasm.h\"\n\nuint64_t twr_epoch_timems();\n
"},{"location":"api/api-c-general/#twr_getc32","title":"twr_getc32","text":"Gets a 32 bit unicode code point character from stdin. Unlike the standard C library function getchar
, twr_getc32
does not buffer a line (that is, twr_getc32
will return a character before the user presses Enter).
twr_getc32
is implemented as:
int twr_getc32() {\n return io_getc32(twr_get_stdio_con());\n}\n
Note that stdlib getchar
and ungetc
are not currently implemented.
Note that C character input is blocking and you must use twrWasmModuleAsync -- see stdin for details on how to enable blocking character input.
Also see:
io_mbgets
- get a multibyte string from a console using the current locale character encodingtwr_mbgets
- similar to io_mbgets
, except always gets a multibyte locale format string from stdin.io_mbgetc
- get a multibyte character from an IoConsole (like stdin
) using the current locale character encodinggetc
(sames as fgetc
) - get a single byte from a FILE * (IoConsole) -- returning ASCII or extended ASCII (window-1252 encoding)io_getc32
- gets a 32 bit unicode code point from an IoConsole (which currently needs to be stdin)#include \"twr-crt.h\"\n\nint twr_getc32();\n
"},{"location":"api/api-c-general/#twr_get_navlang","title":"twr_get_navlang","text":"Returns the BCP 47 language tag as found in javacript navigator.language
. If len is not null, it will be filled in with the string length of the language tag.
#include \"twr-crt.h\"\n\nconst char* twr_get_navlang(int *len);\n
"},{"location":"api/api-c-general/#twr_get_current_locale","title":"twr_get_current_locale","text":"extern inline locale_t twr_get_current_locale(void);\n
twr_get_current_locale
will return the locale that has been set by setlocale
. It can be used to pass to a function that takes a locale_t.
Functions like twr_dtoa
do not localize the decimal point. To get a localized decimal point, you can use printf
, or alternately twr_localize_numeric_string
to post process a string. For example:
char b[10];\nstrcpy(b, \"1.23\");\ntwr_localize_numeric_string(b, twr_get_current_locale());\n// if locale was set to french, then b is now 1,23\n
#include <locale.h>\n\nvoid twr_localize_numeric_string(char* str, locale_t locale);\n
"},{"location":"api/api-c-general/#twr_mem_debug_stats","title":"twr_mem_debug_stats","text":"Print memory map and malloc stats to stderr or stdout.
(note FILE * is the same as struct IoConsole*)
#include <stdio.h>\n\nvoid twr_mem_debug_stats(struct IoConsole* outcon);\n
"},{"location":"api/api-c-general/#twr_mbgets","title":"twr_mbgets","text":"Gets a string from stdin. The string will be in the current locale's character encoding -- ASCII for \"C\", and either UTF-8 or windows-1252 for \"\". See localization.
#include \"twr-crt.h\"\n\nchar* twr_mbgets(char* buffer);\n
Internally this function uses the stdio IoConsole -- see the IoConsole section for more advanced input/output.
This function will encode characters as specified by the LC_CTYPE category of the current locale. ASCII is used for \"C\", and UTF-8 and Windows-1252 are also supported (see localization)
Note that C character input is blocking and you must use twrWasmModuleAsync -- see stdin for details on how to enable blocking character input.
"},{"location":"api/api-c-general/#twr_mbslen_l","title":"twr_mbslen_l","text":"Returns the number of characters in a string using the character encoding of the passed locale (ASCII for \"C\", UTF-8, or windows-1252 for \"\"). You can use twr_get_current_locale
to find the current locale.
#include <string.h>\n\nsize_t twr_mbslen_l(const char *str, locale_t locale);\n
"},{"location":"api/api-c-general/#twr_sleep","title":"twr_sleep","text":"twr_sleep
is a traditional blocking sleep function. This function is blocking, and so is only available if you use twrWasmModuleAsync
.
#include \"twr-wasm.h\"\n\nvoid twr_sleep(int ms);\n
"},{"location":"api/api-c-general/#twr_tofixed","title":"twr_tofixed","text":"This function is identical to its JavaScript version.
#include \"twr-wasm.h\"\n\nvoid twr_tofixed(char* buffer, int buffer_size, double value, int dec_digits);\n
The functions to convert double to text are snprintf
, fcvt_s
,twr_dtoa
, twr_toexponential
, and twr_tofixed
This function is identical to its JavaScript version.
#include \"twr-wasm.h\"\n\nvoid twr_toexponential(char* buffer, int buffer_size, double value, int dec_digits);\n
The functions to convert double to text are snprintf
, fcvt_s
,twr_dtoa
, twr_toexponential
, and twr_tofixed
Mirror image the passed in string.
#include \"twr-crt.h\"\n\nvoid twr_strhorizflip(char * buffer, int n);\n
"},{"location":"api/api-c-general/#twr_utf8_char_len","title":"twr_utf8_char_len","text":"Returns the number of bytes in a UTF-8 character (passed as a string pointer). UTF-8 characters can be 1 to 4 bytes in length.
#include <string.h>\n\nint twr_utf8_char_len(const char *str);\n
"},{"location":"api/api-c-general/#twr_utf32_to_code_page","title":"twr_utf32_to_code_page","text":"Takes a utf32 value (aka unicode code point value), and fills in the passed character array buffer with the character encoding of the utf32 value, using the current locale's LC_CTYPE code page. The buffer is 0 terminated.
Also see c32rtomb
and c16rtomb
.
For example:
char strbuf[6]; // max size of utf-8 is 4+terminating zero. Max size of ASCII or windows 1252 is 1 + terminating zero\nsetlocale(LC_ALL, \"\"); // set to default locale, which will be UTF-8 encoding with local language/region\ntwr_utf32_to_code_page(strbuf, 0x000020AC); // encode a Euro code point \nprintf(\"%s\", strbuf); \nassert ( strcmp(strbuf,\"\\xE2\\x82\\xAC\")==0 ); // utf-8 encoding of euro\nassert ( strcmp(strbuf,\"\u20ac\")==0 ); // clang string literals default to utf-8 encoding\n
include <locale.h>\n\nvoid twr_utf32_to_code_page(char* out, int utf32)\n
"},{"location":"api/api-c-general/#twr_vprintf","title":"twr_vprintf","text":"Performs a printf by calling the callback with cbdata for each character.
#include \"twr-crt.h\"\n\nvoid twr_vprintf(twr_cbprintf_callback out, void* cbdata, const char *format, va_list* args);\n
"},{"location":"api/api-c-general/#floating-math-helpers","title":"floating math helpers","text":"int twr_isnan(double v);\nint twr_isinf(double v);\ndouble twr_nanval();\ndouble twr_infval();\n
"},{"location":"api/api-c-stdlib/","title":"Standard C library for WebAssembly","text":"This section describes twr-wasm's support for the Standard C Library. twr-wasm includes its own implementation of the standard C library optimized for WebAssembly and Wasm running in a web browser. This is a core feature of twr-wasm.
For documentation of these functions, see the many standard C library documentation web sites.
The following subset of the standard C library is available. Also see twr-wasm/include
folder for include files.
* fprintf will only work with these -- stderr, stdin, stdout */\n/* these return 'struct IoConsole *' which is same as 'FILE *' */\n#define stderr (FILE *)(twr_get_stderr_con())\n#define stdin (FILE *)(twr_get_stdio_con())\n#define stdout (FILE *)(twr_get_stdio_con())\n\nint snprintf(char *buffer, size_t bufsz, const char *format, ... );\nint sprintf( char *buffer, const char *format, ... );\nint vsnprintf(char *buffer, size_t bufsz, const char *format, va_list vlist);\nint vasprintf(char **strp, const char* format, va_list vlist );\nint printf(const char* format, ...);\nint vprintf(const char* format, va_list vlist );\nint puts(const char *str);\nint putchar(int c);\n\ntypedef struct IoConsole FILE; \nint vfprintf(FILE *stream, const char *format, va_list vlist);\nint fprintf(FILE *stream, const char* format, ...);\nsize_t fwrite(const void* buffer, size_t size, size_t count, FILE* stream);\nint ferror(FILE *stream);\nint feof(FILE *stream);\nint fflush(FILE *stream);\nint is_terminal(FILE *stream);\nint fputc(int ch, FILE* stream);\nint putc(int ch, FILE* stream);\nint fgetc(FILE *stream );\nint getc(FILE *stream);\n
"},{"location":"api/api-c-stdlib/#stdlibh","title":"stdlib.h","text":"void *malloc(size_t size);\nvoid free(void *mem);\nsize_t avail(void);\nvoid *realloc( void *ptr, size_t new_size );\nvoid* calloc( size_t num, size_t size );\nvoid *aligned_alloc( size_t alignment, size_t size );\n\nint rand(void);\nvoid srand(int seed);\n\n#define __min(a,b) (((a) < (b)) ? (a) : (b))\n#define __max(a,b) (((a) > (b)) ? (a) : (b))\n\nint _fcvt_s(\n char* buffer,\n size_t sizeInBytes,\n double value,\n int fracpart_numdigits,\n int *dec,\n int *sign\n);\ndouble atof(const char* str);\nint atoi(const char *str);\nlong atol( const char *str );\nlong long atoll( const char *str );\nlong strtol(const char *str, char **str_end, int base);\nlong long strtoll(const char *str, char **str_end, int base);\nlong long strtoll_l(const char *str, char **str_end, int base, locale_t loc);\nunsigned long long strtoull(const char *str, char **str_end, int base);\nunsigned long long strtoull_l(const char *str, char **str_end, int base, locale_t loc);\nunsigned long strtoul(const char *str, char ** str_end, int base);\nfloat strtof(const char *str, char ** str_end);\nfloat strtof_l(const char *str, char ** str_end, locale_t locale);\ndouble strtod(const char *str, char **str_end);\ndouble strtod_l(const char *str, char **str_end, locale_t locale);\nlong double strtold(const char *str, char **str_end);\nlong double strtold_l(const char *str, char **str_end, locale_t locale);\nint _itoa_s(int64_t value, char * buffer, size_t size, int radix);\n\ndiv_t div( int x, int y );\nldiv_t ldiv( long x, long y );\nlldiv_t lldiv( long long x, long long y );\n\n_Noreturn void abort(void);\nint atexit(void (*func)(void));\n
Note that _fcvt_s as currently enabled has these limitations: - fractional digits <=100 - values must be less than 1e+21 - values negative exponents must be smaller than 1e-99
There is a full featured version of _fcvt_s in the source code, but is not currently enabled, since the version enabled is smaller and works in most use cases.
"},{"location":"api/api-c-stdlib/#asserth","title":"assert.h","text":"void assert(int expression);\n
"},{"location":"api/api-c-stdlib/#mathh","title":"math.h","text":"int abs(int n);\ndouble acos(double arg);\ndouble asin(double arg);\ndouble atan(double arg);\ndouble ceil(double arg);\ndouble cos(double arg);\ndouble exp(double arg);\ndouble fabs(double arg);\ndouble floor(double arg);\ndouble fmod(double x, double y);\ndouble log(double arg);\ndouble pow(double base, double exp);\ndouble sin(double arg);\ndouble sqrt(double arg);\ndouble tan(double arg);\ndouble trunc(double arg);\n
"},{"location":"api/api-c-stdlib/#stdargh","title":"stdarg.h","text":"#define va_start(v,l) __builtin_va_start(v,l)\n#define va_end(v) __builtin_va_end(v)\n#define va_arg(v,l) __builtin_va_arg(v,l)\n#define va_copy(d,s) __builtin_va_copy(d,s)\ntypedef __builtin_va_list va_list;\n
"},{"location":"api/api-c-stdlib/#ctypeh","title":"ctype.h","text":"int isascii(int);\nint toascii(int);\nint isalnum(int c);\nint isalpha(int c);\nint isblank(int);\nint iscntrl(int);\nint isdigit(int c);\nint isgraph(int c);\nint islower(int);\nint isprint(int);\nint ispunct(int);\nint isspace(int c);\nint isupper(int);\nint isxdigit(int);\nint tolower(int c);\nint toupper(int c);\n\nint isalnum_l(int c, locale_t loc);\nint isalpha_l(int c, locale_t loc);\nint isblank_l(int c, locale_t loc);\nint iscntrl_l(int c, locale_t loc);\nint isdigit_l(int c, locale_t loc);\nint isgraph_l(int c, locale_t loc);\nint islower_l(int c, locale_t loc);\nint isprint_l(int c, locale_t loc);\nint ispunct_l(int c, locale_t loc);\nint isspace_l(int c, locale_t loc);\nint isupper_l(int c, locale_t loc);\nint isxdigit_l(int c, locale_t loc);\nint tolower_l(int c, locale_t loc);\nint toupper_l(int c, locale_t loc);\n
"},{"location":"api/api-c-stdlib/#stddefh","title":"stddef.h","text":"#define offsetof(TYPE, MEMBER) __builtin_offsetof (TYPE, MEMBER)\ntypedef __PTRDIFF_TYPE__ ptrdiff_t;\ntypedef double max_align_t;\n
"},{"location":"api/api-c-stdlib/#stringh","title":"string.h","text":"size_t strlen(const char * str);\nchar *strdup(const char * source);\nchar *strcpy(char *dest, const char *source);\nint strcat_s(char *dest, size_t destsz, const char *src);\nchar* strcat(char *dest, const char *src);\nchar *strncpy(char *dest, const char *source, size_t count);\nint strcmp(const char* string1, const char* string2);\nint strncmp(const char* lhs, const char* rhs, size_t count);\nint stricmp(const char* string1, const char* string2);\nint strnicmp(const char* string1, const char* string2, size_t count);\nint strcoll(const char* lhs, const char* rhs);\nint strcoll_l(const char* lhs, const char* rhs, locale_t loc);\nchar *strchr(const char *str, int ch);\nvoid *memchr(const void *ptr, int ch, size_t count);\nchar *strstr(const char *haystack, const char *needle);\nchar * strerror(int errnum );\nchar * _strerror(const char *strErrMsg);\nvoid *memmove(void *dest, const void *src, size_t n);\nint memcmp( const void* lhs, const void* rhs, size_t count );\nvoid bzero (void *to, size_t count);\n\n// implemented in memcpy.wat\nvoid *memcpy(void *dest, const void * src, size_t n);\nvoid *memset(void *mem, int c, size_t n);\n
"},{"location":"api/api-c-stdlib/#timeh","title":"time.h","text":"typedef unsigned long time_t;\nunsigned long time(unsigned long *time);\nsize_t strftime(char *s, size_t maxsize, const char *format, const struct tm *timeptr);\nsize_t strftime_l(char *s, size_t maxsize, const char *format, const struct tm *timeptr, locale_t locale);\nstruct tm *localtime(const time_t *timer);\nint gettimeofday(struct timeval *tv, void* notused);\n#define timerisset(tvp) ((tvp)->tv_sec || (tvp)->tv_usec)\n#define timercmp(tvp,uvp,cmp) \\\n ((tvp)->tv_sec cmp (uvp)->tv_sec || \\\n ((tvp)->tv_sec == (uvp)->tv_sec && (tvp)->tv_usec cmp (uvp)->tv_usec))\n#define timerclear(tvp) (tvp)->tv_sec = (tvp)->tv_usec = 0\n
"},{"location":"api/api-c-stdlib/#localeh","title":"locale.h","text":"#define LC_GLOBAL_LOCALE twr_get_current_locale()\nchar* setlocale(int category, const char* locale);\nstruct lconv *localeconv(void);\nlocale_t newlocale(int category_mask, const char *locale, locale_t base);\nlocale_t uselocale(locale_t);\nvoid freelocale(locale_t);\nlocale_t duplocale(locale_t);\nextern inline locale_t twr_get_current_locale(void);\n
"},{"location":"api/api-c-stdlib/#ucharh","title":"uchar.h","text":"typedef uint_least32_t char32_t;\ntypedef uint_least16_t char16_t;\n\nsize_t c32rtomb( char* s, char32_t c32, mbstate_t* ps );\n
"},{"location":"api/api-c-stdlib/#errnoh","title":"errno.h","text":"typedef int errno_t;\n\nextern int * _errno(void);\n#define errno (*_errno())\n\nerrno_t _set_errno(int _Value);\nerrno_t _get_errno(int *_Value);\n
"},{"location":"api/api-c-stdlib/#_stdtypesh","title":"_stdtypes.h","text":"// don't include directly -- included by various .h files
typedef unsigned long size_t;\n#define MAX_SIZE_T 2147483647 \n\n#ifdef __cplusplus\n#define NULL __null\n#else\n#define NULL ((void*)0)\n#endif\n\ntypedef struct __locale_t_struct * locale_t;\n
"},{"location":"api/api-c-stdlib/#other-include-files-available","title":"Other include files available","text":"float.h\nlimits.h\nstdbool.h\nstdint.h\n
"},{"location":"api/api-libc%2B%2B/","title":"libc++ for WebAssembly","text":"This section describes twr-wasm's support for using the standard c++ library libc++ with WebAssembly.
twr-wasm includes libc++ built for WebAssembly in the twr-wasm/lib-c
folder.
For C++ the use of libc++ is optional. That is you can build twr-wasm projects in C++ with or without libc++.
See the examples tests-libcx and tests-user for examples of using libc++.
See the balls example for how to create a C++ WebAssembly program without the standard C++ library. The primary advantage to this approach is a bit smaller code size. You don't need to staticly link libc++.
Some of the key options twr-wasm's libc++ for WebAssembly was built with are these:
DLIBCXX_ENABLE_LOCALIZATION=ON \nDLIBCXX_ENABLE_UNICODE=ON \nDLIBCXX_ENABLE_RTTI=ON \nDLIBCXX_ENABLE_STATIC_ABI_LIBRARY=ON \n\nDCMAKE_BUILD_TYPE=Release \nDCMAKE_CXX_STANDARD=20 \n\nDLIBCXX_ENABLE_EXCEPTIONS=OFF \nDLIBCXX_ENABLE_THREADS=OFF \nDLIBCXX_ENABLE_SHARED=OFF \nDLIBCXX_ENABLE_WIDE_CHARACTERS=OFF \nDLIBCXX_ENABLE_FILESYSTEM=OFF \nDLIBCXX_ENABLE_TIME_ZONE_DATABASE=OFF \nDLIBCXX_ENABLE_MONOTONIC_CLOCK=OFF \nDLIBCXX_ENABLE_RANDOM_DEVICE=OFF\n
"},{"location":"api/api-localization/","title":"Localization Reference for twr-wasm","text":"This section details twr-wasm's WebAssembly localization support.
Also see Introduction to Character Encoding Support with twr-wasm
"},{"location":"api/api-localization/#using-c","title":"Using C:","text":"Standard C locale functions are supported by twr-wasm. ASCII, UTF-8 and windows-1252 encoding is supported by the twr-wasm standard C library locale. twr-wasm also includes C functions for UTF-32 support.
"},{"location":"api/api-localization/#using-c_1","title":"Using C++:","text":"twr-wasm C locales support ASCII, UTF-8 or windows-1252 encoding. UTF-16/32 are not supported as a std c lib locale setting, but functions are provided to convert utf-32 (unicode code points) to and from ASCII, UTF-8, and windows-1252 \"code pages\" (there are other miscellaneous utf-32 based functions as well.)
"},{"location":"api/api-localization/#locales-standard-c-library","title":"Locales (Standard C Library)","text":""},{"location":"api/api-localization/#c","title":"\"C\"","text":"\"C\" is the default locale, as usual. When \"C\" is selected, the functions operate as usual. One subtly is that console i/o functions (such as printf
) will generally function as expected with UTF-8, since the div
and window
consoles correctly handle UTF-8 character encoding. This is normal on some OSs, such as linux, but not the default on Windows (which often defaults to windows-1252 for backward compatibility).
isgraph
style functions will only recognize ASCII characters, as is normal. Functions such as strcmp
operate on the byte sequence, which will typically results in UTF-8 codes being compared lexically. strcoll
will use lexical ordering.
\"POSIX\" is the same as \"C\"
"},{"location":"api/api-localization/#_1","title":"\"\"","text":"\"\" is the locale to specify the users default setting (this selects the setting used by the browser). This will also enable UTF-8 in functions such as strcoll
. For example, if your browser is set to \"en-US\" as its default locale, setlocale(LC_ALL, \"\")
will return en-US.UTF-8
.
isgraph
style functions will still only recognize ASCII characters (since UTF-8 doesn't encode any single bytes greater than 127). strcoll
uses locale specific ordering, and printf
will use locale specific decimal points. strcmp
still compares two strings lexicographically (byte-by-byte) without considering locale-specific rules, per the spec.
\".UTF-8\" is the same as \"\" with twr-wasm.
"},{"location":"api/api-localization/#1252","title":"\".1252\"","text":"\".1252\" will select the current default locale, but use windows-1252 character encoding (instead of UTF-8). Windows-1252 is a super set of ISO-8859-1 and is the most commonly used encoding for many european languages when unicode is not used. This mode is primarily for legacy software, backwards compatibly, and windows compatibility.
"},{"location":"api/api-localization/#others","title":"Others","text":"Setting arbitrary locales, such as \"fr-FR\" when the browser is defaulted to another locale, is not supported.
"},{"location":"api/api-localization/#select-the-default-locale","title":"Select the default locale","text":"To select the user's browser's default locale using the C language, and enable consistent utf-8 support, use a call like this:
setlocale(LC_ALL, \"\")\n
"},{"location":"api/api-localization/#c-and-libc-functions","title":"C and libc++ functions","text":"If you are using twr-wasm's build of libc++, libc++ locale and unicode functions work as normal.
The usual standard C library locale support is available, along with some POSIX extensions. In addition, some locale useful twr-wasm specific functions are documented in C API, such as twr_get_current_locale
,twr_mbgets
, twr_getc32
, twr_utf8_char_len
, twr_mbslen_l
, twr_utf32_to_code_page
, twr_code_page_to_utf32_streamed
, twr_get_navlang
, twr_localize_numeric_string
.
Note that io_getc32
, getc(stdin)
, fgetc(stdin)
do not look at the current locale. io_getc32
returns a 32 bit unicode code point, and getc
/fgetc
return extended ASCII.
For a locale aware character input, use io_mbgetc()
or twr_mbgets()
. Both use the locale category LC_CTYPE. See C API.
Note that when the locale is not set (or whenever the \"C\" locale is set) functions that get character(s) from stdin that are locale aware, like twr_mbgets()
, behave different than functions that output characters to stdout (like puts
, io_putstr
, io_putc
, putchar
). Characters to stdout in \"C\" locale will handle UTF-8 characters. For stdin, \"C\" locale uses ASCII.
For consistent UTF-8 (or windows-1252) behavior, set the locale as discussed above ( use setlocale
)
The primary standard C library locale functions are:
char* setlocale(int category, const char* locale);\nstruct lconv *localeconv(void);\n
As well as the two standard library functions above, appropriate functions take into account the current locale (printf, strcoll, etc).
Note that setlocale
returns a string using BCP 47 format (like a web browser). Locale strings look like \"en-US.UTF-8\", instead of \"en_US.UTF-8\". A dash, not an underscore, is used as a separator.
POSIX functions These are the extended POSIX style functions provided that are related to locale:
locale_t newlocale(int category_mask, const char *locale, locale_t base);\nlocale_t uselocale(locale_t);\nvoid freelocale(locale_t);\nlocale_t duplocale(locale_t);\n\nint isalnum_l(int c, locale_t loc);\nint isalpha_l(int c, locale_t loc);\nint isblank_l(int c, locale_t loc);\nint iscntrl_l(int c, locale_t loc);\nint isdigit_l(int c, locale_t loc);\nint isgraph_l(int c, locale_t loc);\nint islower_l(int c, locale_t loc);\nint isprint_l(int c, locale_t loc);\nint ispunct_l(int c, locale_t loc);\nint isspace_l(int c, locale_t loc);\nint isupper_l(int c, locale_t loc);\nint isxdigit_l(int c, locale_t loc);\nint tolower_l(int c, locale_t loc);\nint toupper_l(int c, locale_t loc);\n\nlong long strtoll_l(const char *str, char **str_end, int base, locale_t loc);\nunsigned long long strtoull_l(const char *str, char **str_end, int base, locale_t loc);\nfloat strtof_l(const char *str, char ** str_end, locale_t locale);\ndouble strtod_l(const char *str, char **str_end, locale_t locale);\nlong double strtold_l(const char *str, char **str_end, locale_t locale);\n\nint strcoll_l(const char* lhs, const char* rhs, locale_t loc);\n\nsize_t strftime_l(char *s, size_t maxsize, const char *format, const struct tm *timeptr, locale_t locale);\n
"},{"location":"api/api-typescript/","title":"TypeScript-JavaScript API to load and call Wasm","text":"This section describes the twr-wasm TypeScript/JavaScript classes that you use to load your Wasm modules, and to call C functions in your Wasm modules.
class twrWasmModule
and class twrWasmModuleAsync
have similar APIs. The primary difference is that class twrWasmModuleAsync
proxies functionality through a Web Worker thread, which allows blocking C functions to be called in your WebAssembly Module. The Async
part of twrWasmModuleAsync
refers to the ability to await
on a blocking callC
in your JavaScript main thread, when using twrWasmModuleAsync
.
import {twrWasmModule} from \"twr-wasm\";\n\nconst mod = new twrWasmModule();\n
twrWasmModule
provides the two core JavaScript APIs for access to a WebAssembly Module: loadWasm
to load your .wasm
module (your compiled C code).callC
to call a C functionUse loadWasm
to load your compiled C/C++ code (the .wasm
file).
await mod.loadWasm(\"./mycode.wasm\")\n
"},{"location":"api/api-typescript/#callc","title":"callC","text":"After your .wasm
module is loaded with loadWasm
, you call functions in your C/C++ from TypeScript/JavaScript like this:
let result=await mod.callC([\"function_name\", param1, param2])\n
If you are calling into C++, you need to use extern \"C\" like this in your C++ function:
extern \"C\" int function_name() {}\n
Each C/C++ function that you wish to call from TypeScript/JavaScript needs to be exported in your wasm-ld
command line with an option like this:
--export=function_name\n
Or like this in your source file: __attribute__((export_name(\"function_name\")))\nvoid function_name() {\n ...\n}\n
Fo more details, see the Compiler Options.
callC
takes an array where:
and the next optional entries are a variable number of arguments to pass to the C function, of type:
number
- will be converted to a signed or unsigned long
, int32_t
, int
, float
or double
as needed to match the C function declaration.bigint
- will be converted into an int64_t
or equivalentstring
- converted to a char *
of malloc'd module memory where string is copied intoArrayBuffer
- the array is copied into malloc'd module memory. If you need to pass the length, pass it as a separate argument. Any modifications to the memory made by your C code will be reflected back into the JavaScript ArrayBuffer.URL
- the url contents are copied into malloc'd module Memory, and two C arguments are generated - index (pointer) to the memory, and lengthcallC
returns the value returned by the C function. long
, int32_t
, int
, float
or double
and the like are returned as a number
, int64_t
is returned as a bigint
, and pointers are returned as a number
. The contents of the pointer will need to be extracted using the functions listed below in the section \"Accessing Data in the WebAssembly Memory\". The callC example also illustrates this.
More details can be found in this article: Passing Function Arguments to WebAssembly and in this example. The FFT example demonstrates passing and modifying a Float32Array
view of an ArrayBuffer
.
import {twrWasmModuleAsync} from \"twr-wasm\";\n\nconst amod = new twrWasmModuleAsync();\n
twrWasmModuleAsync
implements all of the same functions as twrWasmModule
, plus allows blocking inputs, and blocking code generally. This is achieved by proxying all the calls through a Web Worker thread.
For example, with this C function in your Wasm module:
void mysleep() {\n twr_sleep(5000); // sleep 5 seconds\n}\n
can be called from your JavaScript main loop like this:
await amod.callC([\"mysleep\"]);\n
This is useful for inputting from stdin
, or for traditional blocking loops. The example stdio-div - Printf and Input Using a div Tag demos this.
You must use twrWasmModuleAsync
in order to:
twr_mbgets
)twr_sleep
See stdio section for information on enabling blocking character input, as well as this Example.
When linking your C/C++ code, twrWasmModule
and twrWasmModuleAsync
use slightly different wasm-ld
options since twrWasmModuleAsync
uses shared memory. twrWasmModule
will operate with shared memory, so technically you could just use the same share memory options with either module, but you don't need the overhead of shared memory when using twrWasmModule, and so better to not enable it.
See Compiler Options.
twrWasmModuleAsync
uses SharedArrayBuffers which require certain HTTP headers to be set. Note that twrWasmModule
has an advantage in that it does not use SharedArrayBuffers.
Github pages doesn't support the needed CORS headers for SharedArrayBuffers. But other web serving sites do have options to enable the needed CORS headers. For example, the azure static web site config file staticwebapp.config.json
looks like this:
{\n \"globalHeaders\": {\n \"Access-Control-Allow-Origin\": \"*\",\n \"Cross-Origin-Embedder-Policy\": \"require-corp\",\n \"Cross-Origin-Opener-Policy\": \"same-origin\"\n }\n}\n
server.py in the examples folder will launch a local server with the correct headers. To use Chrome without a web server, see the Hello World walk through.
"},{"location":"api/api-typescript/#class-options","title":"Class Options","text":"The twrWasmModule
and twrWasmModuleAsync
constructor both take optional options.
For example:
let amod=new twrWasmModuleAsync();\n\nlet amod=new twrWasmModuleAsync({\n windim:[50,20], \n forecolor:\"beige\", \n backcolor:\"DarkOliveGreen\", \n fontsize:18\n });\n
For a <div id=\"twr_iodiv\">
it is simpler to set the color and font in the div tag per the normal HTML method. But for <div id=\"twr_iocanvas\">
, that method won't work and you need to use the constructor options for color and fontsize.
These are the options:
export type TStdioVals=\"div\"|\"canvas\"|\"null\"|\"debug\";\n\nexport interface IModOpts {\n stdio?:TStdioVals, \n windim?:[number, number],\n forecolor?:string,\n backcolor?:string,\n fontsize?:number,\n imports?:{},\n}\n
"},{"location":"api/api-typescript/#stdio","title":"stdio","text":"You can explicitly set your stdio source (for C/C++ printf, etc) with the stdio option, but typically you don't set it. Instead, it will auto set as described here
"},{"location":"api/api-typescript/#windim","title":"windim","text":"This options is used with a terminal console ( <canvas id=\"twr_iocanvas\">
) to set the width and height, in characters.
The canvas width and height, in pixels, will be set based on your fontsize and the width and height (in characters) of the terminal.
"},{"location":"api/api-typescript/#forecolor-and-backcolor","title":"forecolor and backcolor","text":"These can be set to a CSS color (like '#FFFFFF' or 'white') to change the default background and foreground colors.
"},{"location":"api/api-typescript/#fonsize","title":"fonsize","text":"Changes the default fontsize for div or canvas based I/O. The size is in pixels.
"},{"location":"api/api-typescript/#divlog","title":"divLog","text":"If stdio
is set to twr_iodiv
, you can use the divLog
twrWasmModule/Async function like this:
import {twrWasmModule} from \"twr-wasm\";\n\nconst mod = new twrWasmModule();\nawait mod.loadWasm(\"./tests.wasm\");\nawait mod.callC([\"tests\"]);\n\nmod.divLog(\"\\nsin() speed test\");\nlet sumA=0;\nconst start=Date.now();\n\nfor (let i=0; i<2000000;i++)\n sumA=sumA+Math.sin(i);\n\nconst endA=Date.now();\n\nlet sumB=await mod.callC([\"sin_test\"]);\nconst endB=Date.now();\n\nmod.divLog(\"sum A: \", sumA, \" in ms: \", endA-start);\nmod.divLog(\"sum B: \", sumB, \" in ms: \", endB-endA);\n
"},{"location":"api/api-typescript/#accessing-data-in-the-webassembly-memory","title":"Accessing Data in the WebAssembly Memory","text":"callC()
will convert your JavaScript arguments into a form suitable for use by your C code. However, if you return or want to access struct values inside TypeScript you will find the following twrWasmModule
and twrWasmModuleAsync
functions handy. See the callc example and Passing Function Arguments from JavaScript to C/C++ with WebAssembly for an explanation of how these functions work.
async putString(sin:string, codePage=codePageUTF8) // returns index into WebAssembly.Memory\nasync putU8(u8a:Uint8Array) // returns index into WebAssembly.Memory\nasync putArrayBuffer(ab:ArrayBuffer) // returns index into WebAssembly.Memory\nasync fetchAndPutURL(fnin:URL) // returns index into WebAssembly.Memory\nasync malloc(size:number) // returns index in WebAssembly.Memory. \n\nstringToU8(sin:string, codePage=codePageUTF8)\ncopyString(buffer:number, buffer_size:number, sin:string, codePage=codePageUTF8):void\ngetLong(idx:number): number\nsetLong(idx:number, value:number)\ngetDouble(idx:number): number\nsetDouble(idx:number, value:number)\ngetShort(idx:number): number\ngetString(strIndex:number, len?:number, codePage=codePageUTF8): string\ngetU8Arr(idx:number): Uint8Array\ngetU32Arr(idx:number): Uint32Array\n\nmemory?:WebAssembly.Memory;\nmem8:Uint8Array;\nmem32:Uint32Array;\nmemD:Float64Array;\n
"},{"location":"examples/examples-balls/","title":"Bouncing Balls - 2D Draw API Wasm Example","text":"This example uses twr-wasm's 2D Draw API and a C++ Canvas class with WebAssembly and C++ to bounce balls around your HTML page.
The bouncing balls example demonstrates
This example does not use libc++, which results in smaller code size. For an example that uses libc++ see tests-libcxx.
"},{"location":"examples/examples-balls/#screen-grab-of-balls-example","title":"Screen Grab of Balls Example","text":""},{"location":"examples/examples-callc/","title":"callC - Calling WebAssembly Functions Example","text":"This example demonstrates how to pass and return values between TypeScript/JavaScript and C/C++ when you are using WebAssembly with twr-wasm.
This article explains the key concepts to pass arguments between JavaScript/TypeScript and Wasm C/C++.
This example is a demo of integrating the popular KISS FFT C library with TypeScript/JavaScript/HTML using WebAssembly. The FFT C library is compiled into a Wasm (WebAssembly) module using clang, with the help of twr-wasm. The FFT Wasm module is used by the HTML page to calculate the FFT. The FFT input and output is drawn to the web page using JavaScript canvas functions.
The FFT library exposes APIs to process data, and doesn't use stdio.
The FFT APIs use float32 arrays for complex-number input and output data, and a configuration C struct. In the example I generate the input data by adding a 1K and 5K sine waves, call the kiss FFT API to perform the FFT on the generated sine waves, and then graph the input and output data using a JavaScript Canvas.
Here is part of the code. The rest can be found on github.
index.html
<head>\n <title>Fast Fourier transform (FFT)</title>\n</head>\n<body style=\"background-color:white\">\n\n <br>\n\n <div style=\"font:24px arial\">Input Signal</div>\n <canvas id=\"c-input\" width=\"1024\" height=\"300\" style=\"background-color:lightgray\"></canvas>\n\n <br><br><br>\n\n <div style=\"font:24px arial\">FFT Output</div>\n <canvas id=\"c-output\" width=\"1024\" height=\"300\" style=\"background-color:lightgray\"></canvas>\n\n <script type=\"module\">\n import {fftDemo} from \"./fft-script.js\";\n\n fftDemo();\n\n </script>\n</body>\n
fft-script.jsimport {twrWasmModule} from \"twr-wasm\";\n\nexport async function fftDemo() {\n\n const mod=new twrWasmModule();\n\n // load the kiss_fft C code as is, unmodified\n await mod.loadWasm('kiss_fft.wasm');\n\n // kissFFTData stores and graphs the input and output data\n // in this example the fft has 1024 bins, and I am using a 48K sampling rate\n let fft=new kissFFTData(1024, 48000);\n fft.genSin(1000)\n fft.addSin(5000)\n fft.graphIn(\"c-input\");\n\n // see kiss_fft README, but in summary you: (a) alloc config, (b) compute the FFT, (c) free the config\n // kiss_fft_alloc() returns a malloced structure. Pointers are numbers (index into Wasm module memory) in JS land \n //\n //kiss_fft_cfg cfg = kiss_fft_alloc( nfft ,is_inverse_fft ,0,0 );\n let cfg:number = await mod.callC([\"kiss_fft_alloc\", fft.nfft, 0, 0, 0 ]);\n\n // The FFT input and output data are C arrays of complex numbers.\n // typedef struct {\n // kiss_fft_scalar r;\n // kiss_fft_scalar i;\n // } kiss_fft_cpx;\n //\n // /* default is float */\n // define kiss_fft_scalar float\n\n // So if the FFT data has 1024 bins, then 1024 * 2 floats (r & i) * 4 bytes per float are needed.\n // I use a JS Float32Array view on the ArrayBuffer to access the floats\n\n // When an arrayBuffer is passed in as an argument to mod.callC,\n // callC will malloc memory in the Wasm module of a size that matches the array buffer, then\n // copy the arraybuffer into the malloc'd memory prior to the function call, \n // then copy the malloc'd memory contents back into the arrayBuffer post call.\n // The malloc'd memory is free'd post call. \n\n // void kiss_fft(kiss_fft_cfg cfg,const kiss_fft_cpx *fin,kiss_fft_cpx *fout);\n await mod.callC([\"kiss_fft\", cfg, fft.inArrayBuf, fft.outArrayBuf]);\n\n fft.graphOut(\"c-output\");\n\n await mod.callC([\"free\", cfg]); // not much point to this since all the module memory is about to disappear\n}\n
"},{"location":"examples/examples-helloworld/","title":"Hello World - WebAssembly C Example","text":"This example is an very simple twr-wasm program. It uses WebAssembly and C to print \"hello, world!\" to an HTML <div>
tag.
Also see: Hello World - Step-by-Step C to Wasm.
This is a simple test of various libc++ functions using WebAssembly with twr-wasm. The C++ program links with libc++. An example makefile is provided.
Also see this WebAssembly program that uses libc++ with twr-wasm to implement a CLI console.
This example is a port to Wasm of a 20 year old Win32 C Maze creator, with the help of twr-wasm 2D Draw APIs.
This Maze generator uses the twr-wasm \"d2d\" (Draw 2D) C APIs. These allow drawing onto an HTML canvas from C/C++. (Also see the balls C++ example).
This C code is interesting in that it is a combination of blocking and non blocking functions. The CalcMaze
function is blocking when the \"slow draw\" flag is set. It uses Sleep
in this case. For this reason, I use twrWasmModuleAsync. The solve section uses repeated calls to SolveStep
, which works well with a JavaScript main loop. I used a javascript interval timer to make repeated calls to the C SolveStep
function. If all the C code was structured this way, twrWasmModule
could have been used (instead of the Async version)
To port this code to twr-wasm I wrote a (very tiny) Win32 compatible API (in winemu.c/winemu.h). It only implements the features needed to port Maze, but it might be useful to use as a starting point for porting your Win32 code to the web.
index.html<head>\n <title>Maze</title>\n</head>\n<body style=\"background-color:powderblue\">\n <canvas id=\"twr_d2dcanvas\" width=\"600\" height=\"600\"></canvas>\n\n <script type=\"module\">\n import {mazeRunner} from \"./maze-script.js\";\n\n mazeRunner();\n </script>\n</body>\n
maze-script.jsimport {twrWasmModuleAsync} from \"twr-wasm\";\n\nexport async function mazeRunner() {\n\n const amod=new twrWasmModuleAsync();\n\n await amod.loadWasm('maze.wasm');\n\n //void CalcMaze(HWND hWnd, LONG cell_size, LONG is_black_bg, LONG isd - slow draw)\n await amod.callC([\"CalcMaze\", 0, 7, 0, 1]);\n await amod.callC([\"SolveBegin\"]);\n\n let timer = setInterval(async ()=>{\n let isdone=await amod.callC([\"SolveStep\", 0]); //SolveStep(hwnd))\n if (isdone) clearInterval(timer);\n }, 50);\n}\n
"},{"location":"examples/examples-overview/","title":"WebAssembly C/C++ Examples","text":""},{"location":"examples/examples-overview/#overview","title":"Overview","text":"These C and C++ examples demonstrate how to create different types of WebAssembly (wasm) programs with the twr-wasm library.
These are good examples to use as starting points for your own Wasm projects.
These examples are a good place to learn how to configure clang and wasm-ld to compile and link C/C++ code for use with WebAssembly (wasm).
"},{"location":"examples/examples-overview/#example-quick-links","title":"Example Quick Links","text":"Each of these examples are designed to illustrate how to use a feature of twr-wasm.
Name Description Link helloworld A very simple C Wasm example to get you started helloworld stdio-div This simple C program demos inputting andprinting characters to adiv
tag stdio-div stdio-canvas This simple C program demos writing and inputtingfrom a <canvas>
tag that twr-wasm configuresas a windowed \"mini-terminal\" stdio-canvas balls These fun Bouncing Balls are written in C++ and demo the2D drawing APIs with a C++ Canvas wrapper class balls pong A simple game of Pong written in C++ to demo 2D drawing APIs with a C++ canvas wrapper class and taking user input from JS pong maze This is an old Win32 program ported to wasmand demos the 2D Draw APIs maze fft A demo of calling a C library to perform an FFTthat is graphed in TypeScript fft callC A demo of passing and returning values betweenJavaScript and Wasm module callc tests twr-wasm unit tests tests tests-user \"cli\" for tests using libc++ and <canvas>
tests-user tests-libcxx Smoke test for libc++. Shows how to use libc++. tests-libcxx"},{"location":"examples/examples-overview/#building-the-examples","title":"Building the Examples","text":"See Example Readme for more information on building and running the examples.
"},{"location":"examples/examples-pong/","title":"Pong - 2D Game Example","text":"Similar to the balls example, this example uses twr-wasm's 2D Draw API and a C++ canvas class to run a simple game of singleplayer Pong.
The Pong example demonstrates
This example does not use libc++, which results in smaller code size. For an example that uses libc++ see tests-libcxx.
"},{"location":"examples/examples-pong/#screen-grab-of-pong-example","title":"Screen Grab of Pong Example","text":""},{"location":"examples/examples-stdio-canvas/","title":"stdio-canvas - Terminal Using a Canvas Tag","text":"A simple ANSI WebAssembly C \"terminal\" is created with input and output directed to an HTML <canvas>
tag.
This example will move a string up or down in the terminal window when you press the u or d or arrow keys.
This example also draws a graphic box around the terminal window.
#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#include <locale.h>\n#include \"twr-crt.h\"\n\n/* this twr-wasm C example draws a utf-8 string in the middle of a windowed console, */\n/* and allows the user to move the string up or down with the u, d or arrow keys */\n\n/* see include/twr-io.h for available functions to draw chars to windowed console */\n\nvoid draw_outline(struct IoConsoleWindow* iow);\nvoid show_str_centered(struct IoConsoleWindow* iow, int h, const char* str);\n\nvoid stdio_canvas() {\n struct IoConsoleWindow* iow=(struct IoConsoleWindow*)twr_get_stdio_con();\n\n assert(iow->con.header.type&IO_TYPE_WINDOW);\n\n setlocale(LC_ALL, \"\"); // set user default locale, which is always UTF-8. This is here to turn on UTF-8.\n\n int h;\n const char* str=\"Hello World (press u, d, \u2191, or \u2193)\"; // arrows are UTF-8 multibyte\n const char* spc=\" \";\n char inbuf[6]; // UTF-8 should be max 4 bytes plus string ending 0\n\n h=iow->display.height/2;\n\n draw_outline(iow);\n\n while (1) {\n show_str_centered(iow, h, str);\n io_mbgetc(stdin, inbuf); // also see twr_getc32 documentation\n show_str_centered(iow, h, spc); // erase old string\n\n if (strcmp(inbuf,\"u\")==0 || strcmp(inbuf,\"\u2191\")==0) { // arrows are multibyte UTF-8.\n h=h-1;\n if (h<1) h=1; // border I drew is in the 0 position\n }\n if (strcmp(inbuf,\"d\")==0 || strcmp(inbuf,\"\u2193\")==0) {\n h=h+1;\n if (h>=(iow->display.height-1)) h=iow->display.height-2; // border I drew is in the height-1 position\n }\n }\n}\n\nvoid show_str_centered(struct IoConsoleWindow* iow, int h, const char* str) {\n int len=twr_mbslen_l(str, twr_get_current_locale());\n int x=(iow->display.width-len)/2;\n io_set_cursorxy(iow, x, h);\n io_putstr(&iow->con, str);\n}\n\nvoid draw_outline(struct IoConsoleWindow* iow) {\n const int w=iow->display.width*2; // graphic cells are 2x3\n const int h=iow->display.height*3;\n unsigned long fgcolor, bgcolor;\n\n io_begin_draw(&iow->con);\n\n io_get_colors(&iow->con, &fgcolor, &bgcolor);\n io_set_colors(&iow->con, 0x000000, bgcolor); // draw in black\n\n for (int i=0; i<w; i++) {\n io_setreset(iow, i, 0, true);\n io_setreset(iow, i, h-1, true);\n }\n\n for (int i=0; i<h; i++) {\n io_setreset(iow, 0, i, true);\n io_setreset(iow, w-1, i, true);\n }\n\n io_set_colors(&iow->con, fgcolor, bgcolor); // restore\n\n io_end_draw(&iow->con);\n\n}\n
"},{"location":"examples/examples-stdio-canvas/#html-code","title":"HTML Code","text":"index.html<!doctype html>\n<html>\n<head>\n <title>stdio-canvas example</title>\n</head>\n<body>\n <canvas id=\"twr_iocanvas\" tabindex=\"0\"></canvas>\n\n <!-- importmap used when un-bundled -->\n <script type=\"importmap\">\n {\n \"imports\": {\n \"twr-wasm\": \"../../lib-js/index.js\"\n }\n }\n </script>\n\n <script type=\"module\">\n import {twrWasmModuleAsync} from \"twr-wasm\";\n\n try {\n const amod = new twrWasmModuleAsync({windim:[50,20], forecolor:\"beige\", backcolor:\"DarkOliveGreen\", fontsize:18});\n\n document.getElementById(\"twr_iocanvas\").focus();\n document.getElementById(\"twr_iocanvas\").addEventListener(\"keydown\",(ev)=>{amod.keyDownCanvas(ev)});\n\n await amod.loadWasm(\"./stdio-canvas.wasm\");\n const r=await amod.callC([\"stdio_canvas\"]);\n console.log(\"callC returned: \"+r);\n }\n catch(ex) {\n console.log(\"unexpected exception\");\n throw ex;\n }\n\n </script>\n</body>\n</html>\n
"},{"location":"examples/examples-stdio-div/","title":"stdio-div - Printf and Input Using a div Tag","text":"This simple WebAssembly C program demos inputting and printing characters with a div
tag.
#include <stdio.h>\n#include <stdlib.h>\n#include \"twr-crt.h\"\n\nvoid stdio_div() {\n char inbuf[64];\n int i;\n\n printf(\"Square Calculator\\n\");\n\n while (1) {\n printf(\"Enter an integer: \");\n twr_mbgets(inbuf);\n i=atoi(inbuf);\n printf(\"%d squared is %d\\n\\n\",i,i*i);\n }\n}\n
"},{"location":"examples/examples-stdio-div/#html-code","title":"HTML Code","text":"We are using twrWasmModuleAsync
which integrates blocking C code into JavaScript. twrWasmModuleAsync
can also be used to receive key input from a <div>
or <canvas>
tag.
<head>\n <title>stdio-div example</title>\n</head>\n<body>\n <div id=\"twr_iodiv\" style=\"background-color:LightGray;color:DarkGreen\" tabindex=\"0\">Loading... <br></div>\n\n <script type=\"module\">\n import {twrWasmModuleAsync} from \"twr-wasm\";\n\n let amod;\n\n try {\n amod = new twrWasmModuleAsync();\n\n document.getElementById(\"twr_iodiv\").innerHTML =\"<br>\";\n document.getElementById(\"twr_iodiv\").addEventListener(\"keydown\",(ev)=>{amod.keyDownDiv(ev)});\n\n await amod.loadWasm(\"./stdio-div.wasm\");\n await amod.callC([\"stdio_div\"]);\n }\n catch(ex) {\n amod.divLog(\"unexpected exception\");\n throw ex;\n }\n\n </script>\n</body>\n
"},{"location":"gettingstarted/basicsteps/","title":"Basic Steps To Create Your Wasm Project","text":"This section describes the basic steps to integrate your TypeScript/JavaScript with C/C++ WebAssembly code.
"},{"location":"gettingstarted/basicsteps/#overview-of-webassembly-project","title":"Overview of WebAssembly Project","text":"Your C/C++ WebAssembly project will consist of HTML (and related JavaScript or TypeScript) and C or C++ source files that are compiled into a \".wasm
\" binary file that is loaded as a WebAssembly module by your JavaScript.
On the JavaScript side of your WebAssembly project you will use the twr-wasm JavaScript/TypeScript class twrWasmModule
or twrWasmModuleAsync
to load the .wasm
module, and then call C functions in it using callC
(more details are in the TypeScript/Javascript API section).
You will call C functions (or C++ with ' extern \"C\" ' linkage) in the .wasm
module from your JavaScript. You can also call JavaScript functions from your C/C++ code, but this is less common.
There is no direct equivalent to a C \"main\". Instead, a Wasm module provides exported C functions that you can call from JavaScript/TypeScript. A Wasm module is more like a runtime loaded dynamic library.
You're C/C++ code can be non-blocking or blocking. Blocking means that it \"takes a long time\" to return. For example, if you want to send mouse events to C code, have the code process them then return, this would be non-blocking. Alternately, if your C code is a big loop that never returns, that would be very blocking. You can use the twr-wasm class twrWasmModuleAsync
to execute blocking code from JavaScript. The example maze demonstrates both non-blocking and blocking C calls.
Here are some examples of different types of C/C++ code:
<div>
or <canvas>
tag. This is explained in the stdio section.<div>
or <canvas>
using stdio type C/C++ functions, or it could render to a <canvas>
using 2D drawing APIs that correspond to JavaScript canvas 2D draw operations. (Balls) is an example.Here are the general steps to integrate your C with your JavaScript:
.wasm
file.import
. <div id=twr_iodiv>
or <canvas id=twr_iocanvas>
to your HTML (see stdio)new twrWasmModule()
, followed by a call to loadWasm()
, then one or more callC()
.twrWasmModuleAsync()
-- which is interchangeable with twrWasmModule, but proxies through a worker thread, and adds blocking support, including blocking char input.This section explains twr-wasm's WebAssembly support for ASCII, UTF-8, windows-1252, and UTF-32 character encoding.
"},{"location":"gettingstarted/charencoding/#getting-started","title":"Getting Started","text":"When using C with twr-wasm, you will likely want to add this line to the start of your code:
setlocale(LC_ALL, \"\")\n
This will change the C locale language to the one selected in the browser, and will enable consistent UTF-8 character encoding support.
Without this line, the standard C runtime will (mostly) default character encoding to ASCII, as per the standard. The exception is that just as with gcc, twr-wasm consoles support outputting UTF-8.
"},{"location":"gettingstarted/charencoding/#character-encodings","title":"Character Encodings","text":"twr-wasm supports ASCII, UNICODE, and extended-ASCII (in the form of Windows-1252).
These days UNICODE with UTF-8 encoding is the most popular method of displaying and encoding text. UTF-8 is popular because it has the deep character glyph definitions of UNICODE with an encoding that provides (a) the best backwards compatibility with ASCII, and (b) a compact memory footprint. It does this at the expense of some multibyte complexities.
UTF-8 is variable length, and uses between one to four bytes to represent any unicode code point, with ASCII compatibility in the first 128 bytes. It is also the standard for the web, and the default for clang. But because UTF-8 uses a variable number of bytes per character it can make string manipulation in C a bit harder than ASCII, Windows-1252 or UTF-32.
In this document you will see the term \"locale\". This term originated (at least as its commonly used in programming) in the standard C library, and is also used in the standard C++ library (libc++ in twr-wasm). A locale refers to a region of the world, along with a specific character encoding. The twr-wasm standard c runtime uses a label akin to this to define a locale: en-US.UTF-8
. Of note is that libc++ and the standard C runtime have different domains for their locales (ie, they don't directly impact each other). You can learn more about locales by searching the internet.
twr-wasm C locales support ASCII, UTF-8 or windows-1252 character encoding. UTF-16/32 are not supported as a std c lib locale setting, but functions are provided to convert utf-32 (unicode code points) to and from ASCII, UTF-8, and windows-1252 \"code pages\" (there are other miscellaneous utf-32 based functions as well.)
Although twr-wasm's standard c library locale doesn't support utf-32 directly, you can use int arrays (instead of byte arrays) to hold utf-32 strings, and then convert them to/from utf-8 with the help of the provided functions for this purpose. Alternately, you can use libc++, which has classes that directly support utf-16 and utf-32.
"},{"location":"gettingstarted/charencoding/#windows-compatibility-with-windows-1252","title":"Windows Compatibility with Windows-1252","text":"Windows-1252 is the default character encoding on Windows computers in many countries - particularly the Americas and western Europe -- and particularly when using MSVC. Linux, clang, gcc, and the web commonly default in some way to UTF-8 character encoding. Windows-1252 is an extension of ASCII that uses a single byte per character. This makes it easier than UTF-8 from a programmers perspective, but it doesn't represent as many characters. It is supported by twr-wasm to make it easier to port legacy C code, windows code, as well as a simpler alternative to UTF-8.
twr-wasm supports Windows-1252, and you can enable it like this:
setlocale(LC_ALL, \".1252\")\n
This will set the locale to the default browser language, and character encoding to Windows-1252.
1252 String Literals These days text editors generally default to UTF-8. In order to use windows-1252 source code and/or string literals, such as const char * str=\"\u20ac100\"
you may need to:
--finput-charset
and -fexec-charset
By default, the Microsoft Visual Studio C compiler (MSVC) does not treat string literals as UTF-8. Instead, it treats them as being encoded in the current code page of the system, which is typically Windows-1252 on western european language Windows systems. twr-wasm is designed to work with clang, which does default to utf-8, so if you are compiling code written for MSVC, and you use extend character sets (non ASCII), you may need to adjust your compiler settings with the flags mentioned above.
"},{"location":"gettingstarted/charencoding/#more","title":"More","text":"For more details see Localization Reference for twr-wasm
"},{"location":"gettingstarted/compiler-opts/","title":"Compiling, Linking, and Memory Options","text":"This section described how to use clang
to compile C/C++ code for WebAssembly, and how to use wasm-ld
to link your files into a .wasm module, when using twr-wasm.
twr-wasm lets you use clang directly, without a wrapper. This section describes the needed clang compile options and the wasm-ld link options. You can also take a look at the example makefiles.
"},{"location":"gettingstarted/compiler-opts/#compiler-notes","title":"Compiler Notes","text":"twr-wasm has been tested with clang 17.0.6 and wasm-ld 17.0.6.
If you are using nix, the default clang packages are wrapped with flags that break compilation. The following packages don't have this issue:
When compiling C code with clang for use with Wasm and twr-wasm, use these clang options:
--target=wasm32 -nostdinc -nostdlib -isystem ../../include\n
Here is an example of a compile command:
clang --target=wasm32 -nostdinc -nostdlib -isystem ./node_modules/twr-wasm/include -c helloworld.c -o helloworld.o\n
-isystem
should be adjusted to point to where the folder twr-wasm/include
is installed. For example:
../../include
is a relative link to include
that works if your project is a sub folder in the examples
folder. ./node_modules/twr-wasm/include
assumes you installed with npm
into your project folder. (see the Hello World Walk Through).When compiling C++ code with clang for use with Wasm and twr-wasm, use these clang options:
--target=wasm32 -fno-exceptions -nostdlibinc -nostdinc -nostdlib -isystem ../../include\n
"},{"location":"gettingstarted/compiler-opts/#wasm-ld-linker-options","title":"wasm-ld Linker Options","text":"Use the wasm-ld linker directly with twr-wasm.
For example:
wasm-ld helloworld.o ./node_modules/twr-wasm/lib-c/twr.a -o helloworld.wasm --no-entry --initial-memory=131072 --max-memory=131072 --export=hello \n
For C and C++ link to twr.a
to link to the twr-wasm library.
For C++ link to libc++.a
if you are using libc++. (see the tests-libcxx example makefile).
Be sure to adjust the path to twr.a
and libc++.a
as needed to the location where twr-wasm/lib-c/
is installed.
All of the twr-wasm functions are staticly linked from the library lib-c/twr.a
. There is also a version ( lib-c/twrd.a
) of twr-wasm library available with debug symbols. One of these two static libraries should be added to the list of files to link (normally this is twr.a
). Both versions are built with asserts enabled. twr.a
is built with -O3
. twrd.a
is built with -g -O0
.
C functions that you wish to call from JavaScript should either have an -export
option passed to wasm-ld
, or you can use the __attribute__((export_name(\"function_name\")))
option in your C function definition.
All exported functions to JavaScript should be C linkage (extern \"C\"
if using C++).
wasm-ld should be passed the following options:
If Using twrWasmModule:
--no-entry --initial-memory=<size> --max-memory=<size>\n
If Using twrWasmModuleAsync:
--no-entry --shared-memory --no-check-features --initial-memory=<size> --max-memory=<size>\n
"},{"location":"gettingstarted/compiler-opts/#memory-options-memory-size-stack-size-etc","title":"Memory Options (Memory Size, Stack Size, etc)","text":"WebAssembly.Memory
contains all the data used by your code (including the data needs of staticly linked libraries such as twr-wasm or libc++), but it does not store your actual code. It provides a contiguous, mutable array of raw bytes. Code execution and storage in WebAssembly are handled separately using the WebAssembly.Module
and WebAssembly.Instance
objects. The code (compiled WebAssembly instructions) is stored in the WebAssembly.Module
, while WebAssembly.Memory
is used to manage the linear memory accessible to the WebAssembly instance for storing data. Examples of data include your static data (.bss section or the .data section), the heap (used by malloc
and free
), and the stack (used for function calls and local variables).
The memory size should be a multiple of 64*1024 (64K) chunks. \"initial-memory\" and \"max-memory\" should be set to the same number since there is no support for automatically growing memory in twr-wasm. The memory is an export out of the .wasm
into the JavaScript code -- you should not create or set the size of WebAssembly.Memory
in JavaScript when using twr-wasm.
You set the memory size for your module (WebAssembly.Memory
) using wasm-ld
options as follows (this examples sets your Wasm memory to 1MB).
if using twrWasmModule
:
--initial-memory=1048576 --max-memory=1048576\n
"},{"location":"gettingstarted/compiler-opts/#twrwasmmoduleasync","title":"twrWasmModuleAsync","text":"If you are using twrWasmModuleAsync
, shared memory must also be enabled. Like this:
--shared-memory --no-check-features --initial-memory=1048576 --max-memory=1048576\n
See this production note on using shared memory.
"},{"location":"gettingstarted/compiler-opts/#stack-size","title":"Stack Size","text":"You can change your C/C++ stack size from the default 64K with the following wasm-ld
option. This example sets the stack at 128K
-z stack-size=131072\n
"},{"location":"gettingstarted/compiler-opts/#print-memory-map","title":"Print Memory Map","text":"You can print your module memory map, heap stats, and stack size using the function from C:
void twr_mem_debug_stats(struct IoConsole* outcon);\n
You can call it from Javascript with the output sent to the debug console (stderr) like this: twrWasmModule/Async.callC([\"twr_wasm_print_mem_debug_stats\"])\n
"},{"location":"gettingstarted/compiler-opts/#typescriptjavascript-malloc-and-memory-access","title":"TypeScript/JavaScript malloc and Memory Access","text":"twrWasmModule
and twrWasmModuleAsync
expose malloc
as an async function, as well as the WebAssembly Module memory as:
async malloc(size:number);\n\nmemory?:WebAssembly.Memory;\nmem8:Uint8Array;\nmem32:Uint32Array;\nmemD:Float64Array;\n
to call free
from JavaScript (you probably won't need to), you can use: twrWasmModule/Async.callC([\"twr_free\", index]); // index to memory to free, as returned by malloc\n
more information on these functions and module public variables can be found in the examples in this section: Passing Function Arguments to WebAssembly.
"},{"location":"gettingstarted/debugging/","title":"Debugging WebAssembly","text":"This section describes tips for debugging your WebAssembly (Wasm) program. Some of these techniques are WebAssembly generic, some are specific to using twr-wasm.
"},{"location":"gettingstarted/debugging/#debug-and-release-libraries","title":"Debug and Release libraries","text":"There are release (twr.a) and debug (twrd.a) versions of the twr-wasm C library. The \"debug\" version has debug symbols enabled with -g
and is built with optimizations disabled via -O0
. The \"release\" version has no debug symbols and optimization is set to -O3
. Both have asserts enabled. In general, you should use the \"release\" version unless you wish to step through the twr-wasm source -- in which case use the \"debug\" version.
libc++.a is not built with debug symbols.
"},{"location":"gettingstarted/debugging/#source-level-debugging-webassembly-cc","title":"Source Level Debugging WebAssembly C/C++","text":"In order to enable C/C++ source debugging with Wasm and clang, do the following:
-O
flag or set to -O0
) twrWasmModuleAsync
-- see the server.py example.Use twr_conlog
to print to the JavaScript console from C (see API ref section).
#include \"twr-wasm.h\"\n\ntwr_conlog(\"hello 99 in hex: %x\",99);\n
Use twrWasmModule.divLog()
to print to a div inside JavaScript code (see API ref section).
Note: If you use this technique, you will not be able to get the C/C++ DevTool chrome extension to run, and so source level debugging won't work. (If you know how to fix this, please contact me on github.)
You can execute and debug JavaScript with Wasm from local files without an HTTP server. It might be helpful to download the twr-wasm source code from github when you do this (so you can step through the twr-wasm typescript code as needed).
See the examples and Example Readme for more detail on how this works.
In general, you will need to add a clip of code similar to this to your HTML:
<script type=\"importmap\">\n {\n \"imports\": {\n \"twr-wasm\": \"./../../lib-js/index.js\"\n }\n }\n</script>\n
Make sure the paths to twr-wasm/lib-js/index.js
are correct for where your source is located. The above is correct for the provided examples.
You will need to set the following flags when running chrome from the shell or VS Code (the first is only strictly required if using twrWasmModuleAsync).
--enable-features=SharedArrayBuffer\n--allow-file-access-from-files\n
If you are using VS Code, You can create a launch.json entry similar to this:
launch.json{\n \"configurations\": [\n {\n \"name\": \"Launch Chrome\",\n \"request\": \"launch\",\n \"type\": \"chrome\",\n \"runtimeArgs\": [\n \"--allow-file-access-from-files\",\n \"--autoplay-policy=no-user-gesture-required\",\n \"--enable-features=SharedArrayBuffer\"\n ],\n \"file\": \"${workspaceFolder}/index.html\",\n \"cwd\": \"${workspaceFolder}/\",\n }\n ]\n}\n
"},{"location":"gettingstarted/helloworld/","title":"Create and Run WebAssembly Hello World","text":"This section shows you, step by step, how to to create a C \"hello world\" program for WebAssembly (Wasm) with twr-wasm, C, HTML, and JavaScript.
You will learn how to:
clang
wasm-ld
to create a helloworld.wasm fileYou can find code for a hello world example in the folder examples\\helloworld. It is similar, but not identical to this walk through. The primary differences are the paths for lib-c, lib-js, and include.
"},{"location":"gettingstarted/helloworld/#step-0-installation","title":"Step 0: Installation","text":"hello-proj
cd
into hello-proj
npm install twr-wasm
hello-proj\\\n\u251c\u2500\u2500package.json\n\u2514\u2500\u2500node_modules\\\n \u2514\u2500\u2500twr-wasm\\\n \u251c\u2500\u2500examples\\\n \u2514\u2500\u2500include\\\n \u2514\u2500\u2500lib-c\\\n \u2514\u2500\u2500lib-js\\\n \u2514\u2500\u2500LICENSE\n \u2514\u2500\u2500package.json\n \u2514\u2500\u2500readme.md\n
Create a file helloworld.c
in hello-proj
helloworld.c
#include <stdio.h>\n\nvoid hello() {\n printf(\"hello world\\n\");\n}\n
"},{"location":"gettingstarted/helloworld/#step-2-create-the-html","title":"Step 2: Create the HTML","text":"Create a file index.html
in hello-proj
index.html
<!doctype html>\n<html>\n<head>\n <title>Hello World</title>\n\n <script type=\"importmap\">\n {\n \"imports\": {\n \"twr-wasm\": \"./node_modules/twr-wasm/lib-js/index.js\"\n }\n }\n </script>\n\n</head>\n<body>\n <div id=\"twr_iodiv\"></div>\n\n <script type=\"module\">\n import {twrWasmModule} from \"twr-wasm\";\n\n const mod = new twrWasmModule();\n await mod.loadWasm(\"./helloworld.wasm\");\n await mod.callC([\"hello\"]);\n </script>\n</body>\n</html>\n
This example uses Import Maps, which are used when not using a bundler like WebPack or Parcel. For smaller projects, this can be simpler with a more clear debugging and development environment. This is the approach we will use for this example (no bundler).
The path in the importmap
section of index.html
should point to the location where you installed twr-wasm/lib-js
. The path above is correct for this project example with the indicated folder structure.
cd hello-proj\nclang --target=wasm32 -nostdinc -nostdlib -isystem ./node_modules/twr-wasm/include -c helloworld.c -o helloworld.o\nwasm-ld helloworld.o ./node_modules/twr-wasm/lib-c/twr.a -o helloworld.wasm --no-entry --initial-memory=131072 --max-memory=131072 --export=hello \n
The path to twr.a
and to include
should match your installation. The above path is correct for this example.
As an alternate to executing clang and wasm-ld from the shell, here is a Makefile that will work for this example:
MakefileCC := clang\nTWRCFLAGS := --target=wasm32 -nostdinc -nostdlib -isystem ./node_modules/twr-wasm/include\nCFLAGS := -c -Wall -O3 $(TWRCFLAGS)\nCFLAGS_DEBUG := -c -Wall -g -O0 $(TWRCFLAGS)\n\n.PHONY: default\n\ndefault: helloworld.wasm\n\nhelloworld.o: helloworld.c\n $(CC) $(CFLAGS) $< -o $@\n\nhelloworld.wasm: helloworld.o \n wasm-ld helloworld.o ./node_modules/twr-wasm/lib-c/twr.a -o helloworld.wasm \\\n --no-entry --initial-memory=131072 --max-memory=131072 \\\n --export=hello \n
Copy the above into a file named Makefile
and execute with make
(or mingw32-make
in windows).
The two easiest ways to load and execute your index.html
web page locally are:
You can run a local server to view your helloworld program.
hello-proj
folder (where your index.html
resides). python server.py
.http://localhost:8000/index.html
At this pont your folder structure should look like this:
hello-proj\\\n\u2514\u2500\u2500node_modules\\\n\u2514\u2500\u2500helloworld.c\n\u2514\u2500\u2500helloworld.o\n\u2514\u2500\u2500helloworld.wasm\n\u2514\u2500\u2500index.html\n\u2514\u2500\u2500Makefile\n\u2514\u2500\u2500package.json\n\u2514\u2500\u2500server.py\n
"},{"location":"gettingstarted/helloworld/#option-b-vs-code-launchjson","title":"Option B: VS Code launch.json","text":"Alternately, you can launch chrome without a local web server. Add an entry similar to the following to hello-proj\\.vscode\\launch.json
. This assumes your workspaceFolder is hello-proj
.
{\n \"configurations\": [\n {\n \"name\": \"Launch Chrome Hello, World!\",\n \"request\": \"launch\",\n \"type\": \"chrome\",\n \"runtimeArgs\": [\n \"--allow-file-access-from-files\",\n \"--autoplay-policy=no-user-gesture-required\",\n \"--enable-features=SharedArrayBuffer\"\n ],\n \"file\": \"${workspaceFolder}/index.html\",\n \"cwd\": \"${workspaceFolder}/\",\n }\n ]\n}\n
Once you have created this file, you:
--autoplay-policy=no-user-gesture-required
and --enable-features=SharedArrayBuffer
are not required for this simple \"hello world\" example, but will be needed if you request user input or you are using twrWasModuleAsync
.
You can find a live link to hello world on this page.
"},{"location":"gettingstarted/helloworld/#next-steps-after-hello-world","title":"Next steps after hello world","text":"A good way to get your own code up and running is to copy one of the examples, get it to build and run, then start modifying it. Note you will need to modify the paths for include
, lib-js
, lib-c
, etc. based on your project structure. The examples are all setup with relative paths assuming the folder structure twr-wasm\\examples\\<example>
The examples include MakeFiles.
\"Hello World\" uses the twr-wasm class twrWasmModule
. If you wish to use C blocking functions, such as twr_getc32
or twr_sleep
, you should use twrWasmModuleAsync
. This square calculator example shows how to do this.
If you wish to build an app that makes non-block calls into C, the balls example shows how to do this. The maze example uses a combination of blocking and non-blocking C functions.
"},{"location":"gettingstarted/helloworld/#debugging","title":"Debugging","text":"See the debugging section for debugging tips, including setting up Wasm source level debugging.
"},{"location":"gettingstarted/installation/","title":"Installing twr-wasm","text":"A simple way to install twr-wasm is:
npm install twr-wasm\n
See the \"Hello World walk through\" in the following section for more specifics.
There are actually two methods of installation with different pros and cons:
npm install
will install everything necessary to build your software: built libraries (lib-js, lib-c) and includes. In addition the examples are installed.git clone
will copy the above as well as the source and VS Code settings.When using twr-wasm
your applications needs to access both JavaScript and C twr-wasm libraries. This is explained in the installation sections below, as well as in the Hello World walk through.
npm install twr-wasm\n
After installation from npm, you will have a folder structure like this: node_modules\\\n twr-wasm\\\n examples\\\n include\\\n lib-c\\\n lib-js\\\n LICENSE\n package.json\n readme.md\n
The JavaScript and TypeScript exports are in lib-js
and should be found by VS Code, TypeScript or your bundler as usual when using a statement like import {twrWasmModule} from \"twr-wasm\"
. The C library (twr.a
) that you will need to link your C/C++ program to is found in the libs-c
folder, and the C/C++ include files that you will need to use in your C/C++ program are found in the include
folder. You will need to use paths to to these folders in your makefile. See the Hello World walk through for details.
There is no real downside to this installation method, except possibly: (1) it does not include source code (use git clone for that), and (b) the C libraries are buried inside your node_modules.
"},{"location":"gettingstarted/installation/#git-install","title":"git install","text":" git clone https://github.com/twiddlingbits/twr-wasm\n
This method of installation installs the complete code base, including source and built binaries.
The primary downside to this method is that the JavaScript side of twr-wasm will not be placed in a node_modules folder. This will create a little extra work to configure a bundler, TypeScript or VS Code to find the location of imports.
There are a few solutions to this. For example, in the provided Hello World example, a package.json
file with an alias
entry is used. This syntax is supported by the Parcel bundler:
{\n \"@parcel/resolver-default\": {\n \"packageExports\": true\n },\n \"alias\": {\n \"twr-wasm\": \"../../lib-js/index.js\"\n },\n \"dependencies\": {\n \"twr-wasm\": \"^2.0.0\"\n }\n}\n
The FFT example uses the paths
entry in the tsconfig.json
file. This is found by TypeScript, VS Code and the Parcel bundler. This is probably the best solution if you are using TypeScript.
\"paths\": {\n \"twr-wasm\": [\"./../../lib-js/index\"]\n}\n
The paths for alias
and paths
shown above are correct for the included examples, but will likely need to be adjust for your project.
All of the examples have makefiles that use a relative path for twr.a
and includes
. These paths will work fine if your code is in an examples sub-folder as a peer to the other examples. But assuming your code is in your own project folder elsewhere, you will need to determine the correct path to twr.a
and includes
for your project's makefile. Details on how to do this can be found in the following sections: Hello World walk through and the Compiler and Linker Options section.
To build C/C++ code for use in your Wasm project, you will need to install clang and the wasm-ld linker. If you are using Windows, more details can be found at the end of the Building Source section.
"},{"location":"gettingstarted/installation/#python","title":"python","text":"To use the included examples\\server.py
you will need to install python. server.py is a simple HTTP server for local testing that sets the correct CORS headers for twrWasmModuleAsync
. As explained in the following Hello World walk through, you can alternately execute HTML files directly using VS Code and Chrome.
This article describes techniques to transfer data between JavaScript/TypeScript and C/C++ when using WebAssembly. It delves a bit \u201cunder the covers\u201d to explain how this works when you use a library like twr-wasm or Emscripten. In this article, I am using twr-wasm for the examples. Emscripten does something similar.
For an example that illustrates the concepts discussed here, see: the callC example.
"},{"location":"gettingstarted/parameters/#webassembly-virtual-machine-intrinsic-capabilities","title":"WebAssembly Virtual Machine Intrinsic Capabilities","text":"The WebAssembly VM (often referred to as a Wasm \u201cRuntime\u201d) is limited to passing numbers between C functions and the Wasm host (I\u2019ll assume that\u2019s JavaScript for this document). In other words, if you are using the most basic WebAssembly capabilities provided by JavaScript, such as WebAssembly.Module
, WebAssembly.Instance
, and instance.exports
, your function calls and return types can only be:
These correspond to the WebAssembly spec support for: i32, i64, f32, and f64.
Note that a JavaScript number
is of type Float 64 (known as a double
in C/C++.). If you are storing an integer into a JavaScript number
, it is converted to a Float 64, and its maximum \"integer\" precision is significantly less than 64 bits (its about 52 bits, but this is a simplification). As a result, to use a 64-bit integers with JavaScript the bigint
type is used.
When using 32-bit WebAssembly (by far the most common default), and you call a C function from JavaScript without using any \u201chelper\u201d libraries (like twr-wasm), the following argument types can be passed:
number
type is converted to an Integer 32 and passed to C when the C function prototype specifies a signed or unsigned int
, long
, int32_t
, or a pointer type. All of these are 32 bits in length in wasm32.bigint
type is converted to an Integer 64 and passed to C when the C function prototype specifies signed or unsigned int64_t
(or equivalent). Attempting to pass a JavaScript number
to a C int64_t
will fail with a JavaScript runtime error.number
type is converted to a Float 32 when the C function prototype specifies a float
.number
type is passed as a Float 64 when the C function prototype specifies a double
.The same rules apply to the return types.
"},{"location":"gettingstarted/parameters/#c-structs-javascript-c","title":"C Structs: JavaScript <--> C","text":"This section shows how to create a C struct
in JavaScript, then pase it to a C function, and then read the modified C struct
in JavaScript.
Although the techniques described here are explained with a struct
example, the basic techniques are used with other data types as well (such as strings). For common data types, like a string, libraries like twr-wasm will handle these details for you automatically.
To create and pass a C struct
from JavaScript to C, the technique is to call the WebAssembly C malloc
from JavaScript to allocate WebAssembly memory and then manipulating the memory in JavaScript. One complexity is that each struct entry\u2019s memory address needs to be calculated. And when calculating the WebAssembly Memory indices for the struct entries, C structure padding must be accounted for.
Before we delve into the actual code, lets review C struct entry padding.
In clang, if you declare this structure in your C code:
struct test_struct {\n int a;\n char b;\n int *c;\n};\n
int a
, will be at offset 0 in memory (from the start of the struct
in memory).char b
, will be at offset 4 in memory. This is expected since the length of an int is 4 bytes.int *c
, will be at offset 8 in memory, not at offset 5 as you might expect. The compiler adds three bytes of padding to align the pointer to a 4-byte boundary.This behavior is dependent on your compiler, cpu, and whether you are using 32 or 64-bit architecture. For wasm32 with clang:
If you are not familiar with structure padding, there are many articles on the web.
Alignment requirements are why twr-wasm malloc
(and GCC malloc
for that matter) aligns new memory allocations on an 8-byte boundary.
We can create and initialize the above struct test_struct
like this in JavaScript:
//...\nconst mod = new twrWasmModule();\n//...\nconst structSize=12;\nconst structIndexA=0;\nconst structIndexB=4;\nconst structIndexC=8; // compiler allocates pointer on 4 byte boundaries\nlet structMem=await mod.malloc(structSize);\nlet intMem=await mod.malloc(4);\nmod.setLong(structMem+structIndexA, 1);\nmod.mem8[structMem+structIndexB]=2; // you can access the memory directly with the mem8, mem32, and memD (float64 aka double) byte arrays.\nmod.setLong(structMem+structIndexC, intMem);\nmod.setLong(intMem, 200000);\n
note that:
await mod.malloc(structSize)
is a shortcut for: await mod.callC([\"malloc\", structSize])
mod.malloc
returns a C pointer as a number
. This pointer is also an index into WebAssembly.Memory
-- which is exposed as the byte array (Uint8Array
) via mod.mem8
by twr-wasm.struct
in JavaScript/TypeScript, you have to do a bit of arithmetic to find the correct structure entry.int *c
is a pointer to an int
. So a separate malloc
to hold the int
is needed. setLong
to set a byte. Instead you access the byte array view of the WebAssembly memory with mod.mem8
. Functions like mod.setLong
manipulate this byte array for you.mod.mem8
(Uint8Array), you can also access WebAssembly.Memory directly via mod.mem32
(Uint32Array), and mod.memD
(Float64Array).Assume we have C code that adds 2 to each entry of the test_struct
:
__attribute__((export_name(\"do_struct\")))\nvoid do_struct(struct test_struct *p) {\n p->a=p->a+2;\n p->b=p->b+2;\n (*p->c)++;\n (*p->c)++;\n}\n
Once the struct
has been created in JavaScript, you can call the C function do_struct
that adds 2 to each entry like this in twr-wasm:
await mod.callC([\"do_struct\", structMem]); // will add two to each value\n
"},{"location":"gettingstarted/parameters/#accessing-returned-c-struct-in-javascript","title":"Accessing returned C struct in JavaScript","text":"You access the returned elements like this using JavaScript:
success=mod.getLong(structMem+structIndexA)==3;\nsuccess=success && mod.mem8[structMem+structIndexB]==4;\nconst intValPtr=mod.getLong(structMem+structIndexC);\nsuccess=success && intValPtr==intMem;\nsuccess=success && mod.getLong(intValPtr)==200002;\n
You can see the additional complexity of de-referencing the int *
.
You can free the malloced memory like this:
await mod.callC([\"free\", intMem]); // unlike malloc, there is no short cut for free, yet\nawait mod.callC([\"free\", structMem]);\n
The complete code for this example is here.
"},{"location":"gettingstarted/parameters/#passing-strings-from-javascript-to-cc-webassembly","title":"Passing Strings from JavaScript to C/C++ WebAssembly","text":"Although you can use the technique I am about to describe here directly (by writing your own code), it is generally accomplished by using a third-party library such as twr-wasm or Emscripten. These libraries handle the nitty-gritty for you.
To pass a string from JavaScript/TypeScript to a WebAssembly module, the general approach is to:
malloc
from JavaScript. malloc
returns a pointer, which is an index into the WebAssembly Memory.In the case of twr-wasm, the above steps are handled automatically for you by the callC
function:
mod.callC([\"my_function\", \"this is my string\"]); // mod is instance of twrWasmModule\n
Under the covers, to pass \"this is my string\" from JavaScript to the C Web Assembly function, callC
will execute code like this:
// twrWasmModule member function\nasync putString(sin:string, codePage = codePageUTF8) {\n const ru8 = this.stringToU8(sin, codePage); // convert a string to UTF8 encoded characters stored in a Uint8Array\n const strIndex = await this.malloc(ru8.length + 1); // shortcut for: await this.callC([\"malloc\", ru8.length + 1]);\n this.mem8.set(ru8, strIndex); // mem8 is of type Uint8Array and is the Wasm Module\u2019s Memory\n this.mem8[strIndex + ru8.length] = 0;\n return strIndex;\n}\n
this.malloc
is the standard C runtime malloc
function, provided by twr-wasm, and linked into your .wasm
code that is loaded into the WebAssembly Module. Likewise, twr-wasm will call free
after the function call is executed."},{"location":"gettingstarted/parameters/#returning-a-string-from-cc-webassembly-to-javascript","title":"Returning a String from C/C++ WebAssembly to JavaScript","text":"Returning a string from C to JavaScript is the reverse of passing in a string from JavaScript to C. When the \u201craw\u201d WebAssembly capabilities are used (WebAssembly.Module
, etc.) and your C code looks like this:
return(\"my string\");\n
The WebAssembly VM and JavaScript host will cause your JavaScript to receive an unsigned 32-bit integer. This is the pointer to the string, cast to an unsigned 32-bit integer. This integer is an index into the WebAssembly Memory.
twr-wasm provides a function to pull the string out of WebAssembly Memory and convert the character encoding to a JavaScript string. JavaScript strings are Unicode 16, but twr-wasm supports ASCII, UTF-8, and windows-1252 string encoding. When extracted and converted, a copy of the string is made.
const retStringPtr = await mod.callC([\"ret_string_function\"]);\nconsole.log(mod.getString(retStringPtr));\n
The retStringPtr
is an integer 32 (but converted to a JavaScript number
, which is Float 64). This integer is an index into the WebAssembly Memory.
When callC
in twr-wasm is used to pass an ArrayBuffer to and from C/C++, some details are handled for you. The technique is similar to that used for a string
or as performed manually for a struct
above, with the following differences:
ArrayBuffers
have entries of all the same length, so the index math is straight forward and now struct
padding is needed.ArrayBuffer
is passed to a function, the function receives a pointer to the malloc
memory. If the length is not known by the function, the length needs to be passed as a separate argument.callC
returns, any modifications made to the memory by the C code are reflected back into the ArrayBuffer
.Here is an example:
let ba = new Uint8Array(4);\nba[0] = 99; ba[1] = 98; ba[2] = 97; ba[3] = 6;\nconst ret_sum = await mod.callC([\"param_bytearray\", ba.buffer, ba.length]);\n
See this example for the complete example.
"},{"location":"gettingstarted/parameters/#passing-a-javascript-object-to-webassembly","title":"Passing a JavaScript Object to WebAssembly","text":""},{"location":"gettingstarted/parameters/#simple-case-use-c-struct","title":"Simple Case - use C struct","text":"For a simple object like this:
const a = 'foo';\nconst b = 42;\n\nconst obj = {\n a: a,\n b: b\n};\n
It is straightforward to convert to a C struct like this:
struct obj {\n const char* a;\n int b;\n};\n
To pass this JavaScript object to WebAssembly, a C struct is created (using the struct
techniques described above). Each object entry is then copied into the corresponding C struct
entry (using the struct
and string techniques described above)."},{"location":"gettingstarted/parameters/#more-complicated-object","title":"More Complicated Object","text":"A JavaScript object can contain entries that are of more complexity than simple C data types. For example:
const a = 'foo';\nconst b = 42;\nconst map = new Map();\nmap1.set('a', 1);\nmap1.set('b', 2);\nmap1.set('c', 3);\nconst object2 = { a: a, b: b, c: map };\n
In this case, you are going to have to do more work. An approach is to use the libc++ map
class, which is similar to the JavaScript Map
. You could also perhaps use the libc++ vector
.
To handle this more complicated JavaScript object with a Map
entry, an approach is to export functions from WebAssembly to create and add entries to the libc++ map
(you need to use extern 'C'
to export these C++ access functions as C functions). In otherworld, you might export from your Wasm Module C functions like this:
void* createMap(); // return an unsigned long Map ID\nvoid addIntToMap(void* mapID, int newInt);\n
You would then use these functions in JavaScript to build your C++ map
. JavaScript would access this map
using the unsigned long
identifier (the void *
returned by createMap
). After creating and adding entries to the map
, you would set this MapID to object2.c
.
There are alternative approaches. For example, you could convert the JavaScript Map
to a C struct, by enumerating every entry in the Map
. Your C struct might look like: `
struct entry {\n char* name;\n int value;\n};\n\nstruct mapUnroll {\n int MapLen;\n struct entry* entries[];\n};\n
This approach is probably even more work, less general purpose, and less efficient.
"},{"location":"gettingstarted/parameters/#summary","title":"Summary","text":"I hope this has demystified how JavaScript values are passed to and from WebAssembly. In many cases, functions like twr-wasm's mod.callC
will handle the work for you. But in more bespoke cases, you will have to handle some of the work yourself.
This section describes how you can direct C/C++ standard input or output to or from a div or canvas tag in a twr-wasm C/C++ Wasm project.
"},{"location":"gettingstarted/stdio/#use-div-or-canvas-tag","title":"Use div or canvas tag","text":"Standard input and output can be directed to a <div>
or to a <canvas>
HTML tag. Using a <div>
is a simple way to display the output of a printf
, or to get input from getc
(using traditional standard library blocking input).
A <canvas>
tag can be used by twr-wasm to create a simple ANSI style terminal or console. This windowed terminal supports the same streamed output and input features as a does a div tag, but also supports x,y coordinates, colors, and other features. The window console supports chunky (low res) graphics (each character cell can be used as a 2x3 graphic array).
Another difference between a div stream and a canvas stream, is that a div tag will grow as more text is added. On the other hand, a canvas tag has a fixed width and height, and additional text will cause a scroll as it fills up the window.
Unicode characters are supported by stdout
and stdin
(see Character Encoding Support with twr-wasm).
If you wish to use a div or canvas tag for stdio when using twr-wasm, in your HTML file add a <div id=\"twr_iodiv\">
or alternately a <canvas id=\"twr_iocanvas\">
tag.
<div id=\"twr_iodiv\">
will be used for stdin
and stdout
if found.<canvas id=\"twr_iocanvas\">
will be used for stdin
and stdout
if it exists and no div found. <div>
or <canvas>
is defined in your HTML, then stdout
is sent to the debug console in your browser. And stdin
is not available.Note that you can also add a <canvas id=\"twr_d2dcanvas\">
to your HTML to define a canvas to be used by twr-wasm's 2D drawing APIs.
stderr
streams to the browser's debug console.
<div>
View square demo Source simple \"terminal\" via <canvas>
View hello world demo Source \"cli\" with a <canvas>
stdio View CLI demo using libc++ Source"},{"location":"gettingstarted/stdio/#io-console-docs","title":"IO Console Docs","text":"stdin
, stdout
, and stderr
are abstracted by a twr-wasm IO Consoles.
Stdin
and stdout
can support UTF-8 or Windows-1252 character encodings (see Character Encoding Support with twr-wasm).
#include <stdio.h>
to access stdout
, stdin
, stderr
, and FILE
.
FILE is supported for user input and output, and for stderr. File i/o (to a filesystem) is not currently supported.
"},{"location":"gettingstarted/stdio/#stdout-and-stderr-functions","title":"stdout and stderr functions","text":"You can use these functions to output to the standard library defines stderr
or stdout
:
fputc, putc, vfprintf, fprintf, fwrite\n
You can also use the IO Console functions referenced above to send to stdout
and stderr
.
These functions go to stdout:
printf, vprintf, puts, putchar\n
Note that when characters are sent to the browser console using stderr
they will not render to the console until a newline, return, or ASCII 03 (End-of-Text) is sent.
For example:
#include <stdio.h>\n\nfprintf(stderr, \"hello over there in browser debug console land\\n\");\n
A more common method to send output to the debug console is to use twr_conlog
. See General C API Section.
Reading from stdin
is blocking, and so twrWasmModuleAsync
must be used to receive keys from stdin. See the next section for the needed JavaScript line.
You can get characters from stdin
with any of these functions:
io_mbgets
- get a multibyte string from the stdin IoConsole using the current locale character encoding io_getc32
- gets a 32 bit unicode code point the stdin IoConsoleio_mbgetc
- get a single multibyte character from the stdin IoConsole using the current locale character encodingtwr_mbgets
- similar to io_mbgets
, but assumes stdin
is the input sourcegetc
(sames as fgetc
) - get a single byte from a FILE * (struct IoConsole *) -- which should be stdin. Returning ASCII or extended ASCII (window-1252 encoding).twrWasmModuleAsync
must be used to receive keys from stdin. In addtion, you should add a line like the following to your JavaScript for stdin to work:
If using twr_iodiv
document.getElementById(\"twr_iodiv\").addEventListener(\"keydown\",(ev)=>{amod.keyDownDiv(ev)});\n
If using twr_iocanvas
document.getElementById(\"twr_iocanvas\").addEventListener(\"keydown\",(ev)=>{amod.keyDownCanvas(ev)});\n
You likely want a line like this to automatically set the focus to the div or canvas element (so the user doesn't have to click on the element to manually set focus. Key events are sent to the element with focus.):
document.getElementById(\"twr_iocanvas\").focus();\n
You will also need to set the tabindex attribute in your div tag like this:
<div id=\"twr_iodiv\" tabindex=\"0\"></div>\n
See the stdio-div and stdio-canvas examples.
"},{"location":"gettingstarted/stdio/#sending-asyncronous-events-to-wasm-functions","title":"Sending asyncronous events to Wasm functions","text":"Note that this section describes blocking input using stdin. As an alternative, you can send events (keyboard, mouse, timer, etc) to a non-blocking C function from JavaScript using callC
. See the balls
example.
The source can be found at:
https://github.com/twiddlingbits/twr-wasm\n
The main
branch contains the latest release. The dev
branch is work in progress.
You will need these core tools, versions used in release are in ():
In addition, you might need:
There is a deprecated gcc build that I used to use for testing, but now the tests are executed in wasm.
"},{"location":"more/building/#to-build-the-libraries-lib-c-lib-js","title":"To Build the Libraries (lib-c, lib-js)","text":"cd source\nmake\n
or on windows cd source\nmingw32-make\n
"},{"location":"more/building/#to-build-the-examples","title":"To Build the Examples","text":"See examples/readme.md for more information.
To build the examples, but not bundle them.
cd examples\nsh buildall.sh\n
To build bundles:
sh buildbundles.sh\n
"},{"location":"more/building/#to-build-the-docs","title":"To Build the docs","text":"The docs are created using the material theme for mkdocs.
In twr-wasm root folder:
mkdocs build\n
The destination of the build is found in the mkdocs.yml
file (site_dir: azure/docsite/
).
Usually the docs are built as part of building the static web site that hosts the docs and examples. This is accomplished using this shell script (found in examples folder):
buildazure.sh\n
"},{"location":"more/building/#to-build-libc-for-wasm-and-twr-wasm","title":"To Build libc++ for Wasm and twr-wasm","text":"See the instructions in the comments in the shell script source\\libcxx\\buildlibcxx.sh
Here is how I installed the tools for windows:
install MSYS2 \n 1. https://www.msys2.org/\n 2. After the install completes, run UCRT64 terminal by clicking on the MSYS2 UCRT64 in the Start menu\n 3. pacman -Syuu\n\n install gcc using MSYS2 UCRT64\n 1. Use MSYS2 UCRT64 terminal (per above)\n 1. pacman -S mingw-w64-ucrt-x86_64-toolchain\n\n install clang and wasm-ld using MSYS2 UCRT64\n 2. Use MSYS2 UCRT64 (per above)\n 1. pacman -S mingw-w64-ucrt-x86_64-clang\n 2. pacman -S mingw-w64-x86_64-lld\n\nupdate PATH env variable using the windows control panel (search for path)\n 2. added C:\\msys64\\ucrt64\\bin \n 3. added C:\\msys64\\mingw64\\bin \n 4. added C:\\msys64\\usr\\bin (for sh.exe used by mingw32-make)\n
wabt tools: can be found here https://github.com/WebAssembly/wabt/releases
"},{"location":"more/production/","title":"CORS headers needed to use twrWasmModuleAsync","text":"Important Production Note
twr-wasm class twrWasmModuleAsync
uses SharedArrayBuffers
, and there are special CORS headers that need to be configured to use SharedArrayBuffers
, that are not widely enabled by default on web servers. The server.py or staticwebapp.config.json examples show which headers to set (also see the SharedArrayBuffer
documentation online).
HTML browsers can load a WebAssembly module, and execute it's bytecode in a virtual machine. To create this bytecode (.wasm file) from C/C++, you compile your code using clang with the target code format being WebAssembly (aka Wasm) byte code. If you are not using a support library like twr-wasm or emscripten, there are a few issues that one immediately encounters trying to execute code that is more complicated than squaring a number.
The Wasm virtual machine simply executes the instructions that are generated by the clang compiler and linked by the linker into the .wasm file. The first issue encountered is that some code that is generated by a compiler assumes a compiler support library will be linked to your code. That is, clang code generation will produce calls to compiler support routines for floating point, memcpy
, and the like. In clang, these support routines are in the \"compile-rt\" support library. Typically clang handles this behind to scenes for you. But support for a WebAssembly version of this compiler support library is not (as of this writing) included in a clang distribution.
The next level up the library stack is the standard C runtime library. This library provides functions like malloc
and printf
. And then built on the standard C runtime library is the standard c++ library - like libc++. WebAssembly versions of these libraries are also not part of a clang distribution.
To get access to WebAssembly versions of these libraries you need to use emscripten or twr-wasm.
The second problem is that all the function calls between your Wasm module and your javascript are limited to parameters and return values that are numbers (integer and float). No strings, arrays, struct pointers, etc. (for more on this see this doc).
The third problem is that legacy C code or games often block, and when written this way they don't naturally integrate with the JavaScript asynchronous programming model.
twr-wasm is a static C library (twr.a) that you can link to your clang C/C++ Wasm code, as well as a set of JavaScript/TypeScript modules that solve these issues.
In addition, twr-wasm provides APIs that you can use in your WebAssembly code - such as Canvas compatible 2D drawing APIs, a simple ANSI terminal emulator, character encoding support, and more.
"}]} \ No newline at end of file diff --git a/azure/examples/balls/balls-dbg.wasm b/azure/examples/balls/balls-dbg.wasm index e6684e84fb021b200737ecb9fe818e63e1cc554d..b497f25bf4bf443e999b1e1632fb9106cef1726e 100644 GIT binary patch delta 36710 zcmcG%33OCN&^CO#?@TglG9)u(vfq$>BY}jFum_NRUqyCM5fl|rSp)-!2#T@{*eFOq zkVQn$sDOf^fTALzqJpBJxZr|-qM)LFPj%m$Nqpb)o&TKwd>oy0RaaG4cU4zc_w76L z+QI0@4@U2}%GuG--+Vhe7TTt_WoJX@^fhcrXpt?M9SS{Vdy(x5jkZ^^T{Y|NYZ!>y zM^0l$YhI4Lo}tEYX5q85voG5bdeoVQ&zI%r=koJkXG4DBTqw?E(4PwRXQ=u!%T)mC zr(8u;^_lC&Ub|~%&(_keG4venPwQlKGOlT%?RWvWN-eFk(Ya7l1YHzC*GiVw&FE@$ zn~>Js=x%fg)AlfWfHt()odWsKxJjp@?p)s}2$PAwR IUF5nI7ivad@1{HVtfmf$toWv8=fAxi&hPROCT2R^`-RWJMmY-$
zizeN3@5JFFwoh}gglO?t5^EK4u!;L|#%5|7(=_cuocKJ6`NZWUX3x@Q#%WqpfBpae
z2X1?OGHagOc0w}^Q+}8aO7QQ)2=$jf`LQfb165;AyFN5t)AU6SO|#o WxY59o0M1CqJd~pxkAv!SoWlH%cI*Hq#&H0j
zv5wDx_dC7=KF+Zhvhj{X_)KuL1wPU7ZZ7^LIc~;hvZDtCQXJVR8;%oTN_BjX&osx)
z_)K@q5HA++yIG7d3UPS4Nen3DX{=C8p+7yu)AXlG?82W2x(3K@N)1CZkl?-~E*0`Y
z_<4=4MZ7gzCmt@6&{st~tLwAKDcJ;YC7$(CX&UxMfV}b*$q7Z!;EBhx;W6lTdQEv9
zZLcRE0~-DGGzd|Mv6$1g>bph%VqTaa8D{^f7%20uWmqXzf&tYZD#nI3OMFv|pKn M;G}SHqDW
zA0b&%ec+$-t6ydm{GQ2_nfzqoR*-)a=XgNfLwQ$==1>&i!|G)EG*r=AH`nZeY5n
z4LcOr=}BORYEF2lig(7B&g`?Ok
zHbR7rWEjagB#-W+mkOapVYx{wi47xGUoufyq`P#1E{
z7RR8vT&iAgsy3wR^`>gSRBb_Csmy{L6GWRk5zjiJW7%`2xNVM@c_*6LQM?TrntAn3
zRuMsAElCS6)U;eIT)&78cd??D3YiAvJ%r5Cw8Z|Jb`@jE(GPNea3@wC6>>TPlcL#G
z?jN#Gyl@vw%Bz433w;VC2NvjsG)xxc3rARHp=&{NVhBfzYj?5K=;>fdm;9>$&cWxaD{y#TfC;+c)CypoPj788fQ
Zp8xPq*=zVPbShT
zOXoZJ(6X5|#((&}lqpsPQh}f5S}T?l-