From 38d6e0f6d9eb9935b8a4422cf7b72db1bed1a25d Mon Sep 17 00:00:00 2001 From: Patbox <39821509+Patbox@users.noreply.github.com> Date: Mon, 23 Dec 2024 12:25:28 +0100 Subject: [PATCH] Switch to Chicory Wasm runtime with (aot experiment) --- build.gradle | 7 +- gradle.properties | 2 +- .../haykam821/consolebox/game/GameCanvas.java | 174 ++++++++++-------- .../haykam821/consolebox/game/GameMemory.java | 33 ++-- .../consolebox/mixin/MemoryAccessor.java | 13 ++ .../consolebox/mixin/MemoryTypeAccessor.java | 14 -- .../consolebox/mixin/ParserAccessor.java | 15 ++ src/main/resources/consolebox.mixin.json | 5 +- 8 files changed, 144 insertions(+), 119 deletions(-) create mode 100644 src/main/java/io/github/haykam821/consolebox/mixin/MemoryAccessor.java delete mode 100644 src/main/java/io/github/haykam821/consolebox/mixin/MemoryTypeAccessor.java create mode 100644 src/main/java/io/github/haykam821/consolebox/mixin/ParserAccessor.java diff --git a/build.gradle b/build.gradle index 52a3acd..240a949 100644 --- a/build.gradle +++ b/build.gradle @@ -31,8 +31,11 @@ dependencies { // Plasmid modImplementation("xyz.nucleoid:plasmid:${project.plasmid_version}") - // Wasmtime - include implementation("io.github.kawamuray.wasmtime:wasmtime-java:${project.wasmtime_version}") + // Chicory + include implementation("com.dylibso.chicory:runtime:${project.chicory_version}") + include implementation("com.dylibso.chicory:aot-experimental:${project.chicory_version}") + compileOnly("com.dylibso.chicory:host-module-annotations-experimental:${project.chicory_version}") + annotationProcessor("com.dylibso.chicory:host-module-processor-experimental:${project.chicory_version}") } processResources { diff --git a/gradle.properties b/gradle.properties index efad267..b19d9c3 100644 --- a/gradle.properties +++ b/gradle.properties @@ -11,4 +11,4 @@ loader_version = 0.16.9 fabric_version = 0.112.0+1.21.4 plasmid_version = 0.6.2+1.21.4 -wasmtime_version = 0.18.0 +chicory_version = 1.0.0-M2 diff --git a/src/main/java/io/github/haykam821/consolebox/game/GameCanvas.java b/src/main/java/io/github/haykam821/consolebox/game/GameCanvas.java index a343eae..4062eeb 100644 --- a/src/main/java/io/github/haykam821/consolebox/game/GameCanvas.java +++ b/src/main/java/io/github/haykam821/consolebox/game/GameCanvas.java @@ -1,5 +1,14 @@ package io.github.haykam821.consolebox.game; +import com.dylibso.chicory.experimental.aot.AotMachine; +import com.dylibso.chicory.experimental.hostmodule.annotations.HostModule; +import com.dylibso.chicory.experimental.hostmodule.annotations.WasmExport; +import com.dylibso.chicory.runtime.*; +import com.dylibso.chicory.wasm.ChicoryException; +import com.dylibso.chicory.wasm.MalformedException; +import com.dylibso.chicory.wasm.Parser; +import com.dylibso.chicory.wasm.WasmModule; +import com.dylibso.chicory.wasm.types.ValueType; import eu.pb4.mapcanvas.api.core.*; import eu.pb4.mapcanvas.api.font.DefaultFonts; import eu.pb4.mapcanvas.api.utils.CanvasUtils; @@ -10,28 +19,27 @@ import io.github.haykam821.consolebox.game.audio.TonePan; import io.github.haykam821.consolebox.game.palette.GamePalette; import io.github.haykam821.consolebox.game.render.FramebufferRendering; -import io.github.kawamuray.wasmtime.Module; -import io.github.kawamuray.wasmtime.WasmFunctionError.I32ExitError; -import io.github.kawamuray.wasmtime.WasmFunctionError.TrapError; -import io.github.kawamuray.wasmtime.*; -import io.github.kawamuray.wasmtime.WasmFunctions.Consumer0; +import io.github.haykam821.consolebox.mixin.ParserAccessor; import net.fabricmc.loader.api.FabricLoader; import net.minecraft.item.FilledMapItem; import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.MathHelper; import net.minecraft.util.math.Vec3d; -import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.imageio.ImageIO; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.util.ArrayList; import java.util.List; -import java.util.function.Supplier; + +@HostModule("env") public class GameCanvas { private static final Logger LOGGER = LoggerFactory.getLogger("GameCanvas"); @@ -59,37 +67,46 @@ private static CanvasImage readImage(String path) { private static final int SECTION_HEIGHT = 6; private static final int SECTION_WIDTH = 8; - private static final Consumer0 EMPTY_CALLBACK = () -> { - }; + private static final ExportFunction EMPTY_CALLBACK = (long... longs) -> new long[0]; private static final int DRAW_OFFSET_X = (SECTION_WIDTH * 64 - HardwareConstants.SCREEN_WIDTH / 2); private static final int DRAW_OFFSET_Y = (SECTION_HEIGHT * 64 - HardwareConstants.SCREEN_HEIGHT / 2); private final ConsoleBoxConfig config; - private final Store store; private final GameMemory memory; private final GamePalette palette; private final CombinedPlayerCanvas canvas; - private final WasmFunctions.Consumer0 startCallback; - private WasmFunctions.Consumer0 updateCallback; + private final ExportFunction startCallback; + private ExportFunction updateCallback; private final AudioController audioController; private SaveHandler saveHandler = SaveHandler.NO_OP; public GameCanvas(ConsoleBoxConfig config, AudioController audioController) { + + this.config = config; this.audioController = audioController; - this.store = Store.withoutData(); - this.memory = new GameMemory(this.store); + this.memory = new GameMemory(); - Engine engine = this.store.engine(); + var importBuilder = ImportValues.builder(); + this.defineImports(importBuilder); + WasmModule.Builder moduleBuilder = WasmModule.builder(); + try { + var parser = new Parser(); + parser.parse(new ByteArrayInputStream(config.getGameData()), (s) -> ParserAccessor.callOnSection(moduleBuilder, s)); + } catch (Throwable e) { + throw new RuntimeException(e); + } + moduleBuilder.setMemorySection(null); + var module = moduleBuilder.build(); - Linker linker = new Linker(this.store.engine()); - this.defineImports(linker); - Module module = new Module(engine, config.getGameData()); - linker.module(this.store, "", module); + Instance instance = Instance.builder(module) + .withImportValues(importBuilder.build()) + .withMachineFactory(AotMachine::new) + .build(); this.palette = new GamePalette(this.memory); this.canvas = DrawableCanvas.create(SECTION_WIDTH, SECTION_HEIGHT); @@ -137,50 +154,23 @@ public GameCanvas(ConsoleBoxConfig config, AudioController audioController) { DefaultFonts.VANILLA.drawText(this.canvas, text, DRAW_OFFSET_X - 78, DRAW_OFFSET_Y + HardwareConstants.SCREEN_HEIGHT - 59, 8, CanvasColor.BLACK_HIGH); DefaultFonts.VANILLA.drawText(this.canvas, text, DRAW_OFFSET_X - 79, DRAW_OFFSET_Y + HardwareConstants.SCREEN_HEIGHT - 60, 8, CanvasColor.WHITE_HIGH); - this.startCallback = this.getCallback(linker, "start"); - this.updateCallback = this.getCallback(linker, "update"); + this.startCallback = this.getCallback(instance, "start"); + this.updateCallback = this.getCallback(instance, "update"); } - private void defineImport(Linker linker, String name, Func func) { - Extern extern = Extern.fromFunc(func); - linker.define(this.store, "env", name, extern); - } - - private void defineImports(Linker linker) { - this.defineImport(linker, "blit", WasmFunctions.wrap(this.store, WasmValType.I32, WasmValType.I32, WasmValType.I32, WasmValType.I32, WasmValType.I32, WasmValType.I32, this::blit)); - this.defineImport(linker, "blitSub", WasmFunctions.wrap(this.store, WasmValType.I32, WasmValType.I32, WasmValType.I32, WasmValType.I32, WasmValType.I32, WasmValType.I32, WasmValType.I32, WasmValType.I32, WasmValType.I32, this::blitSub)); - - this.defineImport(linker, "line", WasmFunctions.wrap(this.store, WasmValType.I32, WasmValType.I32, WasmValType.I32, WasmValType.I32, this::line)); - this.defineImport(linker, "hline", WasmFunctions.wrap(this.store, WasmValType.I32, WasmValType.I32, WasmValType.I32, this::hline)); - this.defineImport(linker, "vline", WasmFunctions.wrap(this.store, WasmValType.I32, WasmValType.I32, WasmValType.I32, this::vline)); - - this.defineImport(linker, "oval", WasmFunctions.wrap(this.store, WasmValType.I32, WasmValType.I32, WasmValType.I32, WasmValType.I32, this::oval)); - this.defineImport(linker, "rect", WasmFunctions.wrap(this.store, WasmValType.I32, WasmValType.I32, WasmValType.I32, WasmValType.I32, this::rect)); - - this.defineImport(linker, "text", WasmFunctions.wrap(this.store, WasmValType.I32, WasmValType.I32, WasmValType.I32, this::text)); - this.defineImport(linker, "textUtf8", WasmFunctions.wrap(this.store, WasmValType.I32, WasmValType.I32, WasmValType.I32, WasmValType.I32, this::textUtf8)); - this.defineImport(linker, "textUtf16", WasmFunctions.wrap(this.store, WasmValType.I32, WasmValType.I32, WasmValType.I32, WasmValType.I32, this::textUtf16)); - - this.defineImport(linker, "tone", WasmFunctions.wrap(this.store, WasmValType.I32, WasmValType.I32, WasmValType.I32, WasmValType.I32, this::tone)); - - this.defineImport(linker, "diskr", WasmFunctions.wrap(this.store, WasmValType.I32, WasmValType.I32, WasmValType.I32, this::diskr)); - this.defineImport(linker, "diskw", WasmFunctions.wrap(this.store, WasmValType.I32, WasmValType.I32, WasmValType.I32, this::diskw)); - - this.defineImport(linker, "trace", WasmFunctions.wrap(this.store, WasmValType.I32, this::trace)); - this.defineImport(linker, "traceUtf8", WasmFunctions.wrap(this.store, WasmValType.I32, WasmValType.I32, this::traceUtf8)); - this.defineImport(linker, "traceUtf16", WasmFunctions.wrap(this.store, WasmValType.I32, WasmValType.I32, this::traceUtf16)); - this.defineImport(linker, "tracef", WasmFunctions.wrap(this.store, WasmValType.I32, WasmValType.I32, this::tracef)); - - Extern memoryExtern = this.memory.createExtern(); - linker.define(this.store, "env", "memory", memoryExtern); + private void defineImports(ImportValues.Builder linker) { + linker.addFunction(toHostFunctions()); + linker.addMemory(new ImportMemory("env", "memory", this.memory.memory())); } // External functions - private void blit(int spriteAddress, int x, int y, int width, int height, int flags) { + @WasmExport("blit") + public void blit(int spriteAddress, int x, int y, int width, int height, int flags) { this.blitSub(spriteAddress, x, y, width, height, 0, 0, width, flags); } - private void blitSub(int spriteAddress, int x, int y, int width, int height, int sourceX, int sourceY, int stride, int flags) { + @WasmExport("blitSub") + public void blitSub(int spriteAddress, int x, int y, int width, int height, int sourceX, int sourceY, int stride, int flags) { ByteBuffer buffer = this.memory.getFramebuffer(); int drawColors = this.memory.readDrawColors(); boolean bpp2 = (flags & 1) > 0; @@ -191,14 +181,16 @@ private void blitSub(int spriteAddress, int x, int y, int width, int height, int FramebufferRendering.drawSprite(buffer, drawColors, this.memory.getBuffer(), spriteAddress, x, y, width, height, sourceX, sourceY, stride, bpp2, flipX, flipY, rotate); } - private void line(int x1, int y1, int x2, int y2) { + @WasmExport("line") + public void line(int x1, int y1, int x2, int y2) { ByteBuffer buffer = this.memory.getFramebuffer(); int drawColors = this.memory.readDrawColors(); FramebufferRendering.drawLine(buffer, drawColors, x1, y1, x2, y2); } - private void hline(int x, int y, int length) { + @WasmExport("hline") + public void hline(int x, int y, int length) { byte strokeColor = (byte) (this.memory.readDrawColors() & 0b1111); if (strokeColor != 0) { @@ -210,7 +202,8 @@ private void hline(int x, int y, int length) { } } - private void vline(int x, int y, int length) { + @WasmExport("vline") + public void vline(int x, int y, int length) { if (y + length <= 0 || x < 0 || x >= HardwareConstants.SCREEN_HEIGHT) { return; } @@ -232,14 +225,16 @@ private void vline(int x, int y, int length) { } } - private void oval(int x, int y, int width, int height) { + @WasmExport("oval") + public void oval(int x, int y, int width, int height) { ByteBuffer buffer = this.memory.getFramebuffer(); int drawColors = this.memory.readDrawColors(); FramebufferRendering.drawOval(buffer, drawColors, x, y, width, height); } - private void rect(int x, int y, int width, int height) { + @WasmExport("rect") + public void rect(int x, int y, int width, int height) { ByteBuffer buffer = this.memory.getFramebuffer(); int drawColors = this.memory.readDrawColors(); @@ -256,19 +251,23 @@ private void drawText(byte[] string, int x, int y) { FramebufferRendering.drawText(buffer, drawColors, string, x, y); } - private void text(int string, int x, int y) { + @WasmExport("text") + public void text(int string, int x, int y) { this.drawText(this.memory.readStringRaw(string), x, y); } - private void textUtf8(int string, int length, int x, int y) { + @WasmExport("textUtf8") + public void textUtf8(int string, int length, int x, int y) { this.drawText(this.memory.readUnterminatedStringRaw8(string, length), x, y); } - private void textUtf16(int string, int length, int x, int y) { + @WasmExport("textUtf16") + public void textUtf16(int string, int length, int x, int y) { this.drawText(this.memory.readUnterminatedStringRaw16LE(string, length), x, y); } - private void tone(int frequency, int duration, int volume, int flags) { + @WasmExport("tone") + public void tone(int frequency, int duration, int volume, int flags) { var channel = switch (flags & 0b11) { case 0 -> AudioChannel.PULSE_1; case 1 -> AudioChannel.PULSE_2; @@ -304,7 +303,8 @@ private void tone(int frequency, int duration, int volume, int flags) { // Intentionally empty as sound is unsupported } - private int diskr(int address, int size) { + @WasmExport("diskr") + public int diskr(int address, int size) { if (!this.saveHandler.canUse()) { return 0; } @@ -318,7 +318,8 @@ private int diskr(int address, int size) { return 0; } - private int diskw(int address, int size) { + @WasmExport("diskw") + public int diskw(int address, int size) { if (!this.saveHandler.canUse()) { return 0; } @@ -332,22 +333,30 @@ private int diskw(int address, int size) { return 0; } - private void trace(int string) { + @WasmExport("trace") + public void trace(int string) { LOGGER.trace("From game: {}", this.memory.readString(string)); } - private void traceUtf8(int string, int length) { + @WasmExport("traceUtf8") + public void traceUtf8(int string, int length) { LOGGER.trace("From game: {}", this.memory.readUnterminatedString(string, length, StandardCharsets.UTF_8)); } - private void traceUtf16(int string, int length) { + @WasmExport("traceUtf16") + public void traceUtf16(int string, int length) { LOGGER.trace("From game: {}", this.memory.readUnterminatedString(string, length, StandardCharsets.UTF_16LE)); } - private void tracef(int string, int stack) { + @WasmExport("tracef") + public void tracef(int string, int stack) { LOGGER.trace("From game (unformatted): {}", this.memory.readString(string)); } + private HostFunction[] toHostFunctions() { + return GameCanvas_ModuleFactory.toHostFunctions(this); + } + // Behavior private void update() { if (!this.memory.readSystemPreserveFramebuffer()) { @@ -357,7 +366,7 @@ private void update() { } } - this.updateCallback.accept(); + this.updateCallback.apply(); } public void render() { @@ -417,7 +426,8 @@ public void tick(long lastTime) { this.error = e; } } - //DefaultFonts.VANILLA.drawText(this.canvas, "TIME: +" + lastTime, 0, 0, 8, CanvasColor.RED_HIGH); + CanvasUtils.fill(this.canvas, DRAW_OFFSET_X, DRAW_OFFSET_Y - 18, DRAW_OFFSET_X + 80, DRAW_OFFSET_Y - 8, CanvasColor.BLACK_NORMAL); + DefaultFonts.VANILLA.drawText(this.canvas, "TIME: " + lastTime, DRAW_OFFSET_X + 2, DRAW_OFFSET_Y - 17, 8, CanvasColor.WHITE_HIGH); this.canvas.sendUpdates(); } } @@ -438,13 +448,13 @@ private void drawError(Throwable e) { String message1; String message2; - if (e instanceof TrapError trapError) { + if (e instanceof TrapException trapError) { message1 = "Execution error! (TRAP)"; - message2 = trapError.trap() != null ? trapError.trap().name() : ""; - } else if (e instanceof I32ExitError exitError) { + message2 = trapError.getMessage(); + } /*else if (e instanceof I32ExitError exitError) { message1 = "Execution error! (EXIT)"; message2 = "Exit code " + exitError.exitCode(); - } else { + } */else { message1 = "Runtime error!"; message2 = e.getMessage(); } @@ -479,7 +489,7 @@ private void drawError(Throwable e) { public void start() { synchronized (this) { try { - this.startCallback.accept(); + this.startCallback.apply(); this.palette.update(); this.render(); } catch (Throwable e) { @@ -514,9 +524,11 @@ public PlayerCanvas getCanvas() { return this.canvas; } - private Consumer0 getCallback(Linker linker, String name) { - return linker.get(this.store, "", name) - .map(extern -> WasmFunctions.consumer(this.store, extern.func())) - .orElse(EMPTY_CALLBACK); + private ExportFunction getCallback(Instance instance, String name) { + try { + return instance.export(name); + } catch (Throwable e) { + return EMPTY_CALLBACK; + } } } diff --git a/src/main/java/io/github/haykam821/consolebox/game/GameMemory.java b/src/main/java/io/github/haykam821/consolebox/game/GameMemory.java index 8b16198..1a077df 100644 --- a/src/main/java/io/github/haykam821/consolebox/game/GameMemory.java +++ b/src/main/java/io/github/haykam821/consolebox/game/GameMemory.java @@ -4,11 +4,9 @@ import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; -import io.github.haykam821.consolebox.mixin.MemoryTypeAccessor; -import io.github.kawamuray.wasmtime.Extern; -import io.github.kawamuray.wasmtime.Memory; -import io.github.kawamuray.wasmtime.MemoryType; -import io.github.kawamuray.wasmtime.Store; +import com.dylibso.chicory.runtime.Memory; +import com.dylibso.chicory.wasm.types.MemoryLimits; +import io.github.haykam821.consolebox.mixin.MemoryAccessor; public final class GameMemory { private static final int PALETTE_ADDRESS = 0x0004; @@ -25,24 +23,24 @@ public final class GameMemory { private static final int FRAMEBUFFER_SIZE = 6400; private final Memory memory; - private final ByteBuffer buffer; private final ByteBuffer framebuffer; - protected GameMemory(Store store) { - this.memory = GameMemory.createMemory(store, HardwareConstants.MEMORY_PAGES); - this.buffer = memory.buffer(store); + protected GameMemory() { + this.memory = GameMemory.createMemory(HardwareConstants.MEMORY_PAGES); + // Todo: fix it later + this.buffer = ((MemoryAccessor) (Object) this.memory).getBuffer(); this.framebuffer = this.buffer.slice(FRAMEBUFFER_ADDRESS, FRAMEBUFFER_SIZE); - this.initializeMemory(); + } - public ByteBuffer getBuffer() { - return this.buffer; + public Memory memory() { + return this.memory; } - public Extern createExtern() { - return Extern.fromMemory(this.memory); + public ByteBuffer getBuffer() { + return this.buffer; } public ByteBuffer getFramebuffer() { @@ -176,10 +174,7 @@ public String toString() { return "GameMemory{" + this.memory + "}"; } - private static Memory createMemory(Store store, int pages) { - MemoryType memoryType = new MemoryType(pages, false); - ((MemoryTypeAccessor) (Object) memoryType).setMaximum(pages); - - return new Memory(store, memoryType); + private static Memory createMemory(int pages) { + return new Memory(new MemoryLimits(pages, pages)); } } diff --git a/src/main/java/io/github/haykam821/consolebox/mixin/MemoryAccessor.java b/src/main/java/io/github/haykam821/consolebox/mixin/MemoryAccessor.java new file mode 100644 index 0000000..fc88d34 --- /dev/null +++ b/src/main/java/io/github/haykam821/consolebox/mixin/MemoryAccessor.java @@ -0,0 +1,13 @@ +package io.github.haykam821.consolebox.mixin; + +import com.dylibso.chicory.runtime.Memory; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +import java.nio.ByteBuffer; + +@Mixin(Memory.class) +public interface MemoryAccessor { + @Accessor + ByteBuffer getBuffer(); +} diff --git a/src/main/java/io/github/haykam821/consolebox/mixin/MemoryTypeAccessor.java b/src/main/java/io/github/haykam821/consolebox/mixin/MemoryTypeAccessor.java deleted file mode 100644 index b56eecf..0000000 --- a/src/main/java/io/github/haykam821/consolebox/mixin/MemoryTypeAccessor.java +++ /dev/null @@ -1,14 +0,0 @@ -package io.github.haykam821.consolebox.mixin; - -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.Mutable; -import org.spongepowered.asm.mixin.gen.Accessor; - -import io.github.kawamuray.wasmtime.MemoryType; - -@Mixin(value = MemoryType.class, remap = false) -public interface MemoryTypeAccessor { - @Accessor("maximum") - @Mutable - public void setMaximum(long maximum); -} diff --git a/src/main/java/io/github/haykam821/consolebox/mixin/ParserAccessor.java b/src/main/java/io/github/haykam821/consolebox/mixin/ParserAccessor.java new file mode 100644 index 0000000..ad91ccf --- /dev/null +++ b/src/main/java/io/github/haykam821/consolebox/mixin/ParserAccessor.java @@ -0,0 +1,15 @@ +package io.github.haykam821.consolebox.mixin; + +import com.dylibso.chicory.wasm.Parser; +import com.dylibso.chicory.wasm.WasmModule; +import com.dylibso.chicory.wasm.types.Section; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Invoker; + +@Mixin(Parser.class) +public interface ParserAccessor { + @Invoker + static void callOnSection(WasmModule.Builder module, Section s) { + throw new UnsupportedOperationException(); + } +} diff --git a/src/main/resources/consolebox.mixin.json b/src/main/resources/consolebox.mixin.json index fa030f3..58379ef 100644 --- a/src/main/resources/consolebox.mixin.json +++ b/src/main/resources/consolebox.mixin.json @@ -1,9 +1,10 @@ { "required": true, "package": "io.github.haykam821.consolebox.mixin", - "compatibilityLevel": "JAVA_8", + "compatibilityLevel": "JAVA_21", "mixins": [ - "MemoryTypeAccessor" + "MemoryAccessor", + "ParserAccessor" ], "injectors": { "defaultRequire": 1