-
-
Notifications
You must be signed in to change notification settings - Fork 719
Add WebAssembly code generation #23584
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
72f2c2b
07ffd44
8599b10
5ab76dc
ce28726
0a9c876
583e729
332a0f7
3681021
6b736ca
fa4aedd
19f7895
1b285b0
0864cee
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,97 @@ | ||
| WebAssembly target support added to DMD | ||
|
|
||
| DMD now recognizes WebAssembly as a compilation target via the `-mwasm32` and `-os=wasm` flags. | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Shouldn't this be Additionally, there are multiple releases of WASI. WASIp1, p2, and p3. We support both p1 and p2 in LDC.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I find
Usually these go hand in hand, and historically DMD would just decide these based on the platform you run it on, only being able to choose So does the compiler need to care about WASIp1, p2, p3 if druntime uses Posix/C apis, or is it up to the user to configure version identifiers and linker commands for that? If there are subtle but important differences in the bindings then I guess the compiler needs that knowledge, but it still sounds weird to make each library an 'os'. Perhaps the documentation should clarify that this is a misnomer, and really just a toggle deciding which (version of) runtime libraries to use.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Each of these WASIp* is a separate OS in the target-triple sense in LDC/Clang, and requires linking against separate libraries/sysroots. LDC handles setting all the correct versions per "OS". There are also differences to Phobos (and maybe DRuntime too...I can't remember) between WASI previews, so they need to be built separately. There's also some minor header differences between different WASI snapshots (reflecting differences in e.g. networking, thread, etc. support) How does DMD handle Musl vs Glibc? |
||
| This can be used to run D code in web browsers, or in local sandbox environments such as `wasmtime`. | ||
| See also: https://github.com/jcbhmr/awesome-webassembly-runtimes | ||
|
|
||
| With wasm-ld and wasmtime installed, and added to PATH (or set in the `WASMTIME` environment variable), try: | ||
|
|
||
| ```D | ||
| import core.stdc.stdio; | ||
|
|
||
| void main() | ||
| { | ||
| printf("Hello WebAssembly!"); | ||
| } | ||
| ``` | ||
|
|
||
| $(CONSOLE | ||
| $ dmd -mwasm32 -os=wasm hello.d | ||
| $ wasmtime run hello.wasm | ||
| ) | ||
|
|
||
| Or: | ||
|
|
||
| $(CONSOLE | ||
| $ dmd -mwasm32 -os=wasm -run hello.d | ||
| ) | ||
|
|
||
| WebAssembly is an architecture, not an operating system, so `-os=wasm` is really | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure Other aliases look good though. |
||
| a shorthand for "the WebAssembly System Interface, preview 1" (`wasm32-wasip1` in | ||
| target triple terms). `-os=wasi` and `-os=wasip1` are accepted as synonyms. Only | ||
| WASI preview 1 is supported; preview 2 replaces the preview 1 syscalls with the | ||
| component model and would need its own `-os` value. | ||
|
|
||
| Version identifiers describing the architecture: | ||
| $(UL | ||
| $(LI `WebAssembly`) | ||
| $(LI `LittleEndian`) | ||
| $(LI `D_SIMD`) | ||
| ) | ||
|
|
||
| Version identifiers describing the platform: | ||
| $(UL | ||
| $(LI `WASI`) | ||
| $(LI `WASIp1`) | ||
| $(LI `Posix`, since `wasi-libc` implements a subset of Posix on top of WASI) | ||
| $(LI `CRuntime_WASI`) | ||
| $(LI `CppRuntime_LLVM`, `CppRuntime_Clang`) | ||
| ) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
EDIT: I see you define WASIp1 I don't know if you want to try supporting both (as |
||
|
|
||
| The .wasm should be binary compatible with ldc2. | ||
|
|
||
| To specify the module of exported symbols, use the `wasmImportModule` attribute: | ||
|
|
||
| ```D | ||
| import core.attribute : wasmImportModule; | ||
| @wasmImportModule("wasi_snapshot_preview1") extern (C) void proc_exit(int code); | ||
|
|
||
| // LDC way: | ||
| import ldc.attributes; | ||
| @llvmAttr("wasm-import-module", "wasi_snapshot_preview1") | ||
| extern (C) void proc_exit(int code); | ||
| ``` | ||
|
|
||
| To export a function to the host under a chosen name, use the `wasmExportName` | ||
| attribute. The function is kept by the linker and exported without | ||
| `--export-dynamic`, and the export name may differ from the symbol name: | ||
|
|
||
| ```D | ||
| import core.attribute : wasmExportName; | ||
| @wasmExportName("windowStep") extern (C) void internalStep() { } | ||
|
|
||
| // LDC way: | ||
| import ldc.attributes; | ||
| @llvmAttr("wasm-export-name", "windowStep") | ||
| extern (C) void internalStep() { } | ||
| ``` | ||
|
|
||
| Phobos is linked by default, just like on the native targets. Building it | ||
| mirrors the native library: `make -C phobos wasm` produces `libphobos2-wasm.a` | ||
| (which, like `libphobos2.a`, also contains druntime) in | ||
| `phobos/generated/wasm/release/wasm32`, and `dmd.conf` points `-mwasm32` at it. | ||
| As usual, `-defaultlib=` selects a different library, and an empty | ||
| `-defaultlib=` opts out of the runtime entirely. | ||
|
|
||
| ```D | ||
| import std.stdio; | ||
|
|
||
| void main() | ||
| { | ||
| writeln("hello from ", "wasm"); | ||
| } | ||
| ``` | ||
|
|
||
| Limitations: | ||
| - No deprecated complex number types like `cfloat` and `ifloat` | ||
| - `real` is 64-bit, not 128-bit | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,49 @@ | ||
| /** | ||
| * Break down a D type into basic types for the WebAssembly ABI. | ||
| * | ||
| * Copyright: Copyright (C) 1999-2026 by The D Language Foundation, All Rights Reserved | ||
| * Authors: $(LINK2 https://www.digitalmars.com, Walter Bright) | ||
| * License: $(LINK2 https://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) | ||
| * Source: $(LINK2 https://github.com/dlang/dmd/blob/master/compiler/src/dmd/argtypes_wasm.d, _argtypes_wasm.d) | ||
| * Documentation: https://dlang.org/phobos/dmd_argtypes_wasm.html | ||
| * Coverage: https://codecov.io/gh/dlang/dmd/src/master/compiler/src/dmd/argtypes_wasm.d | ||
| */ | ||
|
|
||
| module dmd.argtypes_wasm; | ||
|
|
||
| import dmd.astenums; | ||
| import dmd.mtype; | ||
| import dmd.typesem; | ||
| import dmd.target : target; | ||
|
|
||
| /**************************************************** | ||
| * Break down a D type into basic types for WebAssembly ABI. | ||
| * | ||
| * Params: | ||
| * t = type to break down | ||
| * Returns: | ||
| * For non-aggregate types or small aggregates: returns the type itself | ||
| * For large aggregates: returns empty (pass by reference) | ||
| */ | ||
| TypeTuple toArgTypes_wasm(Type t) | ||
| { | ||
| if (t == Type.terror) | ||
| return new TypeTuple(t); | ||
|
|
||
| Type tb = t.toBasetype(); | ||
|
|
||
| // void and zero-sized types are not passed | ||
| if (tb.ty == Tvoid || t.size() == 0) | ||
| return null; | ||
|
|
||
| // Scalars and 128-bit vectors are single values (a vector maps to a wasm | ||
| // v128 passed/returned by value, like LDC's -O0 SIMD ABI) | ||
| if (tb.isTypeBasic() || tb.isTypePointer() || tb.ty == Tvector || | ||
| tb.ty == Tclass || tb.ty == Taarray || tb.ty == Tnull || tb.ty == Tfunction) | ||
| return new TypeTuple(t); | ||
|
|
||
| // Aggregates (slices, delegates, structs, static arrays) are passed by | ||
| // reference. Returning empty makes Target.isReturnOnStack() route the | ||
| // return through a hidden sret pointer instead of packing it into a value | ||
| return TypeTuple.empty; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Another option would be to use
wasm-tools validatehttps://github.com/bytecodealliance/wasm-tools