Skip to content
Open
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
57 changes: 50 additions & 7 deletions web-image/README.md
Original file line number Diff line number Diff line change
@@ -1,33 +1,76 @@
# Web Image

Web Image is an experimental backend for Native Image that produces a
WebAssembly module from Java code.

## Prerequisites

Being part of Native Image, see the [Quick Start Guide](../docs/reference-manual/native-image/contribute/DevelopingNativeImage.md)
for all the prerequisites for building Native Image.

In addition, Web Image also uses `wasm-as` from
[binaryen](https://github.com/WebAssembly/binaryen) as its assembler.
Version 119 of `wasm-as` has to be available on your `PATH`.

<details>
<summary>Installation</summary>

Binaryen provides pre-built releases for all major platforms on
[GitHub](https://github.com/WebAssembly/binaryen/releases).

On MacOS, it is recommended to install through [Homebrew](https://brew.sh/), as
the pre-built binaries are not signed and may be quarantined by MacOS.

```bash
brew install binaryen
```
</details>

## Building

To build Web Image, run the `mx build` command in the `web-image` suite:

```bash
cd /path/to/graal
cd web-image
mx build
```

## Usage

Use this as you would regular the regular `native-image` tool, but with an
Use this as you would use the regular `native-image` tool, but with an
additional `--tool:svm-wasm` flag to enable the WebAssembly backend:

```bash
mx native-image --tool:svm-wasm -cp ... HelloWorld
```

This produces `helloworld.js` and `helloworld.js.wasm` in your working
directory. The JavaScript file is a wrapper that loads and runs the WebAssembly
code and can be run with [Node.js](https://nodejs.org/en) 22 or later:
directory. The `--tool:svm-wasm` flag should be the first argument if possible.
If any experimental options specific to the Wasm backend are used, they can
only be added after the `--tool:svm-wasm` flag.

The `--tool:svm-wasm` flag should be the first argument if possible. If any
experimental options specific to the Wasm backend are used, they can only be
added after the `--tool:svm-wasm` flag.
The JavaScript file is a wrapper that loads and runs the WebAssembly
code and can be run with [Node.js](https://nodejs.org/en) 22 or later:

```bash
$ node helloworld.js
# --experimental-wasm-exnref is only required for Node versions before 25
$ node --experimental-wasm-exnref helloworld.js
Hello World
```

## WebAssembly Features

The WebAssembly code generated by Web Image makes use of various WebAssembly
features from WebAssembly 3.0:

- [Garbage Collection](https://github.com/WebAssembly/gc)
- [Exception Handling](https://github.com/WebAssembly/exception-handling/blob/master/proposals/exception-handling/Exceptions.md)
- [Typed Function References](https://github.com/WebAssembly/function-references/blob/main/proposals/function-references/Overview.md)

Support for [WebAssembly 2.0](https://www.w3.org/TR/wasm-core-2/#release-20) is
generally assumed.

## Contributors

- Aleksandar Prokopec
Expand Down
7 changes: 4 additions & 3 deletions web-image/mx.web-image/mx_web_image.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@

_suite = mx.suite("web-image")

_web_image_js_engine_name = os.getenv("NODE_EXE", "node")
_web_image_js_engine_name = [os.getenv("NODE_EXE", "node"), "--experimental-wasm-exnref"]

# Name of GraalVm component defining the web-image macro
web_image_component = "web-image"
Expand All @@ -63,7 +63,7 @@
"web-image:WEBIMAGE_CLOSURE_SUPPORT",
"web-image:WEBIMAGE_GOOGLE_CLOSURE",
]
# Hosted options defined in the web-image-enterprise suite
# Hosted options defined in the web-image suite
# This list has to be kept in sync with the code (the 'webimageoptions' gate tag checks this)
# See also WebImageConfiguration.hosted_options
web_image_hosted_options = [
Expand Down Expand Up @@ -92,6 +92,7 @@
"GrowthTriggerThreshold=",
"HeapGrowthFactor=",
"ImageHeapObjectsPerFunction=",
"LegacyExceptions",
"JSComments=",
"JSRuntime=",
"LogFilter=",
Expand Down Expand Up @@ -489,7 +490,7 @@ def __init__(self):
def apply(self, config):
vm_args, main_class, main_class_args = config

vm_args += ["-Dwebimage.test.js=" + _web_image_js_engine_name]
vm_args += ["-Dwebimage.test.js=" + ",".join(_web_image_js_engine_name)]
vm_args += ["-Dwebimage.test.launcher=" + vm_web_image_path()]
vm_args += ["-Dwebimage.test.flags=" + ",".join(get_launcher_flags(WebImageConfiguration.test_cases))]
# If any of the arguments contains spaces and double quotes, on Windows it will add its own quotes around
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
import jdk.graal.compiler.debug.GraalError;

public class WebImageTestOptions {
public static final String JS_CMD = System.getProperty("webimage.test.js");
public static final List<String> JS_CMD = Arrays.asList(System.getProperty("webimage.test.js", ",").split(","));
private static final String LAUNCHER = System.getProperty("webimage.test.launcher");

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -191,8 +191,7 @@ public static RunResult executeTestProgram(Class<?> c, String[] args, int expect
}

public static RunResult runJS(String cmd, String[] arguments, int expectExitCode) {
List<String> invokeCmd = new ArrayList<>();
invokeCmd.add(WebImageTestOptions.JS_CMD);
List<String> invokeCmd = new ArrayList<>(WebImageTestOptions.JS_CMD);
invokeCmd.add(cmd);

if (arguments != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,6 @@
import com.oracle.svm.hosted.webimage.options.WebImageOptions.CompilerBackend;
import com.oracle.svm.hosted.webimage.util.BenchmarkLogger;
import com.oracle.svm.hosted.webimage.wasm.WebImageWasmLMJavaMainSupport;
import com.oracle.svm.hosted.webimage.wasm.codegen.BinaryenCompat;
import com.oracle.svm.hosted.webimage.wasmgc.WebImageWasmGCJavaMainSupport;
import com.oracle.svm.webimage.WebImageJSJavaMainSupport;
import com.oracle.svm.webimage.WebImageJavaMainSupport;
Expand Down Expand Up @@ -167,11 +166,6 @@ public int build(ImageClassLoader classLoader) {
// For the Wasm backends, turn off closure compiler
optionProvider.getHostedValues().put(WebImageOptions.ClosureCompiler, false);

if (backend == CompilerBackend.WASMGC && !optionProvider.getHostedValues().containsKey(BinaryenCompat.Options.UseBinaryen)) {
// For WasmGC backend, use binaryen by default
optionProvider.getHostedValues().put(BinaryenCompat.Options.UseBinaryen, true);
}

if (!optionProvider.getHostedValues().containsKey(WebImageOptions.NamingConvention)) {
// The naming convention does not affect the binary image (unless debug information
// is embedded) and the REDUCED mode makes the text file a lot easier to read
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ public class StackifierData implements ReconstructionData {
*/
private HIRBlock[] blocks;

private ControlFlowGraph cfg;

/**
* Mapping from a basic block to its index in {@link #blocks}.
*/
Expand Down Expand Up @@ -92,6 +94,10 @@ public HIRBlock[] getBlocks() {
return blocks;
}

public ControlFlowGraph getCfg() {
return cfg;
}

public EconomicMap<HIRBlock, Scope> getEnclosingScope() {
return enclosingScope;
}
Expand Down Expand Up @@ -134,6 +140,7 @@ public void setLabeledBlockEnd(EconomicMap<HIRBlock, LabeledBlock> labeledBlockE

public void setSortedBlocks(HIRBlock[] sortedBlocks, ControlFlowGraph cfg) {
this.blocks = sortedBlocks;
this.cfg = cfg;
this.blockIndexSortOrder = new BlockMap<>(cfg);
for (int i = 0; i < sortedBlocks.length; ++i) {
this.blockIndexSortOrder.put(sortedBlocks[i], i);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@ public class WebImageWasmOptions {
"Has no effect on code size, the binary format does not have comments.")//
public static final EnumOptionKey<CommentVerbosity> WasmComments = new EnumOptionKey<>(CommentVerbosity.NORMAL);

@Option(help = "Enable the legacy exception proposal using try-catch instead of try_table") //
public static final HostedOptionKey<Boolean> LegacyExceptions = new HostedOptionKey<>(false);

@Option(help = "Assemble the Wasm binary file with debug names.")//
public static final HostedOptionKey<Boolean> DebugNames = new HostedOptionKey<>(false) {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -148,14 +148,33 @@ public abstract static class WasmBlock extends Instruction {
* If set to null, the block has no named identifier
*/
protected final WasmId.Label label;
protected final WasmValType result;

public WasmBlock(WasmId.Label label) {
protected WasmBlock(WasmId.Label label, WasmValType result) {
this.label = label;
this.result = result;
}

public WasmId.Label getLabel() {
return label;
}

public WasmValType getResult() {
return result;
}

public boolean hasResult() {
return result != null;
}

@Override
protected String toInnerString() {
if (hasResult()) {
return "() -> " + result;
} else {
return "() -> ()";
}
}
}

/**
Expand All @@ -165,7 +184,11 @@ public static final class Block extends WasmBlock {
public final Instructions instructions = new Instructions();

public Block(WasmId.Label label) {
super(label);
this(label, null);
}

public Block(WasmId.Label label, WasmValType result) {
super(label, result);
}
}

Expand All @@ -176,7 +199,7 @@ public static final class Loop extends WasmBlock {
public final Instructions instructions = new Instructions();

public Loop(WasmId.Label label) {
super(label);
super(label, null);
}
}

Expand All @@ -190,7 +213,7 @@ public static final class If extends WasmBlock {
public final Instruction condition;

public If(WasmId.Label label, Instruction condition) {
super(label);
super(label, null);
this.condition = condition;
}

Expand All @@ -200,10 +223,50 @@ public boolean hasElse() {
}

/**
* The WASM try block from the exception handling proposal.
* The WASM try_table from the exception handling proposal.
* <p>
* Ref: https://github.com/WebAssembly/exception-handling
*/
public static final class TryTable extends WasmBlock {
/**
* A catch clause for a certain tag.
* <p>
* When an exception is caught, the block branches to the label of the appropriate clause.
*/
public static final class Catch {
public final WasmId.Tag tag;
public final WasmId.Label label;

private Catch(WasmId.Tag tag, WasmId.Label label) {
this.tag = tag;
this.label = label;
}

@Override
public String toString() {
return "Catch{tag=" + tag + ", label=" + label + '}';
}
}

public final Instructions instructions = new Instructions();
public final List<Catch> catchBlocks = new ArrayList<>();

public TryTable(WasmId.Label label) {
super(label, null);
}

public void addCatch(WasmId.Tag tag, WasmId.Label catchLabel) {
var catchBlock = new Catch(tag, catchLabel);
catchBlocks.add(catchBlock);
}
}

/**
* The WASM try block from the legacy exception handling proposal.
* <p>
* Ref:
* https://github.com/WebAssembly/exception-handling/blob/master/proposals/exception-handling/legacy/Exceptions.md
*/
public static final class Try extends WasmBlock {

/**
Expand All @@ -222,7 +285,7 @@ private Catch(WasmId.Tag tag) {
public final List<Catch> catchBlocks = new ArrayList<>();

public Try(WasmId.Label label) {
super(label);
super(label, null);
}

public Instructions addCatch(WasmId.Tag tag) {
Expand Down Expand Up @@ -546,6 +609,11 @@ public static Const forWord(WordBase word) {
// TODO GR-42105 Use forInt
return forLong(word.rawValue());
}

@Override
protected String toInnerString() {
return literal.type + ", " + literal.asText();
}
}

/**
Expand Down
Loading