From 5c87063c487ea29933f28311384489816bf5b303 Mon Sep 17 00:00:00 2001 From: Aubrey Taylor Date: Tue, 25 Feb 2025 00:43:18 -0600 Subject: [PATCH] Initial changes (mostly working) --- .cargo/config.toml | 2 + .envrc | 1 + .gitignore | 126 +++ LICENSE.txt | 2 + build.gradle.kts | 115 +++ flake.lock | 113 +++ flake.nix | 72 ++ gradle.properties | 16 + gradle/wrapper/gradle-wrapper.properties | 1 + rust-toolchain.toml | 3 + rust/Cargo.lock | 842 ++++++++++++++++++ rust/Cargo.toml | 24 + rust/build.gradle.kts | 20 + rust/src/component/mmio.rs | 251 ++++++ rust/src/component/mod.rs | 120 +++ rust/src/computer.rs | 202 +++++ rust/src/core.rs | 235 +++++ rust/src/lib.rs | 86 ++ rust/src/memory_map.rs | 3 + rust/src/unsync_cell.rs | 137 +++ settings.gradle.kts | 10 + .../ca/sanae/golemcomputers/GolemComputers.kt | 61 ++ .../sanae/golemcomputers/blocks/GolemBlock.kt | 59 ++ .../golemcomputers/blocks/GolemBlockEntity.kt | 44 + .../client/GolemComputersClient.kt | 8 + .../sanae/golemcomputers/computer/Computer.kt | 62 ++ .../sanae/golemcomputers/computer/IMachine.kt | 5 + .../golemcomputers/computer/RustNative.kt | 32 + .../computer/components/ChatComponent.kt | 29 + .../computer/components/Component.kt | 31 + src/main/resources/fabric.mod.json | 29 + .../resources/golem_computers.mixins.json | 11 + 32 files changed, 2752 insertions(+) create mode 100644 .cargo/config.toml create mode 100644 .envrc create mode 100644 .gitignore create mode 100644 LICENSE.txt create mode 100644 build.gradle.kts create mode 100644 flake.lock create mode 100644 flake.nix create mode 100644 gradle.properties create mode 100644 gradle/wrapper/gradle-wrapper.properties create mode 100644 rust-toolchain.toml create mode 100644 rust/Cargo.lock create mode 100644 rust/Cargo.toml create mode 100644 rust/build.gradle.kts create mode 100644 rust/src/component/mmio.rs create mode 100644 rust/src/component/mod.rs create mode 100644 rust/src/computer.rs create mode 100644 rust/src/core.rs create mode 100644 rust/src/lib.rs create mode 100644 rust/src/memory_map.rs create mode 100644 rust/src/unsync_cell.rs create mode 100644 settings.gradle.kts create mode 100644 src/main/kotlin/ca/sanae/golemcomputers/GolemComputers.kt create mode 100644 src/main/kotlin/ca/sanae/golemcomputers/blocks/GolemBlock.kt create mode 100644 src/main/kotlin/ca/sanae/golemcomputers/blocks/GolemBlockEntity.kt create mode 100644 src/main/kotlin/ca/sanae/golemcomputers/client/GolemComputersClient.kt create mode 100644 src/main/kotlin/ca/sanae/golemcomputers/computer/Computer.kt create mode 100644 src/main/kotlin/ca/sanae/golemcomputers/computer/IMachine.kt create mode 100644 src/main/kotlin/ca/sanae/golemcomputers/computer/RustNative.kt create mode 100644 src/main/kotlin/ca/sanae/golemcomputers/computer/components/ChatComponent.kt create mode 100644 src/main/kotlin/ca/sanae/golemcomputers/computer/components/Component.kt create mode 100644 src/main/resources/fabric.mod.json create mode 100644 src/main/resources/golem_computers.mixins.json diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 0000000..7520df8 --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,2 @@ +[env] +RUSTC_BOOTSTRAP = "1" diff --git a/.envrc b/.envrc new file mode 100644 index 0000000..8392d15 --- /dev/null +++ b/.envrc @@ -0,0 +1 @@ +use flake \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7f97f47 --- /dev/null +++ b/.gitignore @@ -0,0 +1,126 @@ +# User-specific stuff +/.idea + +*.iml +*.ipr +*.iws + +# IntelliJ +/out +# mpeltonen/sbt-idea plugin +/.idea_modules + +# JIRA plugin +atlassian-ide-plugin.xml + +# Compiled class file +*.class + +# Log file +*.log + +# BlueJ files +*.ctxt + +# Package Files # +*.jar +*.war +*.nar +*.ear +*.zip +*.tar.gz +*.rar + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* + +*~ + +# temporary files which can be created if a process still has a handle open of a deleted file +.fuse_hidden* + +# KDE directory preferences +.directory + +# Linux trash folder which might appear on any partition or disk +.Trash-* + +# .nfs files are created when an open file is removed but is still being accessed +.nfs* + +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +# Windows thumbnail cache files +Thumbs.db +Thumbs.db:encryptable +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +[Dd]esktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msix +*.msm +*.msp + +# Windows shortcuts +*.lnk + +/.gradle +/build + +# Ignore Gradle GUI config +gradle-app.setting + +# Cache of project +.gradletasknamecache + +**/build/ + +# Common working directory +/run +/runs + +# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) +!gradle-wrapper.jar + +# direnv +/.direnv + +# rust +/rust/target +/target diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..9e85bcc --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,2 @@ +Copyright (c) 2025 +All rights reserved. diff --git a/build.gradle.kts b/build.gradle.kts new file mode 100644 index 0000000..262875d --- /dev/null +++ b/build.gradle.kts @@ -0,0 +1,115 @@ +import org.jetbrains.kotlin.gradle.dsl.JvmTarget +import org.jetbrains.kotlin.gradle.tasks.KotlinCompile + +plugins { + kotlin("jvm") version "2.1.10" + id("fabric-loom") version "1.9-SNAPSHOT" + id("maven-publish") +} + +version = project.property("mod_version") as String +group = project.property("maven_group") as String + +base { + archivesName.set(project.property("archives_base_name") as String) +} + +val targetJavaVersion = 21 +java { + toolchain.languageVersion = JavaLanguageVersion.of(targetJavaVersion) + // Loom will automatically attach sourcesJar to a RemapSourcesJar task and to the "build" task + // if it is present. + // If you remove this line, sources will not be generated. + withSourcesJar() +} + +loom { + mods { + register("golemcomputers") { + sourceSet("main") + } + } +} + +repositories { + // Add repositories to retrieve artifacts from in here. + // You should only use this when depending on other mods because + // Loom adds the essential maven repositories to download Minecraft and libraries from automatically. + // See https://docs.gradle.org/current/userguide/declaring_repositories.html + // for more information about repositories. +} + +configurations { + create("rustlib") { + isCanBeConsumed = false + isCanBeResolved = true + } +} + + +dependencies { + // To change the versions see the gradle.properties file + minecraft("com.mojang:minecraft:${project.property("minecraft_version")}") + mappings("net.fabricmc:yarn:${project.property("yarn_mappings")}:v2") + modImplementation("net.fabricmc:fabric-loader:${project.property("loader_version")}") + modImplementation("net.fabricmc:fabric-language-kotlin:${project.property("kotlin_loader_version")}") + + modImplementation("net.fabricmc.fabric-api:fabric-api:${project.property("fabric_version")}") + add("rustlib", project(":rust")) + implementation("com.github.jhg023:Pbbl:1.0.2") +} + +tasks.processResources { + inputs.property("version", project.version) + inputs.property("minecraft_version", project.property("minecraft_version")) + inputs.property("loader_version", project.property("loader_version")) + filteringCharset = "UTF-8" + + from(configurations.getByName("rustlib")) + + filesMatching("fabric.mod.json") { + expand( + "version" to project.version, + "minecraft_version" to project.property("minecraft_version"), + "loader_version" to project.property("loader_version"), + "kotlin_loader_version" to project.property("kotlin_loader_version") + ) + } +} + +tasks.withType().configureEach { + // ensure that the encoding is set to UTF-8, no matter what the system default is + // this fixes some edge cases with special characters not displaying correctly + // see http://yodaconditions.net/blog/fix-for-java-file-encoding-problems-with-gradle.html + // If Javadoc is generated, this must be specified in that task too. + options.encoding = "UTF-8" + options.release.set(targetJavaVersion) +} + +tasks.withType().configureEach { + compilerOptions.jvmTarget.set(JvmTarget.fromTarget(targetJavaVersion.toString())) +} + +tasks.jar { + from("LICENSE") { + rename { "${it}_${project.base.archivesName}" } + } +} + +// configure the maven publication +publishing { + publications { + create("mavenJava") { + artifactId = project.property("archives_base_name") as String + from(components["java"]) + } + } + + // See https://docs.gradle.org/current/userguide/publishing_maven.html for information on how to set up publishing. + repositories { + // Add repositories to publish to here. + // Notice: This block does NOT have the same function as the block in the top level. + // The repositories here will be used for publishing your artifact, not for + // retrieving dependencies. + } +} diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..977ad7a --- /dev/null +++ b/flake.lock @@ -0,0 +1,113 @@ +{ + "nodes": { + "fenix": { + "inputs": { + "nixpkgs": [ + "nixpkgs" + ], + "rust-analyzer-src": "rust-analyzer-src" + }, + "locked": { + "lastModified": 1740119667, + "narHash": "sha256-8LsFuU6ORym0DUIqp0Kga95w5T+9UnN3aHaephEakvw=", + "owner": "nix-community", + "repo": "fenix", + "rev": "019d950c784141c8f3ba6ad5d8a53f72fee0953f", + "type": "github" + }, + "original": { + "owner": "nix-community", + "repo": "fenix", + "type": "github" + } + }, + "flake-parts": { + "inputs": { + "nixpkgs-lib": "nixpkgs-lib" + }, + "locked": { + "lastModified": 1738453229, + "narHash": "sha256-7H9XgNiGLKN1G1CgRh0vUL4AheZSYzPm+zmZ7vxbJdo=", + "owner": "hercules-ci", + "repo": "flake-parts", + "rev": "32ea77a06711b758da0ad9bd6a844c5740a87abd", + "type": "github" + }, + "original": { + "owner": "hercules-ci", + "repo": "flake-parts", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1739866667, + "narHash": "sha256-EO1ygNKZlsAC9avfcwHkKGMsmipUk1Uc0TbrEZpkn64=", + "owner": "nixos", + "repo": "nixpkgs", + "rev": "73cf49b8ad837ade2de76f87eb53fc85ed5d4680", + "type": "github" + }, + "original": { + "owner": "nixos", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs-lib": { + "locked": { + "lastModified": 1738452942, + "narHash": "sha256-vJzFZGaCpnmo7I6i416HaBLpC+hvcURh/BQwROcGIp8=", + "type": "tarball", + "url": "https://github.com/NixOS/nixpkgs/archive/072a6db25e947df2f31aab9eccd0ab75d5b2da11.tar.gz" + }, + "original": { + "type": "tarball", + "url": "https://github.com/NixOS/nixpkgs/archive/072a6db25e947df2f31aab9eccd0ab75d5b2da11.tar.gz" + } + }, + "root": { + "inputs": { + "fenix": "fenix", + "flake-parts": "flake-parts", + "nixpkgs": "nixpkgs", + "systems": "systems" + } + }, + "rust-analyzer-src": { + "flake": false, + "locked": { + "lastModified": 1740077634, + "narHash": "sha256-KlYdDhon/hy91NutuBeN8e3qTKf3FXgsudWsjnHud68=", + "owner": "rust-lang", + "repo": "rust-analyzer", + "rev": "88fbdcd510e79ef3bcd81d6d9d4f07bdce84be8c", + "type": "github" + }, + "original": { + "owner": "rust-lang", + "ref": "nightly", + "repo": "rust-analyzer", + "type": "github" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..64a4d5f --- /dev/null +++ b/flake.nix @@ -0,0 +1,72 @@ +{ + inputs = { + nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable"; + flake-parts.url = "github:hercules-ci/flake-parts"; + fenix = { + url = "github:nix-community/fenix"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + systems.url = "github:nix-systems/default"; + }; + + outputs = inputs: + inputs.flake-parts.lib.mkFlake {inherit inputs;} { + systems = import inputs.systems; + perSystem = { + config, + self', + pkgs, + lib, + system, + ... + }: let + fenix = inputs.fenix.packages.${system}; + libs = with pkgs; [ + (fenix.fromToolchainFile { + file = ./rust-toolchain.toml; + sha256 = "sha256-AJ6LX/Q/Er9kS15bn9iflkUwcgYqRQxiOIL2ToVAXaU="; + }) + fenix.rust-analyzer + wabt + maven + cmake + ninja + pkg-config + + #needed for minecraft to function properly + libGL + glfw-wayland-minecraft + libpulseaudio + openal + flite + ]; + mkDevshell = openIdea: + pkgs.mkShell { + nativeBuildInputs = with pkgs; [ + jetbrains.jdk + ]; + + buildInputs = libs; + LD_LIBRARY_PATH = lib.makeLibraryPath libs; + + env = { + JAVA_HOME = "${pkgs.jetbrains.jdk}/lib/openjdk/"; + }; + + shellHook = + if openIdea + then '' + idea-community ./ + exit + '' + else ""; + }; + in { + formatter = pkgs.alejandra; + devShells = { + default = mkDevshell false; + idea = mkDevshell true; + }; + }; + }; +} diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..c4c3c2a --- /dev/null +++ b/gradle.properties @@ -0,0 +1,16 @@ +# Done to increase the memory available to gradle. +org.gradle.jvmargs=-Xmx1G +# Fabric Properties +# check these on https://modmuss50.me/fabric.html +minecraft_version=1.21.4 +yarn_mappings=1.21.4+build.8 +loader_version=0.16.10 +kotlin_loader_version=1.13.1+kotlin.2.1.10 +# Mod Properties +mod_version=1.0-SNAPSHOT +maven_group=ca.sanae +archives_base_name=GolemComputers +# Dependencies +# check this on https://modmuss50.me/fabric.html +fabric_version=0.117.0+1.21.4 +chicory_version=1.0.0 \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..ab6f03b --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1 @@ +distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip diff --git a/rust-toolchain.toml b/rust-toolchain.toml new file mode 100644 index 0000000..ac293e1 --- /dev/null +++ b/rust-toolchain.toml @@ -0,0 +1,3 @@ +[toolchain] +channel = "1.85" +profile = "complete" diff --git a/rust/Cargo.lock b/rust/Cargo.lock new file mode 100644 index 0000000..864f869 --- /dev/null +++ b/rust/Cargo.lock @@ -0,0 +1,842 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "async-channel" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89b47800b0be77592da0afd425cc03468052844aff33b84e33cc696f64e77b6a" +dependencies = [ + "concurrent-queue", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-executor" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30ca9a001c1e8ba5149f91a74362376cc6bc5b919d92d988668657bd570bdcec" +dependencies = [ + "async-task", + "concurrent-queue", + "fastrand", + "futures-lite", + "slab", +] + +[[package]] +name = "async-fs" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebcd09b382f40fcd159c2d695175b2ae620ffa5f3bd6f664131efff4e8b9e04a" +dependencies = [ + "async-lock", + "blocking", + "futures-lite", +] + +[[package]] +name = "async-io" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43a2b323ccce0a1d90b449fd71f2a06ca7faa7c54c2751f06c9bd851fc061059" +dependencies = [ + "async-lock", + "cfg-if", + "concurrent-queue", + "futures-io", + "futures-lite", + "parking", + "polling", + "rustix", + "slab", + "tracing", + "windows-sys 0.59.0", +] + +[[package]] +name = "async-lock" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff6e472cdea888a4bd64f342f09b3f50e1886d32afe8df3d663c01140b811b18" +dependencies = [ + "event-listener", + "event-listener-strategy", + "pin-project-lite", +] + +[[package]] +name = "async-net" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b948000fad4873c1c9339d60f2623323a0cfd3816e5181033c6a5cb68b2accf7" +dependencies = [ + "async-io", + "blocking", + "futures-lite", +] + +[[package]] +name = "async-process" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63255f1dc2381611000436537bbedfe83183faa303a5a0edaf191edef06526bb" +dependencies = [ + "async-channel", + "async-io", + "async-lock", + "async-signal", + "async-task", + "blocking", + "cfg-if", + "event-listener", + "futures-lite", + "rustix", + "tracing", +] + +[[package]] +name = "async-signal" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "637e00349800c0bdf8bfc21ebbc0b6524abea702b0da4168ac00d070d0c0b9f3" +dependencies = [ + "async-io", + "async-lock", + "atomic-waker", + "cfg-if", + "futures-core", + "futures-io", + "rustix", + "signal-hook-registry", + "slab", + "windows-sys 0.59.0", +] + +[[package]] +name = "async-task" +version = "4.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "autocfg" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" + +[[package]] +name = "bitfield-struct" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2be5a46ba01b60005ae2c51a36a29cfe134bcacae2dd5cedcd4615fbaad1494b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "bitflags" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f68f53c83ab957f72c32642f3868eec03eb974d1fb82e453128456482613d36" +dependencies = [ + "serde", +] + +[[package]] +name = "blocking" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "703f41c54fc768e63e091340b424302bb1c29ef4aa0c7f10fe849dfb114d29ea" +dependencies = [ + "async-channel", + "async-task", + "futures-io", + "futures-lite", + "piper", +] + +[[package]] +name = "bytes" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f61dac84819c6588b558454b194026eb1f09c293b9036ae9b159e74e73ab6cf9" + +[[package]] +name = "cc" +version = "1.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c736e259eea577f443d5c86c304f9f4ae0295c43f3ba05c21f1d66b5f06001af" +dependencies = [ + "shlex", +] + +[[package]] +name = "cesu8" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "cmake" +version = "0.1.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7caa3f9de89ddbe2c607f4101924c5abec803763ae9534e4f4d7d8f84aa81f0" +dependencies = [ + "cc", +] + +[[package]] +name = "combine" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +dependencies = [ + "bytes", + "memchr", +] + +[[package]] +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "disarm64" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ccd32ba4e20b8b4a0db9c11d3a34a38ef03d729aa1d9ea4c9cd5dc6b29b5946" +dependencies = [ + "bitfield-struct", + "disarm64_defn", +] + +[[package]] +name = "disarm64_defn" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a968ec75d668b2e37898ba8b84d522e7261abde8721d0da8d877c2f51d911b2" +dependencies = [ + "bitflags", + "serde", + "strum", +] + +[[package]] +name = "errno" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" +dependencies = [ + "libc", + "windows-sys 0.59.0", +] + +[[package]] +name = "event-listener" +version = "5.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3492acde4c3fc54c845eaab3eed8bd00c7a7d881f78bfc801e43a93dec1331ae" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener-strategy" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c3e4e0dd3673c1139bf041f3008816d9cf2946bbfac2945c09e523b8d7b05b2" +dependencies = [ + "event-listener", + "pin-project-lite", +] + +[[package]] +name = "fastrand" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-io" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + +[[package]] +name = "futures-lite" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5edaec856126859abb19ed65f39e90fea3a9574b9707f13539acf4abf7eb532" +dependencies = [ + "fastrand", + "futures-core", + "futures-io", + "parking", + "pin-project-lite", +] + +[[package]] +name = "golem-computers" +version = "0.1.0" +dependencies = [ + "bitfield-struct", + "disarm64", + "jni", + "num-derive", + "num-traits", + "oneshot", + "pollster", + "smol", + "static_assertions", + "unicorn-engine", + "zerocopy", +] + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hermit-abi" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" + +[[package]] +name = "jni" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" +dependencies = [ + "cesu8", + "cfg-if", + "combine", + "jni-sys", + "log", + "thiserror", + "walkdir", + "windows-sys 0.45.0", +] + +[[package]] +name = "jni-sys" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" + +[[package]] +name = "libc" +version = "0.2.170" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "875b3680cb2f8f71bdcf9a30f38d48282f5d3c95cbf9b3fa57269bb5d5c06828" + +[[package]] +name = "linux-raw-sys" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" + +[[package]] +name = "log" +version = "0.4.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30bde2b3dc3671ae49d8e2e9f044c7c005836e7a023ee57cffa25ab82764bb9e" + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "num-derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "oneshot" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ce411919553d3f9fa53a0880544cda985a112117a0444d5ff1e870a893d6ea" + +[[package]] +name = "parking" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "piper" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96c8c490f422ef9a4efd2cb5b42b76c8613d7e7dfc1caf667b8a3350a5acc066" +dependencies = [ + "atomic-waker", + "fastrand", + "futures-io", +] + +[[package]] +name = "pkg-config" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" + +[[package]] +name = "polling" +version = "3.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a604568c3202727d1507653cb121dbd627a58684eb09a820fd746bee38b4442f" +dependencies = [ + "cfg-if", + "concurrent-queue", + "hermit-abi", + "pin-project-lite", + "rustix", + "tracing", + "windows-sys 0.59.0", +] + +[[package]] +name = "pollster" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f3a9f18d041e6d0e102a0a46750538147e5e8992d3b4873aaafee2520b00ce3" + +[[package]] +name = "proc-macro2" +version = "1.0.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rustix" +version = "0.38.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.59.0", +] + +[[package]] +name = "rustversion" +version = "1.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7c45b9784283f1b2e7fb61b42047c2fd678ef0960d4f6f1eba131594cc369d4" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "serde" +version = "1.0.218" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8dfc9d19bdbf6d17e22319da49161d5d0108e4188e8b680aef6299eed22df60" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.218" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f09503e191f4e797cb8aac08e9a4a4695c5edf6a2e70e376d961ddd5c969f82b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "signal-hook-registry" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" +dependencies = [ + "libc", +] + +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + +[[package]] +name = "smol" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a33bd3e260892199c3ccfc487c88b2da2265080acb316cd920da72fdfd7c599f" +dependencies = [ + "async-channel", + "async-executor", + "async-fs", + "async-io", + "async-lock", + "async-net", + "async-process", + "blocking", + "futures-lite", +] + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "strum" +version = "0.26.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "rustversion", + "syn", +] + +[[package]] +name = "syn" +version = "2.0.98" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36147f1a48ae0ec2b5b3bc5b537d267457555a10dc06f3dbc8cb11ba3006d3b1" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing" +version = "0.1.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +dependencies = [ + "pin-project-lite", + "tracing-core", +] + +[[package]] +name = "tracing-core" +version = "0.1.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" + +[[package]] +name = "unicode-ident" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00e2473a93778eb0bad35909dff6a10d28e63f792f16ed15e404fca9d5eeedbe" + +[[package]] +name = "unicorn-engine" +version = "2.1.1" +source = "git+https://github.com/Sanae6/unicorn/?branch=cpreg#31789c88932fc9a025d009e1f668b15d36fb28f9" +dependencies = [ + "bitflags", + "cc", + "cmake", + "libc", + "pkg-config", + "zerocopy", +] + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "winapi-util" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets 0.42.2", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "zerocopy" +version = "0.8.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dde3bb8c68a8f3f1ed4ac9221aad6b10cece3e60a8e2ea54a6a2dec806d0084c" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eea57037071898bf96a6da35fd626f4f27e9cee3ead2a6c703cf09d472b2e700" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] diff --git a/rust/Cargo.toml b/rust/Cargo.toml new file mode 100644 index 0000000..89627a2 --- /dev/null +++ b/rust/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "golem-computers" +version = "0.1.0" +edition = "2024" + +[lib] +crate-type = ["cdylib", "rlib"] + +[dependencies] +bitfield-struct = "0.10.1" +disarm64 = "0.1.24" +jni = "0.21.1" +num-derive = "0.4.2" +num-traits = "0.2.19" +oneshot = "0.1.10" +pollster = "0.4.0" +smol = "2.0.2" +static_assertions = "1.1.0" +unicorn-engine = { version = "2.1.1", default-features = false, features = ["arch_aarch64"], git = "https://github.com/Sanae6/unicorn/", branch = "cpreg" } +zerocopy = { version = "0.8.20", features = ["derive"] } + +[profile.release] +panic = "abort" +debug-assertions = true diff --git a/rust/build.gradle.kts b/rust/build.gradle.kts new file mode 100644 index 0000000..b501b7e --- /dev/null +++ b/rust/build.gradle.kts @@ -0,0 +1,20 @@ +val task = tasks.register("default") { + commandLine("cargo", "build", "--release") + val library = System.mapLibraryName("golem_computers") + val libraryPath = "$workingDir/target/release/$library" + outputs.file(libraryPath) + inputs.dir("$workingDir/src") + + doLast { + if (!file(libraryPath).exists()) { + throw GradleException("the library wasn't properly build! expected $libraryPath") + } + } +} + +configurations { + create("default") { + + } +} +artifacts.add("default", task) \ No newline at end of file diff --git a/rust/src/component/mmio.rs b/rust/src/component/mmio.rs new file mode 100644 index 0000000..1762c67 --- /dev/null +++ b/rust/src/component/mmio.rs @@ -0,0 +1,251 @@ +use std::{ + mem::offset_of, + ops::{Deref, DerefMut, Range}, + pin::Pin, + sync::{atomic::Ordering, Arc}, +}; + +use bitfield_struct::bitfield; +use jni::JavaVM; +use unicorn_engine::Unicorn; +use zerocopy::{ + big_endian::U16 as U16BE, + little_endian::{U16, U32, U64}, + FromBytes, Immutable, IntoBytes, KnownLayout, +}; + +use crate::{ + align_up, component::{Component, ComponentInterface}, core::CpuContext, memory_map::RAM_START, overlapping, range_of_field, unsync_cell::UnsyncCell, unsync_read +}; + +use super::ComponentDma; + +pub struct ComponentIoResult { + component_index: usize, + direction: ComponentIoDirection, +} + +pub enum ComponentIoDirection { + ToComponent, + ToGuest, +} + +impl Component { + pub fn perform_write( + &self, + range: Range, + writer: impl FnOnce(&UnsyncCell), + ram: Arc>, + java_vm: JavaVM, + ) -> Option>>> { + let Self::Populated { + read_transfer_active, + write_transfer_active, + info, + interface, + own_index, + java_component, + .. + } = self + else { + return None; + }; + + if overlapping(&range, &range_of_field!(ComponentInterface, info)) { + return None; + } + let read_range = range_of_field!(ComponentInterface, read_dma); + if overlapping(&range, &read_range) { + if read_transfer_active.load(Ordering::Acquire) || read_range != range { + return None; + } + + writer(interface); + + let dma: ComponentDma = unsync_read!(interface, ComponentInterface, read_dma); + if dma.dma_enabled() + && read_transfer_active.compare_exchange(false, true, Ordering::AcqRel, Ordering::Acquire).is_ok() + { + if !overlapping( + &(dma.address..dma.address + dma.size()), + &(RAM_START..RAM_START + ram.len() as u64), + ) { + read_transfer_active.store(false, Ordering::Release); + return None; + } + let bytes_per_chunk: u32 = info.bytes_per_tick / 5; + let java_component = + let index = return Some(Box::pin(async move { + let java_component = java_component; + ComponentIoResult { component_index: 0, direction: todo!() } + })); + } + + return None; + } + // let write_range = range_of_field!(ComponentInterface, write_dma); + // if write_transfer_active && overlapping(range.clone(), write_range) { + // return; + // } + + return None; + } +} + +#[derive(FromBytes, IntoBytes, Immutable, KnownLayout)] +#[repr(C)] +struct ComponentMmioHeader { + component_count: U32, + /// version bump when component access method (offset table) changes or [ComponentInfo] is non-additively changed + version: U16, + control: U16BE, +} + +#[bitfield(u16)] +#[derive(FromBytes, IntoBytes, Immutable, KnownLayout)] +pub struct ComponentMmioControl { + interrupt_added: bool, + interrupt_removed: bool, + #[bits(2)] + _reserved: U16, + /// read-write, indicates the core to fire interrupts on. indicating an invalid core means no core will receive the interrupt + #[bits(4)] + interrupt_firing_core: u8, + #[bits(8)] + _reserved: U16, +} + +pub fn calculate_components_start(component_count: usize) -> usize { + OFFSETS_START + align_up(component_count as u64 * 4 + 16, 0x1000) as usize +} + +fn read_bytes_to_u64(bytes: &[u8]) -> u64 { + match bytes.len() { + 1 => bytes[0] as u64, + 2 => U16::read_from_bytes(bytes).unwrap().get() as u64, + 4 => U32::read_from_bytes(bytes).unwrap().get() as u64, + 8 => U64::read_from_bytes(bytes).unwrap().get() as u64, + 16 => todo!("128 bit reads are currently neither implemented here nor supported by unicorn hooks"), + _ => panic!("expected byte slice of primitive size"), + } +} + +fn write_bytes_from_u64(bytes: &mut [u8], value: u64) { + match bytes.len() { + 1 => bytes[0] = value.try_into().unwrap(), + 2 => U16::new(value.try_into().unwrap()).write_to(bytes).unwrap(), + 4 => U32::new(value.try_into().unwrap()).write_to(bytes).unwrap(), + 8 => U64::new(value).write_to(bytes).unwrap(), + 16 => todo!("128 bit writes are currently neither implemented here nor supported by unicorn hooks"), + _ => panic!("expected byte slice of primitive size"), + } +} + +const OFFSETS_START: usize = 0x200; +const INTERFACE_INTERVAL: usize = 0x800; + +pub fn component_mmio_read(vcpu: &mut Unicorn, offset: u64, size: usize) -> u64 { + let offset = offset as usize; + let component_count = vcpu.get_data().components.len(); + if offset < OFFSETS_START { + // mmio header + println!("reading from mmio start!"); + let header = ComponentMmioHeader { + component_count: U32::new(component_count as u32), + version: U16::new(0), + control: U16BE::new(vcpu.get_data().component_control.read().into_bits()), + }; + if offset > size_of::() { + return 0; + } + let mut region = [0; size_of::() + size_of::()]; + header.write_to_prefix(&mut region).unwrap(); + let range = offset as usize..(offset + size) as usize; + return read_bytes_to_u64(®ion[range]); + } + + if offset < vcpu.get_data().component_data_start { + // offsets + let range = offset as usize..(offset + size) as usize; + let components_range = OFFSETS_START..(size_of::() * component_count); + + let range = range.start - components_range.start as usize..range.end - components_range.start as usize; + let first_offset_index = range.start >> 2; + let offset_buffer: [U32; 3] = std::array::from_fn(|index| { + (first_offset_index + index < component_count as usize) + .then_some(U32::new( + ((first_offset_index + index) * INTERFACE_INTERVAL as usize) as u32, + )) + .unwrap_or_default() + }); + + return read_bytes_to_u64(&offset_buffer.as_bytes()[range]); + } + + // info region + + let offset = offset - vcpu.get_data().component_data_start; + let index = offset / INTERFACE_INTERVAL as usize; + let offset = offset - (index * INTERFACE_INTERVAL as usize); + + let Some(Component::Populated { interface, .. }) = vcpu.get_data().components.get(index) else { + return 0; + }; + + match size { + 1 => interface.read_into::(offset).into(), + 2 => interface.read_into::(offset).into(), + 4 => interface.read_into::(offset).into(), + 8 => interface.read_into::(offset).into(), + _ => unreachable!(), + } +} + +pub fn component_mmio_write(vcpu: &mut Unicorn, offset: u64, size: usize, value: u64) { + let offset = offset as usize; + let range = offset..offset + size; + + if offset < OFFSETS_START { + if overlapping( + &range, + &(offset_of!(ComponentMmioHeader, control)..size_of::()), + ) { + // control included + let mut buffer = [0; size_of::() + size_of::()]; + write_bytes_from_u64(&mut buffer[range], value); + let header = ComponentMmioHeader::ref_from_prefix(&buffer).unwrap().0; + + vcpu.get_data().component_control.write(ComponentMmioControl::from_bits(header.control.get())); + } + + return; + } + + if offset < vcpu.get_data().component_data_start { + return; + } + + let offset = offset - vcpu.get_data().component_data_start; + let index = offset / INTERFACE_INTERVAL as usize; + let offset = offset - (index * INTERFACE_INTERVAL as usize); + + let Some(component) = vcpu.get_data().components.get(index) else { + return; + }; + + if offset > size_of::() { + return; + } + + component.perform_write( + range, + |interface| match size { + 1 => interface.write_from(offset, &(value as u8)), + 2 => interface.write_from(offset, &U16::new(value as u16)), + 4 => interface.write_from(offset, &U32::new(value as u32)), + 8 => interface.write_from(offset, &U64::new(value as u64)), + _ => unreachable!(), + }, + vcpu.get_data().ram, + ); +} diff --git a/rust/src/component/mod.rs b/rust/src/component/mod.rs new file mode 100644 index 0000000..9fd9c24 --- /dev/null +++ b/rust/src/component/mod.rs @@ -0,0 +1,120 @@ +pub mod mmio; + +use std::{ + cell::RefCell, + mem::offset_of, + ops::{Deref, DerefMut, Range, RangeBounds}, + pin::Pin, + rc::Rc, + sync::{ + atomic::{AtomicBool, Ordering}, + mpsc, Arc, LazyLock, Mutex, + }, +}; + +use bitfield_struct::bitfield; +use jni::objects::GlobalRef; +use unicorn_engine::Unicorn; +use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout}; + +use crate::{ + core::CpuContext, memory_map::RAM_START, overlapping, range_of_field, subtract_range, unsync_cell::UnsyncCell, + unsync_read, +}; + +#[derive(Default)] +pub enum Component { + #[default] + Empty, + Populated { + own_index: usize, + java_component: GlobalRef, + read_transfer_active: AtomicBool, + write_transfer_active: AtomicBool, + info: ComponentInfo, + interface: UnsyncCell, + }, +} + +#[derive(Clone, Copy, Default, FromBytes, IntoBytes, Immutable, KnownLayout)] +#[repr(C)] +pub struct ComponentInterface { + // read only + pub info: ComponentInfo, + + // read-write + control: ComponentControl, + _restart_device: u8, + _reserved: [u8; 6], + read_dma: ComponentDma, + write_dma: ComponentDma, +} + +#[bitfield(u8)] +#[derive(FromBytes, IntoBytes, Immutable, KnownLayout)] +pub struct ComponentControl { + interrupt_when_added: bool, + interrupt_when_removed: bool, + #[bits(2)] + _reserved: u8, + + #[bits(4)] + interrupt_firing_core: u8, +} + +#[derive(Clone, Copy, Default, FromBytes, IntoBytes, Immutable, KnownLayout)] +#[repr(C)] +pub struct ComponentDma { + pub upper_word: ComponentDmaUpper, + pub address: u64, +} + +impl Deref for ComponentDma { + type Target = ComponentDmaUpper; + + fn deref(&self) -> &Self::Target { + &self.upper_word + } +} + +impl DerefMut for ComponentDma { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.upper_word + } +} + +#[bitfield(u64)] +#[derive(FromBytes, IntoBytes, Immutable, KnownLayout)] +pub struct ComponentDmaUpper { + dma_enabled: bool, + #[bits(3)] + _reserved: u8, + /// read-write, indicates the core to fire interrupts on. indicating an invalid core means no core will receive the interrupt + #[bits(4)] + interrupt_firing_core: u8, + + #[bits(56)] + size: u64, +} + +#[derive(Clone, Copy, FromBytes, IntoBytes, Immutable, KnownLayout)] +#[repr(C)] +pub struct ComponentInfo { + /// a null terminated string, the type name of the component + pub type_name: [u8; 256], + /// a null terminated string, the protocol identifier for how to talk to the component + pub protocol: [u8; 256], + pub bytes_per_tick: u32, + pub version: u32, +} + +impl Default for ComponentInfo { + fn default() -> Self { + Self { + type_name: [0; 256], + protocol: [0; 256], + bytes_per_tick: 0, + version: 0, + } + } +} diff --git a/rust/src/computer.rs b/rust/src/computer.rs new file mode 100644 index 0000000..b0dcfb4 --- /dev/null +++ b/rust/src/computer.rs @@ -0,0 +1,202 @@ +use std::sync::{Arc, OnceLock, RwLock}; + +use jni::{ + objects::{JByteBuffer, JIntArray, JObject, JString, JValue}, + signature::{Primitive, ReturnType}, + strings::JNIString, + sys::{jint, jlong}, + JNIEnv, +}; + +use crate::{ + assert_positive, + component::{mmio::ComponentMmioControl, Component, ComponentInfo, ComponentInterface}, + core::CoreHandle, + unsync_cell::UnsyncCell, +}; + +pub struct Computer { + pub components: Arc<[RwLock]>, + pub component_control: Arc>, + pub shared_ram: Arc>, + pub shared_rom: Arc>, + + pub cores: OnceLock>, +} + +impl Computer { + #[unsafe(export_name = "Java_ca_sanae_golemcomputers_computer_Computer_new")] + extern "C" fn new<'local>( + mut env: JNIEnv<'local>, + java_computer: JObject<'local>, + component_capacity: i32, + memory_size: i32, + core_speeds: JIntArray<'local>, + ) { + let (component_capacity, memory_size): (usize, usize) = ( + assert_positive!(env, component_capacity), + assert_positive!(env, memory_size), + ); + + let core_count = env.get_array_length(&core_speeds).unwrap() as usize; + if core_count > 16 { + env.throw("no more than 16 cores are allowed").unwrap(); + return; + } + println!("got to computer construction!"); + let computer = Box::new(Self { + components: { + let mut components = Vec::with_capacity(component_capacity); + components.extend(std::iter::from_fn(|| Default::default()).take(component_capacity)); + Arc::from(components.into_boxed_slice()) + }, + component_control: Arc::new(UnsyncCell::new(ComponentMmioControl::new())), + shared_ram: Arc::from(UnsyncCell::new_zeroed_box(memory_size as usize)), + shared_rom: Arc::from(UnsyncCell::new_zeroed_box(0x1000000 as usize)), + // late initalized, see below + cores: OnceLock::new(), + }); + + println!("core construction"); + let mut speeds = [0; 16]; + env.get_int_array_region(core_speeds, 0, &mut speeds[..core_count]).unwrap(); + println!("core speeds acquired {:?}", &speeds[..core_count]); + let Ok(cores) = speeds + .into_iter() + .take(core_count) + .enumerate() + .map(|(core_index, speed)| { + Ok(CoreHandle::new( + &computer, + core_index, + assert_positive!(env, speed, Err(())), + env.get_java_vm().unwrap(), + )) + }) + .collect::, _>>() + else { + return; + }; + + let _ = computer.cores.set(cores.into_boxed_slice()); + + println!("storing computer info in field!"); + + env + .set_field( + &java_computer, + "address", + "J", + JValue::Long(Box::into_raw(computer) as jlong), + ) + .unwrap(); + } + + fn from_jni<'local>(env: &mut JNIEnv<'local>, computer: JObject<'local>) -> *mut Self { + env + .get_field_unchecked( + &computer, + (env.get_object_class(&computer).unwrap(), "address", "J"), + ReturnType::Primitive(Primitive::Long), + ) + .unwrap() + .j() + .unwrap() as *mut _ + } + + #[unsafe(export_name = "Java_ca_sanae_golemcomputers_computer_Computer_insertComponentNative")] + unsafe extern "C" fn insert_component<'local>( + mut env: JNIEnv<'local>, + computer: JObject<'local>, + component_index: jint, + component_object: JObject<'local>, + type_name: JString<'local>, + protocol: JString<'local>, + bytes_per_tick: u32, + version: u32, + ) { + // Safety: the responsibility of asserting the pointer is valid is on the java caller + let computer = unsafe { &*Self::from_jni(&mut env, computer) }; + + let component_index: usize = assert_positive!(env, component_index); + let mut make_info_string = |string: JString<'local>| { + let mut bytes = [0u8; 256]; + let jstring = env.get_string(&string).unwrap(); + + let length = bytes.len().min(jstring.to_bytes().len()); + bytes[..length].copy_from_slice(&jstring.to_bytes()[..length]); + + bytes + }; + let type_name = make_info_string(type_name); + let protocol = make_info_string(protocol); + let info = ComponentInfo { + type_name, + protocol, + bytes_per_tick, + version, + }; + let java_component = env.new_global_ref(component_object).unwrap(); + *computer.components[component_index].write().unwrap() = Component::Populated { + own_index: component_index, + java_component, + read_transfer_active: false.into(), + write_transfer_active: false.into(), + info, + interface: ComponentInterface::default().into(), + }; + } + + #[unsafe(export_name = "Java_ca_sanae_golemcomputers_computer_Computer_updateRomTest")] + unsafe extern "C" fn update_rom_test<'local>( + mut env: JNIEnv<'local>, + computer: JObject<'local>, + bytes: JByteBuffer<'local>, + ) { + // Safety: the responsibility of asserting the pointer is valid is on the java caller + let computer = unsafe { &*Self::from_jni(&mut env, computer) }; + + computer.shared_rom.update_from_jni(&mut env, &bytes).expect("failed to update rom"); + } + + #[unsafe(export_name = "Java_ca_sanae_golemcomputers_computer_Computer_updateRomFile")] + unsafe extern "C" fn update_rom_file<'local>(env: JNIEnv<'local>, computer: JObject<'local>) { + drop((env, computer)); + todo!() + } + + #[unsafe(export_name = "Java_ca_sanae_golemcomputers_computer_Computer_free")] + unsafe extern "C" fn free<'local>(mut env: JNIEnv<'local>, computer: JObject<'local>) { + let computer = Self::from_jni(&mut env, computer); + // Safety: the responsibility of asserting the pointer is valid is on the java caller + drop(unsafe { Box::from_raw(computer) }) + } + + #[unsafe(export_name = "Java_ca_sanae_golemcomputers_computer_Computer_wakeCoreAt")] + unsafe extern "C" fn wake_core_at<'local>( + mut env: JNIEnv<'local>, + computer: JObject<'local>, + core: jint, + address: jlong, + ) { + let (core, address): (usize, u64) = (assert_positive!(env, core), assert_positive!(env, address)); + // Safety: the responsibility of asserting the pointer is valid is on the java caller + let computer = unsafe { &*Self::from_jni(&mut env, computer) }; + + let Some(core) = computer.cores.get().unwrap().get(core) else { + env.throw("core index was out of bounds").unwrap(); + return; + }; + core.wake_at(address); + } + + #[unsafe(export_name = "Java_ca_sanae_golemcomputers_computer_Computer_tick")] + unsafe extern "C" fn tick<'local>(mut env: JNIEnv<'local>, computer: JObject<'local>) { + // Safety: the responsibility of asserting the pointer is valid is on the java caller + let computer = unsafe { &*Self::from_jni(&mut env, computer) }; + let cores = &computer.cores.get().unwrap(); + for core in cores.iter() { + core.tick(); + } + } +} diff --git a/rust/src/core.rs b/rust/src/core.rs new file mode 100644 index 0000000..d75ab58 --- /dev/null +++ b/rust/src/core.rs @@ -0,0 +1,235 @@ +use std::{ + cell::{OnceCell, RefCell}, + pin::Pin, + rc::Rc, + sync::{mpsc, Arc, RwLock}, +}; + +use disarm64::decoder; +use jni::{JNIEnv, JavaVM}; +use num_derive::FromPrimitive; +use num_traits::FromPrimitive; +use unicorn_engine::{Arch, ArmCpRegister, HookType, Mode, Permission, Query, RegisterARM64, Unicorn}; +use zerocopy::IntoBytes; + +use crate::{component::{mmio::{calculate_components_start, component_mmio_read, component_mmio_write, ComponentMmioControl}, Component, ComponentIoResult}, computer::Computer, unsync_cell::UnsyncCell}; + +pub struct CoreHandle { + message_sender: mpsc::Sender, +} + +/* + + + +todo REWRITEEEEEEE RAAAAH + + + + */ + +pub struct CpuContext<'a> { + component_data_start: usize, + components: Arc<[RwLock]>, + /// see comment on [Computer] + component_control: Arc>, + message_sender: mpsc::Sender, + component_access_futures: Vec>>>, + ram: Arc>, + stopped: bool, + _env: JNIEnv<'a>, +} + +enum CpuMessage { + Tick, + Wake(Option), + Sleep, +} + +impl CoreHandle { + pub fn new(computer: &Computer, core_index: usize, instructions_per_tick: u32, java_vm: JavaVM) -> Self { + let (components, component_control) = (computer.components.clone(), computer.component_control.clone()); + let (started_up_sender, started_up_receiver) = oneshot::channel(); + + let (message_sender, message_receiver) = mpsc::channel(); + let memories = (computer.shared_ram.clone(), computer.shared_rom.clone()); + + std::thread::spawn({ + let message_sender = message_sender.clone(); + + move || { + let env = java_vm.attach_current_thread_permanently().unwrap(); + + let context = CpuContext { + component_data_start: calculate_components_start(components.len()), + components, + component_control, + message_sender, + component_access_futures: Vec::new(), + ram: memories.0.clone(), + _env: env, + stopped: true, + }; + thread( + core_index, + instructions_per_tick, + memories, + context, + started_up_sender, + message_receiver, + ) + } + }); + + let _ = started_up_receiver.recv().expect("failed to start up core"); + + CoreHandle { message_sender } + } + + #[unsafe(export_name = "Java_ca_sanae_golemcomputers_computer_RustNative_coreFree")] + unsafe extern "C" fn free<'local>(_env: JNIEnv<'local>, core: *const Self) { + // Safety: the responsibility of verifying the pointer is valid is on the java caller + drop(unsafe { Arc::from_raw(core) }) + } + + pub fn tick(&self) { + self.message_sender.send(CpuMessage::Tick).expect("cpu thread panicked :("); + } + + pub fn wake_at(&self, start: u64) { + self.message_sender.send(CpuMessage::Wake(Some(start))).unwrap(); + } +} + +#[derive(Debug, FromPrimitive)] +enum Exception { + UndefinedInstruction = 1, + SupervisorCall = 2, + PrefetchAbort = 3, + DataAbort = 4, + Interrupt = 5, + FastInterrupt = 6, + Breakpoint = 7, + ExceptionExit = 8, + KernelTrap = 9, + HypervisorCall = 11, + HypervisorTrap = 12, + SecureMonitorCall = 13, + VirtualInterrupt = 14, + VirtualFastInterrupt = 15, + Semihost = 16, + Nocp = 17, + Invstate = 18, + Stkof = 19, + Lazyfp = 20, + Lserr = 21, + Unaligned = 22, +} + +fn thread( + core_index: usize, + instructions_per_tick: u32, + (shared_ram, shared_rom): (Arc>, Arc>), + context: CpuContext, + started_up_sender: oneshot::Sender<()>, + message_receiver: mpsc::Receiver, +) -> Result<(), ()> { + let mut vcpu = + Unicorn::new_with_data(Arch::ARM64, Mode::LITTLE_ENDIAN, context).expect("failed to spawn unicorn engine"); + + const MPIDR_EL1: ArmCpRegister = ArmCpRegister::from_manual(0b11, 0b000, 0b0000, 0b0000, 0b101); + vcpu.reg_write_cp(CP_REG, MPIDR_EL1, core_index as u64).unwrap(); + + vcpu + .mmio_map( + 0x2000_0000, + 0x1000, + Some(component_mmio_read), + Some(component_mmio_write), + ) + .expect("failed to map mmio for core"); + shared_rom + .map(&mut vcpu, 0x4000_0000, None, Permission::READ | Permission::EXEC) + .expect("failed to map memory for rom"); + shared_ram.map(&mut vcpu, 0x8000_0000, None, Permission::ALL).expect("failed to map memory for rom"); + use RegisterARM64::*; + + vcpu + .add_intr_hook(|vcpu, excp| { + println!("interrupt: {excp}"); + let exception = Exception::from_u32(excp).unwrap(); + + let pc = vcpu.pc_read().unwrap(); + println!("exception: {exception:?}"); + let mut opcode = 0u32; + if let Ok(_) = vcpu.mem_read(pc, opcode.as_mut_bytes()) { + if let Some(opcode) = decoder::decode(opcode) { + let mut inst = String::new(); + disarm64::format_insn::format_insn_pc(pc, &mut inst, &opcode).unwrap(); + println!("pc {pc:08X} {inst}"); + } else { + println!("pc {pc:08X} - op parse failed {opcode:08X}"); + } + } else { + println!("pc {pc:08X} - can't read"); + } + println!("x0 {:08X}", vcpu.reg_read(X0).unwrap()); + println!("x1 {:08X}", vcpu.reg_read(X1).unwrap()); + + let syndrome = vcpu.query(Query::SYNDROME).unwrap(); + println!("0x{syndrome:08X}"); + vcpu.get_data_mut().stopped = true; + vcpu.emu_stop().unwrap(); + }) + .unwrap(); + + vcpu + .add_mem_hook( + HookType::MEM_INVALID, + u64::MIN, + u64::MAX, + |vcpu, mem_type, address, _, value| { + eprintln!("memory exception: {mem_type:?} at {address:X} (value: {value:X})"); + + vcpu.emu_stop().unwrap(); + true + }, + ) + .unwrap(); + + started_up_sender.send(()).expect("panicked when trying to core"); + let try_fetch_message = |stopped| { + if stopped { + message_receiver.recv().map(Some).map_err(|_| ()) + } else { + Ok(message_receiver.try_recv().ok()) + } + }; + + let mut instructions_left = 0; + loop { + while let Some(message) = try_fetch_message(vcpu.get_data().stopped || instructions_left == 0)? { + match message { + CpuMessage::Tick => { + if vcpu.get_data().stopped { + continue; + } + } + CpuMessage::Wake(address) => { + if let Some(address) = address { + vcpu.set_pc(address).unwrap(); + println!("waking at {address}"); + } + vcpu.get_data_mut().stopped = false; + + let pc = vcpu.pc_read().unwrap(); + println!("executing {pc:X}"); + vcpu.emu_start(pc, 0, 0, instructions_per_tick as usize).expect("error while executing cpu"); + } + CpuMessage::Sleep => vcpu.get_data_mut().stopped = true, + } + } + vcpu.emu_start(vcpu.pc_read().unwrap(), 0, 0, instructions_left.min(1000)).expect("error while executing cpu"); + instructions_left = instructions_left.saturating_sub(1000); + } +} diff --git a/rust/src/lib.rs b/rust/src/lib.rs new file mode 100644 index 0000000..def964d --- /dev/null +++ b/rust/src/lib.rs @@ -0,0 +1,86 @@ +#![deny(unsafe_op_in_unsafe_fn)] +#![feature(sync_unsafe_cell)] +#![feature(generic_const_exprs)] + +use std::ops::Range; + +use jni::{ + sys::{jint, JNI_VERSION_1_8}, + JNIEnv, +}; + +pub mod component; +mod computer; +mod core; +pub mod unsync_cell; +mod memory_map; + +#[macro_export] +macro_rules! assert_positive { + ($env: expr, $value: expr) => {{ + let value = $value; + if value < 0 { + ($env) + .throw(format!( + "input {} from java cannot be negative! got {value}", + stringify!($value) + )) + .unwrap(); + return; + } + value as _ + }}; + ($env: expr, $value: expr, $err: expr) => {{ + let value = $value; + if value < 0 { + ($env) + .throw(format!( + "input {} from java cannot be negative! got {value}", + stringify!($value) + )) + .unwrap(); + return $err; + } + value as _ + }}; +} + +#[macro_export] +macro_rules! range_of_field { + ($type: ty, $field: ident) => {{ + const RANGE: std::ops::Range = { + let size = { + let v: std::mem::MaybeUninit<$type> = std::mem::MaybeUninit::uninit(); + size_of_val(unsafe { &v.assume_init_ref().$field }) + }; + + let offset = std::mem::offset_of!($type, $field); + + offset as _..(offset + size) as _ + }; + + let range: std::ops::Range<_> = RANGE.start as _..RANGE.end as _; + range + }}; +} + +fn overlapping(a: &Range, b: &Range) -> bool { + a.contains(&b.start) && a.contains(&b.end) +} + +fn subtract_range(a: &Range, b: &Range) -> Range { + a.start - b.start..a.end - b.end +} + +pub fn align_up(value: u64, alignment: u64) -> u64 { + (value + alignment - 1) & !(alignment - 1) +} + +struct UnicornWrap { + +} + +#[unsafe(no_mangle)] +pub extern "system" fn JNI_OnLoad<'local>(_: JNIEnv<'local>, _: usize) -> jint { + JNI_VERSION_1_8 +} diff --git a/rust/src/memory_map.rs b/rust/src/memory_map.rs new file mode 100644 index 0000000..db4b51c --- /dev/null +++ b/rust/src/memory_map.rs @@ -0,0 +1,3 @@ +pub const COMPONENTS_START: u64 = 0x2000_0000; +pub const ROM_START: u64 = 0x4000_0000; +pub const RAM_START: u64 = 0x8000_0000; diff --git a/rust/src/unsync_cell.rs b/rust/src/unsync_cell.rs new file mode 100644 index 0000000..9050b55 --- /dev/null +++ b/rust/src/unsync_cell.rs @@ -0,0 +1,137 @@ +use std::cell::SyncUnsafeCell; + +use jni::{objects::JByteBuffer, JNIEnv}; +use unicorn_engine::{uc_error, Permission, Unicorn}; +use zerocopy::{FromBytes, FromZeros, Immutable, IntoBytes, KnownLayout}; + +/// A simple replacement for [std::cell::Cell] in Sync contexts which explicitly does not try to synchronize the contents. +/// This is used for contexts where unsynchronized access is explicitly fine. +/// ## Dropping: +/// This type does not drop the value stored inside it if you write a new value to it. Generally the intention of the type is to store primitives and slices, +/// so this shouldn't be a problem for anyone, but **be warned**. +#[derive(Default, FromBytes, IntoBytes, KnownLayout)] +#[repr(transparent)] +pub struct UnsyncCell(SyncUnsafeCell); + +impl UnsyncCell { + pub fn as_mut_ptr(&self) -> *mut T { + self.0.get() + } +} + +impl UnsyncCell { + pub fn new(value: T) -> Self { + Self(SyncUnsafeCell::new(value)) + } + + pub fn write(&self, value: T) { + unsafe { self.as_mut_ptr().write(value) }; + } + + pub fn read(&self) -> T { + unsafe { self.as_mut_ptr().read() } + } +} + +impl UnsyncCell { + // N must be the same as the size of U + pub fn read_into(&self, offset: usize) -> U + where + [u8; size_of::()]:, + { + let mut buffer = [0; size_of::()]; + let end = (offset + size_of::()).min(size_of::()); + assert!(end - offset <= size_of::()); + // Safety: both pointers are valid for 0..(end - offset) + unsafe { (self.as_mut_ptr() as *const u8).copy_to(buffer.as_mut_ptr(), end - offset) }; + U::read_from_bytes(&buffer).expect("N was not the same as the size of U") + } +} + +impl UnsyncCell { + pub fn write_from(&self, offset: usize, value: &U) { + let end = (offset + size_of::()).min(size_of::()); + assert!(end - offset <= size_of::()); + unsafe { (self.as_mut_ptr() as *mut u8).copy_from(value.as_bytes().as_ptr(), end - offset) }; + } +} + +#[macro_export] +macro_rules! unsync_read { + ($value: expr, $T: ty, $field: ident) => { + $crate::unsync_cell::UnsyncCell::<$T>::read_into(&($value), std::mem::offset_of!($T, $field)) + }; +} + +impl UnsyncCell<[T]> { + pub fn new_zeroed_box(size: usize) -> Box { + let mut memory = Box::new_uninit_slice(size); + memory.zero(); + + // Safety: UnsafeCell transparently wraps the slice, meaning the two types are semantically identical + unsafe { std::mem::transmute::, Box>(memory.assume_init()) } + } + + fn as_mut_ptr(&self) -> *mut [T] { + self.0.get() + } + + pub fn len(&self) -> usize { + self.as_mut_ptr().len() + } + + pub fn update_from_slice(&self, bytes: &[u8]) -> Result<(), usize> { + if self.len() < bytes.len() { + return Err(bytes.len() - self.len()); + } + + // Safety: our slice is valid for bytes.len(), both are u8 pointers and therefore aligned + unsafe { + (self.as_mut_ptr() as *mut u8).copy_from(bytes.as_ptr(), bytes.len()); + }; + + Ok(()) + } + + pub fn update_from_jni(&self, env: &mut JNIEnv, byte_buffer: &JByteBuffer) -> Result<(), usize> { + let size = env.get_direct_buffer_capacity(&byte_buffer).expect("failed to get byte buffer size"); + if self.len() < size { + return Err(size - self.len()); + } + let address = env.get_direct_buffer_address(&byte_buffer).expect("failed to get byte buffer address"); + + // Safety: our slice is valid for bytes.len(), jni gives us a valid pointer for bytes.len(), both are u8 pointers and therefore aligned + unsafe { + (self.as_mut_ptr() as *mut u8).copy_from(address, size); + }; + + Ok(()) + } + + pub fn map( + &self, + vcpu: &mut Unicorn, + address: u64, + size: Option, + permissions: Permission, + ) -> Result<(), uc_error> { + let size = size.unwrap_or(self.len()); + if size > self.len() { + return Err(uc_error::ARG); + } + println!("mapping {permissions:?} at {address:08X} for {size:08X}"); + unsafe { vcpu.mem_map_ptr(address, size, permissions, self.as_mut_ptr() as *mut _) } + } +} + +impl Clone for UnsyncCell { + fn clone(&self) -> Self { + Self(SyncUnsafeCell::new(unsafe { self.0.get().read() })) + } +} + +impl From for UnsyncCell { + fn from(value: T) -> Self { + Self(value.into()) + } +} diff --git a/settings.gradle.kts b/settings.gradle.kts new file mode 100644 index 0000000..61d9d66 --- /dev/null +++ b/settings.gradle.kts @@ -0,0 +1,10 @@ +pluginManagement { + repositories { + maven("https://maven.fabricmc.net/") { + name = "Fabric" + } + gradlePluginPortal() + mavenCentral() + } +} +include("rust") diff --git a/src/main/kotlin/ca/sanae/golemcomputers/GolemComputers.kt b/src/main/kotlin/ca/sanae/golemcomputers/GolemComputers.kt new file mode 100644 index 0000000..1342ea9 --- /dev/null +++ b/src/main/kotlin/ca/sanae/golemcomputers/GolemComputers.kt @@ -0,0 +1,61 @@ +package ca.sanae.golemcomputers + +import ca.sanae.golemcomputers.blocks.GolemBlock +import ca.sanae.golemcomputers.blocks.GolemBlockEntity +import ca.sanae.golemcomputers.computer.RustNative +import com.mojang.serialization.Codec +import net.fabricmc.api.ModInitializer +import net.fabricmc.fabric.api.`object`.builder.v1.block.entity.FabricBlockEntityTypeBuilder +import net.minecraft.block.Block +import net.minecraft.block.entity.BlockEntity +import net.minecraft.block.entity.BlockEntityType +import net.minecraft.component.ComponentType +import net.minecraft.network.codec.PacketCodecs +import net.minecraft.registry.Registries +import net.minecraft.registry.Registry +import net.minecraft.registry.RegistryKey +import net.minecraft.registry.RegistryKeys +import net.minecraft.text.Text +import net.minecraft.text.TextCodecs +import net.minecraft.util.Identifier + + +class GolemComputers : ModInitializer { + companion object { + const val MOD_ID = "golem_computers" + + fun id(path: String): Identifier { + return Identifier.of(MOD_ID, path) + } + + val GOLEM_BLOCK_KEY: RegistryKey = RegistryKey.of(RegistryKeys.BLOCK, id("golem")) + val GOLEM_BLOCK = register(GOLEM_BLOCK_KEY, GolemBlock()) + val GOLEM_BLOCK_ENTITY = register(GOLEM_BLOCK_KEY, ::GolemBlockEntity, GOLEM_BLOCK) + + private fun register(key: RegistryKey, block: T): T { + return Registry.register(Registries.BLOCK, key, block) + } + + private fun register(key: RegistryKey>, componentType: ComponentType): ComponentType { + return Registry.register(Registries.DATA_COMPONENT_TYPE, key, componentType) + } + + private fun register( + key: RegistryKey, + entityFactory: FabricBlockEntityTypeBuilder.Factory, + vararg blocks: Block + ): BlockEntityType { + return Registry.register( + Registries.BLOCK_ENTITY_TYPE, + key.value, + FabricBlockEntityTypeBuilder.create(entityFactory, *blocks).build() + ) + } + } + + override fun onInitialize() { + if (!RustNative.isInitialized()) { + error("Rust binary failed to load!") + } + } +} diff --git a/src/main/kotlin/ca/sanae/golemcomputers/blocks/GolemBlock.kt b/src/main/kotlin/ca/sanae/golemcomputers/blocks/GolemBlock.kt new file mode 100644 index 0000000..d3cdabd --- /dev/null +++ b/src/main/kotlin/ca/sanae/golemcomputers/blocks/GolemBlock.kt @@ -0,0 +1,59 @@ +package ca.sanae.golemcomputers.blocks + +import ca.sanae.golemcomputers.GolemComputers +import com.mojang.serialization.MapCodec +import net.minecraft.block.BlockState +import net.minecraft.block.BlockWithEntity +import net.minecraft.block.entity.BlockEntity +import net.minecraft.block.entity.BlockEntityTicker +import net.minecraft.block.entity.BlockEntityType +import net.minecraft.entity.player.PlayerEntity +import net.minecraft.sound.BlockSoundGroup +import net.minecraft.text.Text +import net.minecraft.util.ActionResult +import net.minecraft.util.hit.BlockHitResult +import net.minecraft.util.math.BlockPos +import net.minecraft.world.World + +class GolemBlock : + BlockWithEntity( + Settings.create().registryKey(GolemComputers.GOLEM_BLOCK_KEY).sounds(BlockSoundGroup.METAL).strength(100.0f) + ) { + override fun getCodec(): MapCodec { + return createCodec(::GolemBlock::call) + } + + override fun createBlockEntity(pos: BlockPos, state: BlockState): BlockEntity { + return GolemBlockEntity(pos, state) + } + + override fun getTicker( + world: World?, + state: BlockState?, + type: BlockEntityType? + ): BlockEntityTicker? { + return validateTicker( + type, + GolemComputers.GOLEM_BLOCK_ENTITY + ) { _, _, _, golemBlockEntity -> golemBlockEntity.tick() } + } + + override fun onUse( + state: BlockState, + world: World, + pos: BlockPos, + player: PlayerEntity, + hit: BlockHitResult + ): ActionResult { + val blockEntity = world.getBlockEntity(pos) + if (blockEntity !is GolemBlockEntity) + return super.onUse(state, world, pos, player, hit) + if (world.isClient()) { + return ActionResult.SUCCESS + } + blockEntity.incrementClicks(); + player.sendMessage(Text.literal("You've clicked the block for the ${blockEntity.getClicks()}th time."), true); + + return ActionResult.SUCCESS_SERVER + } +} \ No newline at end of file diff --git a/src/main/kotlin/ca/sanae/golemcomputers/blocks/GolemBlockEntity.kt b/src/main/kotlin/ca/sanae/golemcomputers/blocks/GolemBlockEntity.kt new file mode 100644 index 0000000..be6f6d1 --- /dev/null +++ b/src/main/kotlin/ca/sanae/golemcomputers/blocks/GolemBlockEntity.kt @@ -0,0 +1,44 @@ +package ca.sanae.golemcomputers.blocks + +import ca.sanae.golemcomputers.GolemComputers +import ca.sanae.golemcomputers.computer.Computer +import net.fabricmc.api.EnvType +import net.fabricmc.api.Environment +import net.minecraft.block.BlockState +import net.minecraft.block.entity.BlockEntity +import net.minecraft.nbt.NbtCompound +import net.minecraft.registry.RegistryWrapper +import net.minecraft.util.math.BlockPos +import net.minecraft.world.World + +class GolemBlockEntity(pos: BlockPos?, state: BlockState?) : + BlockEntity(GolemComputers.GOLEM_BLOCK_ENTITY, pos, state) { + private var computer: Computer? = null + + fun getClicks(): Int { + return 0 + } + + fun incrementClicks() { + markDirty() + } + + override fun writeNbt(nbt: NbtCompound, registries: RegistryWrapper.WrapperLookup?) { + nbt.putInt("clicks", 0) + super.writeNbt(nbt, registries) + } + + override fun readNbt(nbt: NbtCompound, registries: RegistryWrapper.WrapperLookup?) { + nbt.getInt("clicks") + super.readNbt(nbt, registries) + } + + override fun setWorld(world: World?) { + computer = if (!world!!.isClient) Computer(world) else null + super.setWorld(world) + } + + fun tick() { + computer?.tick() + } +} \ No newline at end of file diff --git a/src/main/kotlin/ca/sanae/golemcomputers/client/GolemComputersClient.kt b/src/main/kotlin/ca/sanae/golemcomputers/client/GolemComputersClient.kt new file mode 100644 index 0000000..283c52a --- /dev/null +++ b/src/main/kotlin/ca/sanae/golemcomputers/client/GolemComputersClient.kt @@ -0,0 +1,8 @@ +package ca.sanae.golemcomputers.client + +import net.fabricmc.api.ClientModInitializer + +class GolemComputersClient : ClientModInitializer { + override fun onInitializeClient() { + } +} diff --git a/src/main/kotlin/ca/sanae/golemcomputers/computer/Computer.kt b/src/main/kotlin/ca/sanae/golemcomputers/computer/Computer.kt new file mode 100644 index 0000000..6586220 --- /dev/null +++ b/src/main/kotlin/ca/sanae/golemcomputers/computer/Computer.kt @@ -0,0 +1,62 @@ +package ca.sanae.golemcomputers.computer + +import ca.sanae.golemcomputers.computer.RustNative.Companion.cleaner +import ca.sanae.golemcomputers.computer.components.ChatComponent +import ca.sanae.golemcomputers.computer.components.Component +import net.minecraft.world.World +import java.nio.ByteBuffer +import java.nio.channels.FileChannel +import java.nio.file.Files +import kotlin.io.path.Path + +class Computer(val world: World) { + @Suppress("unused") + private val address: Long = 0 + + init { + new(1, 0x1000, intArrayOf(0x10000)) + cleaner.register(this) { + free() + } + val path = Path("/home/aubrey/Projects/golem-software/assembly.bin") + val channel = FileChannel.open(path); + val buffer = channel.map(FileChannel.MapMode.READ_ONLY, 0, channel.size()) + val wakeAddress = buffer.getUnsignedInt(); + + updateRomTest(buffer.rewind()) + println("waking at 0x${wakeAddress.toString(16)}") + insertComponent(0, ChatComponent(this)) + wakeCoreAt(0, wakeAddress) + } + + fun ByteBuffer.getUnsignedInt(): Long { + return Integer.reverseBytes(this.getInt()).toLong() and 0xFFFFFFFFL + } + + fun insertComponent(coreIndex: Int, component: Component) { + insertComponentNative( + coreIndex, + component, + component.info.typeName.toString(), + component.info.protocol.toString(), + component.info.bytesPerTick, + component.info.version + ) + } + + private external fun new(componentCapacity: Int, memorySize: Int, coreIps: IntArray): Long + private external fun free() + private external fun updateRomTest(data: ByteBuffer) + private external fun wakeCoreAt(coreIndex: Int, address: Long) + private external fun insertComponentNative( + index: Int, + component: Component, + typeName: String, + protocol: String, + bytesPerTick: UInt, + version: UInt + ) + + external fun tick(); + +} \ No newline at end of file diff --git a/src/main/kotlin/ca/sanae/golemcomputers/computer/IMachine.kt b/src/main/kotlin/ca/sanae/golemcomputers/computer/IMachine.kt new file mode 100644 index 0000000..d3f5327 --- /dev/null +++ b/src/main/kotlin/ca/sanae/golemcomputers/computer/IMachine.kt @@ -0,0 +1,5 @@ +package ca.sanae.golemcomputers.computer + +interface IMachine { + fun getComponents() {} +} \ No newline at end of file diff --git a/src/main/kotlin/ca/sanae/golemcomputers/computer/RustNative.kt b/src/main/kotlin/ca/sanae/golemcomputers/computer/RustNative.kt new file mode 100644 index 0000000..37836f7 --- /dev/null +++ b/src/main/kotlin/ca/sanae/golemcomputers/computer/RustNative.kt @@ -0,0 +1,32 @@ +package ca.sanae.golemcomputers.computer + +import java.io.File +import java.io.FileOutputStream +import java.lang.ref.Cleaner +import java.nio.channels.Channels + + +class RustNative private constructor() { + companion object { + val cleaner: Cleaner = Cleaner.create() + private val instance: RustNative = RustNative(); + + init { + val nativeLibraryName = System.mapLibraryName("golem_computers") + val tempFile: File = File.createTempFile("extracted_", nativeLibraryName) + + val classLoader = RustNative::class.java.getClassLoader() + + Channels.newChannel(classLoader.getResourceAsStream(nativeLibraryName)!!).use { src -> + FileOutputStream(tempFile).channel.use { dst -> + dst.transferFrom(src, 0, Long.MAX_VALUE) + } + } + System.load(tempFile.absolutePath) + } + + fun isInitialized(): Boolean { + return true + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/ca/sanae/golemcomputers/computer/components/ChatComponent.kt b/src/main/kotlin/ca/sanae/golemcomputers/computer/components/ChatComponent.kt new file mode 100644 index 0000000..ff0445e --- /dev/null +++ b/src/main/kotlin/ca/sanae/golemcomputers/computer/components/ChatComponent.kt @@ -0,0 +1,29 @@ +package ca.sanae.golemcomputers.computer.components + +import ca.sanae.golemcomputers.GolemComputers +import ca.sanae.golemcomputers.computer.Computer +import kotlinx.io.bytestring.decodeToString +import kotlinx.io.bytestring.getByteString +import net.minecraft.text.Text +import java.nio.ByteBuffer + +class ChatComponent(private val computer: Computer) : + Component(GolemComputers.id("chat"), GolemComputers.id("string_stream"), 10000, 0u) { + var chatBuffer: String = "" + override fun gotByteChunk(buffer: ByteBuffer) { + chatBuffer += buffer.getByteString().decodeToString() + while (true) { + val index = chatBuffer.indexOf('\u0000') + if (index == -1) break + val (message, new) = chatBuffer.splitAtIndex(index) + chatBuffer = new + computer.world.server!!.sendMessage(Text.of(message)) + } + } + + override fun restarted() { + chatBuffer = "" + } + + fun String.splitAtIndex(index: Int) = take(index) to substring(index) +} \ No newline at end of file diff --git a/src/main/kotlin/ca/sanae/golemcomputers/computer/components/Component.kt b/src/main/kotlin/ca/sanae/golemcomputers/computer/components/Component.kt new file mode 100644 index 0000000..0a49acd --- /dev/null +++ b/src/main/kotlin/ca/sanae/golemcomputers/computer/components/Component.kt @@ -0,0 +1,31 @@ +package ca.sanae.golemcomputers.computer.components + +import com.github.pbbl.heap.ByteBufferPool +import com.jogamp.common.util.LFRingbuffer +import com.jogamp.common.util.Ringbuffer +import net.minecraft.util.Identifier +import java.nio.ByteBuffer + +abstract class Component(val info: ComponentInfo) { + @Suppress("unused") + private val address: Long = 0 + private val pool = ByteBufferPool() + abstract fun gotByteChunk(buffer: ByteBuffer) + private external fun writeBytesNative(buffer: ByteBuffer): Boolean + fun writeBytes(lambda: ((size: Int) -> ByteBuffer) -> ByteBuffer) { + synchronized(pool) { + val buffer = lambda(pool::take) + val overflowed = writeBytesNative(buffer) + pool.give(buffer) + if (overflowed) { + overflowShutdown() + } + } + } + + // called when the component runs out of runway to store unread data + open fun overflowShutdown() {} + abstract fun restarted() + + data class ComponentInfo(val typeName: Identifier, val protocol: Identifier, val bytesPerTick: UInt, val version: UInt) +} \ No newline at end of file diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json new file mode 100644 index 0000000..bca4b94 --- /dev/null +++ b/src/main/resources/fabric.mod.json @@ -0,0 +1,29 @@ +{ + "schemaVersion": 1, + "id": "golem_computers", + "version": "${version}", + "name": "GolemComputers", + "description": "", + "authors": [], + "contact": {}, + "license": "All-Rights-Reserved", + "icon": "assets/golem_computers/icon.png", + "environment": "*", + "entrypoints": { + "client": [ + "ca.sanae.golemcomputers.client.GolemComputersClient" + ], + "main": [ + "ca.sanae.golemcomputers.GolemComputers" + ] + }, + "mixins": [ + "golem_computers.mixins.json" + ], + "depends": { + "fabricloader": ">=${loader_version}", + "fabric-language-kotlin": ">=${kotlin_loader_version}", + "fabric": "*", + "minecraft": "${minecraft_version}" + } +} diff --git a/src/main/resources/golem_computers.mixins.json b/src/main/resources/golem_computers.mixins.json new file mode 100644 index 0000000..97e50ce --- /dev/null +++ b/src/main/resources/golem_computers.mixins.json @@ -0,0 +1,11 @@ +{ + "required": true, + "minVersion": "0.8", + "package": "ca.sanae.golemcomputers.mixin", + "compatibilityLevel": "JAVA_21", + "mixins": [ + ], + "injectors": { + "defaultRequire": 1 + } +}