Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 42 additions & 1 deletion .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -285,7 +285,7 @@ jobs:
run: echo 'FreeBSD VM check made optional because of random time outs'

arm-qemu:
name: 'Ubuntu 24.04 x64, DMD (AArch64 cross-compile via QEMU)'
name: 'AArch64 cross-compile via QEMU, WASM via wasmtime'
runs-on: ubuntu-latest
timeout-minutes: 30
env:
Expand Down Expand Up @@ -334,3 +334,44 @@ jobs:
run: |
source ~/dlang/*/activate
compiler/test/run.d arm

- name: Install wasmtime
run: |
curl https://wasmtime.dev/install.sh -sSf | bash -s -- --version v46.0.1
echo "$HOME/.wasmtime/bin" >> $GITHUB_PATH

# The test suite validates every generated module with wasm-validate, and
# errors out when it is missing. Distro packages predate `try_table`, so
# install an upstream release.
- name: Install wabt

Copy link
Copy Markdown
Contributor

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 validate

https://github.com/bytecodealliance/wasm-tools

run: |
curl -fsSL https://github.com/WebAssembly/wabt/releases/download/1.0.41/wabt-1.0.41-linux-x64.tar.gz | tar -xz -C "$HOME"
echo "$HOME/wabt-1.0.41/bin" >> $GITHUB_PATH

# Cache the pinned wasi-sdk sysroot tarball the `wasm` target downloads for
# libc.a. Keep the key's version in sync with WASI_SDK_VERSION in
# druntime/Makefile.
- name: Cache wasi-sysroot
uses: actions/cache@v4
with:
path: generated/wasm/wasi-sysroot-33.tar.gz
key: wasi-sysroot-33.0+m

- name: Check for the Phobos wasm target
id: phobos_wasm
run: |
if make -C ../phobos -n wasm > /dev/null 2>&1; then
echo "available=true" >> $GITHUB_OUTPUT
else
echo "available=false" >> $GITHUB_OUTPUT
fi

- name: Build WASM druntime and phobos
if: steps.phobos_wasm.outputs.available == 'true'
run: make -j$N -C ../phobos wasm

- name: Test WASM cross-compile and run via wasmtime
if: steps.phobos_wasm.outputs.available == 'true'
run: |
source ~/dlang/*/activate
compiler/test/run.d wasm
97 changes: 97 additions & 0 deletions changelog/dmd.wasm-target.dd
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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this be -os=wasi? Wasm is not an OS, only an architecture, where Emscripten and WASI are considered OSs (in the target-triple sense; wasm32-unknown-wasi).

Additionally, there are multiple releases of WASI. WASIp1, p2, and p3.

We support both p1 and p2 in LDC.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I find os to be a poorly defined concept from the compiler's perspective. I can't find any documentation on what it means exactly. In my mind there's 3 things the compiler needs to know from the user:

  • Executable file format (PE, ELF, WASM module)
  • ISA (X86, X86_64, Aarch64, WASM code)
  • Libraries linked to make druntime work (win32 libs, Glibc, wasi-libc)

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 -m32 and -m64. Some advances have been made to make dmd support cross compilation with -os and -target , but it's still in a half-baked state IIRC.

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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The 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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure wasm should be allowed to be specified as os at all. It is confusing.

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`)
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You also need to specify which version of WASI.

Define WASIp1 or WASIp2.

EDIT: I see you define WASIp1

I don't know if you want to try supporting both (as -os=wasip1 vs -os=wasip2; or just support the latest; technically WASIp3 is, but we only support wasip2 in LDC right now).


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
6 changes: 4 additions & 2 deletions compiler/include/dmd/target.h
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ struct TargetObjC

struct Target
{
typedef unsigned char OS;
typedef uint16_t OS;
enum
{
/* These are mutually exclusive; one and only one is set.
Expand All @@ -130,9 +130,10 @@ struct Target
OS_Solaris = 0x20,
OS_DragonFlyBSD = 0x40,
OS_Hurd = 0x80,
OS_WASM = 0x100,

// Combination masks
all = OS_linux | OS_Windows | OS_OSX | OS_OpenBSD | OS_FreeBSD | OS_Solaris | OS_DragonFlyBSD | OS_Hurd,
all = OS_linux | OS_Windows | OS_OSX | OS_OpenBSD | OS_FreeBSD | OS_Solaris | OS_DragonFlyBSD | OS_Hurd | OS_WASM,
Posix = OS_linux | OS_OSX | OS_OpenBSD | OS_FreeBSD | OS_Solaris | OS_DragonFlyBSD | OS_Hurd,
};

Expand All @@ -159,6 +160,7 @@ struct Target
d_bool isAArch64; // generate 64 bit Arm code
d_bool isX86_64; // generate 64 bit code for x86_64; true by default for 64 bit dmd
d_bool isX86; // generate 32 bit Intel x86 code
d_bool isWasm; // generate WebAssembly code
d_bool isLP64; // pointers are 64 bits

// Environmental
Expand Down
3 changes: 3 additions & 0 deletions compiler/ini/dragonflybsd/bin32/dmd.conf
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,6 @@ DFLAGS=-I%@P%/../../src/phobos -I%@P%/../../src/druntime/import -L-L%@P%/../lib3

[Environment64]
DFLAGS=-I%@P%/../../src/phobos -I%@P%/../../src/druntime/import -L-L%@P%/../lib64 -L--export-dynamic

[Environmentwasm32]
DFLAGS=-I%@P%/../../src/phobos -I%@P%/../../src/druntime/import -L-L%@P%/../libwasm32
3 changes: 3 additions & 0 deletions compiler/ini/dragonflybsd/bin64/dmd.conf
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,6 @@ DFLAGS=-I%@P%/../../src/phobos -I%@P%/../../src/druntime/import -L-L%@P%/../lib3

[Environment64]
DFLAGS=-I%@P%/../../src/phobos -I%@P%/../../src/druntime/import -L-L%@P%/../lib64 -L--export-dynamic

[Environmentwasm32]
DFLAGS=-I%@P%/../../src/phobos -I%@P%/../../src/druntime/import -L-L%@P%/../libwasm32
3 changes: 3 additions & 0 deletions compiler/ini/freebsd/bin32/dmd.conf
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,6 @@ DFLAGS=-I%@P%/../../src/phobos -I%@P%/../../src/druntime/import -L-L%@P%/../lib3

[Environment64]
DFLAGS=-I%@P%/../../src/phobos -I%@P%/../../src/druntime/import -L-L%@P%/../lib64 -L--export-dynamic

[Environmentwasm32]
DFLAGS=-I%@P%/../../src/phobos -I%@P%/../../src/druntime/import -L-L%@P%/../libwasm32
3 changes: 3 additions & 0 deletions compiler/ini/freebsd/bin64/dmd.conf
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,6 @@ DFLAGS=-fPIC -I%@P%/../../src/phobos -I%@P%/../../src/druntime/import -L-L%@P%/.

[Environment64]
DFLAGS=-fPIC -I%@P%/../../src/phobos -I%@P%/../../src/druntime/import -L-L%@P%/../lib64 -L--export-dynamic

[Environmentwasm32]
DFLAGS=-I%@P%/../../src/phobos -I%@P%/../../src/druntime/import -L-L%@P%/../libwasm32
3 changes: 3 additions & 0 deletions compiler/ini/hurd/bin32/dmd.conf
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,6 @@ DFLAGS=-I%@P%/../../src/phobos -I%@P%/../../src/druntime/import -L-L%@P%/../lib3

[Environment64]
DFLAGS=-I%@P%/../../src/phobos -I%@P%/../../src/druntime/import -L-L%@P%/../lib64 -L--export-dynamic -fPIC

[Environmentwasm32]
DFLAGS=-I%@P%/../../src/phobos -I%@P%/../../src/druntime/import -L-L%@P%/../libwasm32
3 changes: 3 additions & 0 deletions compiler/ini/hurd/bin64/dmd.conf
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,6 @@ DFLAGS=-I%@P%/../../src/phobos -I%@P%/../../src/druntime/import -L-L%@P%/../lib3

[Environment64]
DFLAGS=-I%@P%/../../src/phobos -I%@P%/../../src/druntime/import -L-L%@P%/../lib64 -L--export-dynamic -fPIC

[Environmentwasm32]
DFLAGS=-I%@P%/../../src/phobos -I%@P%/../../src/druntime/import -L-L%@P%/../libwasm32
3 changes: 3 additions & 0 deletions compiler/ini/linux/bin32/dmd.conf
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,6 @@ DFLAGS=-I%@P%/../../src/phobos -I%@P%/../../src/druntime/import -L-L%@P%/../lib3

[Environment64]
DFLAGS=-I%@P%/../../src/phobos -I%@P%/../../src/druntime/import -L-L%@P%/../lib64 -L--export-dynamic -fPIC

[Environmentwasm32]
DFLAGS=-I%@P%/../../src/phobos -I%@P%/../../src/druntime/import -L-L%@P%/../libwasm32
3 changes: 3 additions & 0 deletions compiler/ini/linux/bin64/dmd.conf
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,6 @@ DFLAGS=-I%@P%/../../src/phobos -I%@P%/../../src/druntime/import -L-L%@P%/../lib3

[Environment64]
DFLAGS=-I%@P%/../../src/phobos -I%@P%/../../src/druntime/import -L-L%@P%/../lib64 -L--export-dynamic -fPIC

[Environmentwasm32]
DFLAGS=-I%@P%/../../src/phobos -I%@P%/../../src/druntime/import -L-L%@P%/../libwasm32
3 changes: 3 additions & 0 deletions compiler/ini/openbsd/bin32/dmd.conf
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,6 @@ DFLAGS=-I%@P%/../../src/phobos -I%@P%/../../src/druntime/import -L-L%@P%/../lib3

[Environment64]
DFLAGS=-I%@P%/../../src/phobos -I%@P%/../../src/druntime/import -L-L%@P%/../lib64 -L--export-dynamic -fPIC

[Environmentwasm32]
DFLAGS=-I%@P%/../../src/phobos -I%@P%/../../src/druntime/import -L-L%@P%/../libwasm32
3 changes: 3 additions & 0 deletions compiler/ini/openbsd/bin64/dmd.conf
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,6 @@ DFLAGS=-I%@P%/../../src/phobos -I%@P%/../../src/druntime/import -L-L%@P%/../lib3

[Environment64]
DFLAGS=-I%@P%/../../src/phobos -I%@P%/../../src/druntime/import -L-L%@P%/../lib64 -L--export-dynamic -fPIC

[Environmentwasm32]
DFLAGS=-I%@P%/../../src/phobos -I%@P%/../../src/druntime/import -L-L%@P%/../libwasm32
3 changes: 3 additions & 0 deletions compiler/ini/osx/bin/dmd.conf
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,6 @@
[Environment]

DFLAGS=-I%@P%/../../src/phobos -I%@P%/../../src/druntime/import -L-L%@P%/../lib

[Environmentwasm32]
DFLAGS=-I%@P%/../../src/phobos -I%@P%/../../src/druntime/import -L-L%@P%/../libwasm32
3 changes: 3 additions & 0 deletions compiler/ini/solaris/bin32/dmd.conf
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,6 @@ DFLAGS=-I%@P%/../../src/phobos -I%@P%/../../src/druntime/import -L-L%@P%/../lib3

[Environment64]
DFLAGS=-I%@P%/../../src/phobos -I%@P%/../../src/druntime/import -L-L%@P%/../lib64 -L--export-dynamic -L-lsocket -L-lnsl

[Environmentwasm32]
DFLAGS=-I%@P%/../../src/phobos -I%@P%/../../src/druntime/import -L-L%@P%/../libwasm32
3 changes: 3 additions & 0 deletions compiler/ini/solaris/bin64/dmd.conf
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,6 @@ DFLAGS=-I%@P%/../../src/phobos -I%@P%/../../src/druntime/import -L-L%@P%/../lib3

[Environment64]
DFLAGS=-I%@P%/../../src/phobos -I%@P%/../../src/druntime/import -L-L%@P%/../lib64 -L--export-dynamic -L-lsocket -L-lnsl

[Environmentwasm32]
DFLAGS=-I%@P%/../../src/phobos -I%@P%/../../src/druntime/import -L-L%@P%/../libwasm32
3 changes: 3 additions & 0 deletions compiler/ini/windows/bin/sc.ini
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,6 @@ LIB=%@P%\..\lib32mscoff

[Environment64]
LIB=%@P%\..\lib64

[Environmentwasm32]
DFLAGS=%DFLAGS% "-L-L%@P%\..\libwasm32"
12 changes: 8 additions & 4 deletions compiler/src/build.d
Original file line number Diff line number Diff line change
Expand Up @@ -307,6 +307,9 @@ DFLAGS=-I%@P%/../../../../druntime/import -I%@P%/../../../../../phobos -L-L%@P%/

[Environment64]
DFLAGS=-I%@P%/../../../../druntime/import -I%@P%/../../../../../phobos -L-L%@P%/../../../../../phobos/generated/{OS}/{BUILD}/64{exportDynamic} -fPIC

[Environmentwasm32]
DFLAGS=-I%@P%/../../../../druntime/import -I%@P%/../../../../../phobos -L-L%@P%/../../../wasm/release/wasm32 -L-L%@P%/../../../../../phobos/generated/wasm/release/wasm32
`;
}

Expand Down Expand Up @@ -1511,11 +1514,11 @@ auto sourceFiles()
dmsc.d iasm/dmdx86.d iasm/dmdaarch64.d glue/package.d glue/e2ir.d glue/objc.d
glue/s2ir.d glue/tocsym.d glue/toctype.d glue/tocvdebug.d glue/todt.d glue/toir.d glue/toobj.d
"),
driver: fileArray(env["D"], "dinifile.d dmdparams.d lib/package.d lib/elf.d lib/mach.d lib/mscoff.d
link.d mars.d main.d sarif.d lib/scanelf.d lib/scanmach.d lib/scanmscoff.d timetrace.d vsoptions.d
driver: fileArray(env["D"], "dinifile.d dmdparams.d lib/package.d lib/elf.d lib/mach.d lib/mscoff.d lib/wasm.d
link.d mars.d main.d sarif.d lib/scanelf.d lib/scanmach.d lib/scanmscoff.d lib/scanwasm.d timetrace.d vsoptions.d
"),
frontend: fileArray(env["D"], "
access.d aggregate.d aliasthis.d argtypes_x86.d argtypes_sysv_x64.d argtypes_aarch64.d arrayop.d
access.d aggregate.d aliasthis.d argtypes_x86.d argtypes_sysv_x64.d argtypes_aarch64.d argtypes_wasm.d arrayop.d
arraytypes.d astenums.d ast_node.d astcodegen.d asttypename.d attrib.d attribsem.d blockexit.d builtin.d canthrow.d chkformat.d
cli.d clone.d compiler.d cond.d constfold.d cpreprocess.d ctfeexpr.d
ctorflow.d dcast.d dclass.d declaration.d delegatize.d denum.d deps.d dimport.d
Expand Down Expand Up @@ -1582,7 +1585,8 @@ auto sourceFiles()
pdata.d util2.d backconfig.d rtlsym.d ptrntab.d
dvarstats.d cgen.d barray.d cgcse.d elpicpie.d
dwarfeh.d dwarfdbginf.d cv8.d
machobj.d elfobj.d mscoffobj.d
machobj.d elfobj.d mscoffobj.d wasm/obj.d
wasm/enums.d wasm/util.d wasm/blocks.d wasm/codgen.d wasm/simd.d wasm/wat.d
x86/nteh.d x86/cgreg.d x86/cg87.d x86/cgxmm.d x86/disasm86.d
x86/cgcod.d x86/cod1.d x86/cod2.d x86/cod3.d x86/cod4.d x86/cod5.d
arm/disasmarm.d arm/instr.d arm/cod1.d arm/cod2.d arm/cod3.d arm/cod4.d
Expand Down
49 changes: 49 additions & 0 deletions compiler/src/dmd/argtypes_wasm.d
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;
}
Loading
Loading