Upgrade to Pro — share decks privately, control downloads, hide ads and more …

[SnowOne 2024] Артём Шабуров: "Java и Wasm: приключение в браузере на 20 минут"

jugnsk
April 30, 2024

[SnowOne 2024] Артём Шабуров: "Java и Wasm: приключение в браузере на 20 минут"

WASM, WASI, WAGI — что всё это значит?

Что изменилось в WASM и в браузерах за прошедший год, и почему это важно в том числе для Java и Kotlin?

Я решил во всём этом разобраться, а вылилось это в компиляцию мини-JVM под WASM для запуска Java прямо в браузере.

jugnsk

April 30, 2024
Tweet

More Decks by jugnsk

Other Decks in Programming

Transcript

  1. О себе До 2022 Senior Java/Full-stack developer в: • СберТех

    • Ingram Micro Cloud После Founder/CEO/CTO/etcd. в deffun.io и groql.io 2
  2. О чём? • Зачем нам понадобился Wasm? • Про Wasm

    и WASI • Что нового произошло за последнее время? • При чём тут Java/Kotlin? 3
  3. GraphQL • Язык запросов • Runtime для выполнения запросов 6

    query { movies { directedBy { name } } } type Director implements Person { name: String! born: Date } type Movie { title: String! released: Int tagline: String directedBy: Director } type Query { movies: [Movie!] }
  4. GraphQL-Java 7 RuntimeWiring runtimeWiring = RuntimeWiring.newRuntimeWiring() .type("Query", builder -> builder

    .dataFetcher("movies", new MoviesDataFetcher())) .build(); type Query { movies: [Movie!] } class MoviesDataFetcher implements DataFetcher<Iterable<Movie>> { @Override public Iterable<Movie> get(DataFetchingEnvironment environment) throws Exception {
  5. Почему Wasm? • Возможность запускать приложения написанные на разных языках

    в браузере (и не только) • Безопасность • Переносимость • Перфоманс близкий к native 11
  6. Talk is cheap! int add(int a, int b) { return

    a + b; } package org.example; public final class CommonIntUtils { private CommonIntUtils() {} public static int add(int a, int b) { return a + b; } } 13 ├─ org/ │ ├─ example/ │ │ ├─ CommonIntUtils.java ├─ add.c
  7. Wasm bytecode int add(int a, int b) { return a

    + b; } 14 ├─ add.c clang --version > 9.0
  8. Wasm bytecode int add(int a, int b) { return a

    + b; } 15 ├─ add.c ~/ctest$ clang \ --target=wasm32 \ -nostdlib \ -Wl,--no-entry \ -Wl,--export-all \ -o add.wasm \ add.c
  9. Wasm bytecode int add(int a, int b) { return a

    + b; } 16 ├─ add.c ├─ add.wasm ~/ctest$ clang \ --target=wasm32 \ -nostdlib \ -Wl,--no-entry \ -Wl,--export-all \ -o add.wasm \ add.c
  10. Wasm bytecode int add(int a, int b) { return a

    + b; } 17 ├─ add.c ├─ add.wasm ~/ctest$ clang \ --target=wasm32 \ -O3 \ -nostdlib \ -Wl,--no-entry \ -Wl,--export-all \ -Wl,--lto-O3 \ -o add.wasm \ add.c
  11. Debuggable! WAT? 18 WAT (WebAssembly Text Format) - текстовое представление

    Wasm- байткода • WebAssembly/wabt - The WebAssembly Binary Toolkit • wasm2wat • wat2wasm
  12. Wasm bytecode int add(int a, int b) { return a

    + b; } 19 ├─ add.c ├─ add.wasm ~/ctest$ wasm2wat \ -o add.wat \ add.wasm
  13. Wasm bytecode int add(int a, int b) { return a

    + b; } 20 ├─ add.c ├─ add.wasm ├─ add.wat ~/ctest$ wasm2wat \ -o add.wat \ add.wasm
  14. Wasm bytecode int add(int a, int b) { return a

    + b; } 21 ├─ add.c ├─ add.wasm ├─ add.wat (module (func $add (param i32 i32) (result i32) local.get 1 local.get 0 i32.add) (export "add" (func $add)) )
  15. Варим Java package org.example; public final class CommonIntUtils { private

    CommonIntUtils() {} public static int add(int a, int b) { return a + b; } } 22 ├─ org/ │ ├─ example/ │ │ ├─ CommonIntUtils.java ~/javatest$ javac \ org/example/CommonIntUtils.java
  16. Варим Java package org.example; public final class CommonIntUtils { private

    CommonIntUtils() {} public static int add(int a, int b) { return a + b; } } 23 ├─ org/ │ ├─ example/ │ │ ├─ CommonIntUtils.java │ │ ├─ CommonIntUtils.class ~/javatest$ javac \ org/example/CommonIntUtils.java
  17. Варим Java package org.example; public final class CommonIntUtils { private

    CommonIntUtils() {} public static int add(int a, int b) { return a + b; } } 24 ├─ org/ │ ├─ example/ │ │ ├─ CommonIntUtils.java │ │ ├─ CommonIntUtils.class ~/javatest$ javap \ -c org.example.CommonIntUtils
  18. Варим Java static int add(int a, int b) { return

    a + b; } 25 ├─ org/ │ ├─ example/ │ │ ├─ CommonIntUtils.java │ │ ├─ CommonIntUtils.class static int add(int, int); Code: 0: iload_0 1: iload_1 2: iadd 3: ireturn
  19. Варим Java 26 static int add(int, int); Code: 0: iload_0

    1: iload_1 2: iadd 3: ireturn (module (func $add (param i32 i32) (result i32) local.get 1 local.get 0 i32.add) (export "add" (func $add)) )
  20. Отличия • Типы • i32, i64, f32, f64, v128 •

    externref • Memory • Stack • Linear «expandable array of bytes that Javascript and Wasm can synchronously read and modify» 28
  21. Отличия • Типы • i32, i64, f32, f64, v128 •

    externref • Memory • Stack • Linear • Модульность • functions, memory, imports, exports 29
  22. Отличия • Типы • i32, i64, f32, f64, v128 •

    externref • Memory • Stack • Linear • Модульность • functions, memory, imports, exports • Си-семантика vs. managed 30
  23. Переносимость 33 «ANTLR 4 supports 10 target languages, and each

    of them requires a dedicated full runtime.» • Java • C# • Python • JavaScript • TypeScript • Go • C++ • Swift • PHP • DART
  24. Переносимость 34 «ANTLR 4 supports 10 target languages, and each

    of them requires a dedicated full runtime.» • Java • C# • Python • JavaScript и TypeScript • Go • C++ • Swift • PHP • DART
  25. Переносимость 35 «ANTLR 4 supports 10 target languages, and each

    of them requires a dedicated full runtime. With the advent of WebAssembly, there is an opportunity to have just 1 runtime, that will run faster with language hosts such as JavaScript or Python. ANTLR 5 is primarily about that: switching to WebAssembly.»
  26. Wasm GC • Reference types • arrays, structs, etc. •

    > 30 новых инструкций 42
  27. Wasm GC • Reference types • arrays, structs, etc. •

    > 30 новых инструкций Минусы • Отсутствие mmap и mprotect 43
  28. Новое в WASI Preview2 • IO и FS • HTTP

    и Sockets • Threads (experimental) 47
  29. Новое в WASI Preview2 • IO и FS • HTTP

    и Sockets • Threads (experimental) • Component model 48
  30. Component model • WIT (Wasm Interface Type) • Canonical ABI

    (Application Binary Interface) 49 https://component-model.bytecodealliance.org/introduction.html
  31. Component model User-defined types • Enum • Record • Type

    - type alias Built-in types • Scalars • Tuples • Lists • Options, unions • etc. 50
  32. Component model • Функции • Интерфейсы - набор типов и

    функций 52 interface canvas { type canvas-id = u64; record point { x: u32, y: u32, } draw-line: func(canvas: canvas-id, from: point, to: point); }
  33. Component model • Функции • Интерфейсы - набор типов и

    функций • Пакеты 53 package documentation:example; package documentation:[email protected];
  34. Component model • Функции • Интерфейсы - набор типов и

    функций • Пакеты • Worlds - imports и exports 54
  35. Component model Доступно в следующих языках • Rust • Javascript

    • Python • Kotlin на очереди* 55 * https://youtrack.jetbrains.com/issue/KT-64569/Kotlin-Wasm-Support-Component-Model
  36. JVM как рантайм для Wasm • JNI • Wasmer Java

    • Итерпретатор Wasm на Java • dylibso/chicory 64
  37. JVM как рантайм для Wasm • JNI • Wasmer Java

    • Итерпретатор Wasm на Java • dylibso/chicory • Polyglot VM • GraalVM 65
  38. JVM как рантайм для Wasm • JNI • Wasmer Java

    • Итерпретатор Wasm на Java • dylibso/chicory • Polyglot VM • GraalVM 66
  39. GraalWasm 71 class MovieRatingDataFetcher implements DataFetcher<Integer> { @Override public Integer

    get(DataFetchingEnvironment environment) throws Exception { Movie movie = (Movie) environment.getSource(); RuntimeWiring runtimeWiring = RuntimeWiring.newRuntimeWiring() .type("Movie", builder -> builder .dataFetcher("rating", new MovieRatingDataFetcher()))
  40. GraalWasm 72 Path path = Path.of(ClassLoader.getSystemResource("add.wasm").toURI()); byte[] binary = Files.readAllBytes(path);

    Context.Builder contextBuilder = Context.newBuilder("wasm"); Source.Builder sourceBuilder = Source.newBuilder("wasm", ByteSequence.create(binary), "main"); Source source = sourceBuilder.build(); try (Context context = contextBuilder.build()) { context.eval(source); Value mainFunction = context.getBindings("wasm").getMember("main").getMember("add"); Value newRating = mainFunction.execute(movie.getRating(), 2); return newRating.asInt(); }
  41. GraalWasm WASI 73 Path path = Path.of(ClassLoader.getSystemResource("some.wasm").toURI()); byte[] binary =

    Files.readAllBytes(path); Context.Builder contextBuilder = Context.newBuilder("wasm") .option("wasm.Builtins", "wasi_snapshot_preview1"); Source.Builder sourceBuilder = Source.newBuilder("wasm", ByteSequence.create(binary), "main"); Source source = sourceBuilder.build(); try (Context context = contextBuilder.build()) { context.eval(source); // ... }
  42. GraalWasm Минусы • Поддержка WASI только на уровне WASI Snapshot

    Preview1 • Нет poll_oneoff (epoll) • Совсем нет поддержки WASI Preview2 • Нет Component model, а Polyglot API привязывает к GraalVM 74
  43. Пейсы • Возможность писать Data Fetcher на других языках •

    GraphQL прямо в браузере • Чтобы бесплатные тарифы нам обходились дешевле 76
  44. При чём тут Java? • JVM как рантайм для выполнения

    Wasm • AOT-компиляция Java и выполнение в Wasm-рантайме 78
  45. AOT • Меньше размер исполняемого файла -> меньше качать •

    Время старта • Производительность при холодном старте 79
  46. AOT-компиляторы • TeaVM • JS • Wasm и WASI (experimental)

    • JWebAssembly • Wasm-only • ByteCoder • JS • Wasm • OpenCL 80
  47. AOT и Reflection • TeaVM • JWebAssembly (нет поддержки Reflection)

    • ByteCoder («Reflection support is currently reengineered.»*) 83 * https://mirkosertic.github.io/Bytecoder/chapter-1/page-1-d/
  48. AOT и Reflection • TeaVM - есть Metaprogramming API •

    JWebAssembly (нет поддержки Reflection) • ByteCoder («Reflection support is currently reengineered.»*) 84 * https://mirkosertic.github.io/Bytecoder/chapter-1/page-1-d/
  49. Metaprogramming в TeaVM @Meta private static native Object getField(Class<?> cls,

    Object obj); private static void getField(ReflectClass<Object> cls, Value<Object> obj) { if (cls.getAnnotation(Entity.class) == null) { unsupportedCase(); return; } ReflectField field = cls.getField("a"); exit(() -> field.get(obj)); } Object value = getField(MyEntity.class, myEntity); 85
  50. Metaprogramming в TeaVM Ограничения • Нельзя искать поля и методы

    класса внутри template lambdas • Нельзя получать и присваивать значение ReflectField в compile-time • Нельзя вызывать ReflectMethod.invoke в compile-time 86
  51. А что там Kotlin? • Поддержка Wasm в Alpha •

    Многие kotlinx-библиотеки поддерживают Wasm • serialization • coroutines (с версии 1.8.0) 88
  52. Kotlin GraphQL • ExpediaGroup/graphql-kotlin - built on top of graphql-java

    • aPureBase/KGraphQL - Pure Kotlin GraphQL implementation 89
  53. KGraphQL не совсем Pure Kotlin Использует Jackson (Но только классы

    типа JsonNode) 91 Легко поменять на kotlinx.serialization
  54. KGraphQL не совсем Pure Kotlin Использует Jackson (Но только классы

    типа JsonNode) Использует старую версию coroutines 92 Легко поменять на kotlinx.serialization Обновим
  55. KGraphQL не совсем Pure Kotlin Использует Jackson (Но только классы

    типа JsonNode) Использует старую версию coroutines Использует Java-либу для кеширования (caffeine) 93 Легко поменять на kotlinx.serialization Обновим Не очень-то и надо
  56. KGraphQL не совсем Pure Kotlin Использует Jackson (Но только классы

    типа JsonNode) Использует старую версию coroutines Использует Java-либу для кеширования (caffeine) Использует Java Reflection… 94 Легко поменять на kotlinx.serialization Обновим Не очень-то и надо
  57. Kotlin Reflection • Kotlin Wasm поддерживает только базовые методы KType

    и KClass, но не Constructor#call • Compile-time reflection в JetBrains-Research/reflekt • Экспериментальный • Нет возможности делать Constructor#call 96
  58. KGraphQL 98 val constructor = type.unwrapped().kClass!!.primaryConstructor ?: throw GraphQLError("...", value)

    // ... constructor.callBy(valueMap) interface Type : __Type { // ... val kClass : KClass<*>?
  59. KGraphQL 100 val constructor = type.unwrapped().kClass!!.primaryConstructor ?: throw GraphQLError("...", value)

    // ... constructor.callBy(valueMap) when (type) { is Type.Scalar<*>, is Type.Enum<*> -> createScalar(type, value) is Type.Object<*> -> createObject(type, value) is Type.AList -> createList(type, value.fields) // ... else -> throw RuntimeException("Type is not supported") }
  60. Kotlin Wasm Compose Web использует WASM GC и им уже

    можно пользоваться в последних версиях Для WASI Server side - есть PoC - kowasm/kowasm • Включает некоторые возможности WASI Preview2 Минусы • Официально пока нет поддержки WASI Preview2 и Component model 103
  61. AOT Java - ещё варианты? • TeaVM • JWebAssembly •

    ByteCoder • GraalVM Native Image? • Поддерживает GraphQL-Java и многие другие библиотеки и фреймворки 104
  62. GraalVM Native Image LLVMBackend • С версии 21 нужно собирать

    из сорцов 108 ~/gntest$ native-image \ -H:+UnlockExperimentalVMOptions -H:CompilerBackend=llvm \ -H:TempDirectory=tempo
  63. GraalVM Native Image LLVMBackend • С версии 21 нужно собирать

    из сорцов Файлы с LLVM IR 109 ~/gntest$ native-image \ -H:+UnlockExperimentalVMOptions -H:CompilerBackend=llvm \ -H:TempDirectory=tempo
  64. LLVM statepoint Реализация LLVM statepoint для Wasm GC на стороне

    LLVM llvm/llvm-project/issues/80638 Изменения на стороне Substrate - artipop/graal 111 «While the LLVM backend uses mostly common, well-supported features of LLVM, garbage collection support implies the use of statepoint intrinsics, an experimental feature of LLVM.»
  65. При чём тут Java? • JVM как рантайм для выполнения

    Wasm • AOT-компиляция Java и выполнение в Wasm-рантайме • Wasm JVM 112
  66. Emscripten • Upstream LLVM • emcc - «drop-in replacement» для

    gcc • Пропатченный libc • pthreads • POSIX • Поддержка SDL • Трансляция OpenGL -> WebGL • Множество реализаций FS • На выходе Wasm или JS 113
  67. HotSpot Zero Variant - Zero-Assembler port of JDK • Без

    JIT, вместо него CppInterpreter • и несколько GC • можно наконфигурить только EpsilonGC 114 ~/jdk$ bash ./configure --with-jvm-variants=zero
  68. HotSpot 116 ~/jdk$ emconfigure \ bash ./configure --with-jvm-variants=zero configure:87389: checking

    whether the C compiler works configure:87411: /root/emsdk/upstream/emscripten/emcc -m64 -m64 conftest.c >&5 wasm-ld: error: /tmp/emscripten_temp/conftest_0.o: must specify -mwasm64 to process wasm64 object files
  69. HotSpot Библиотеки • ALSA, X11 - обязательны даже для headless

    JDK «Note that alsa is needed even if you only want to build a headless JDK.» «Note that X11 is needed even if you only want to build a headless JDK.» 119 https://openjdk.org/groups/build/doc/building.html
  70. HotSpot Библиотеки • ALSA, X11 - обязательны даже для headless

    JDK • При кросс-компиляции нужны копии библиотек для host и target 120
  71. HotSpot Библиотеки • ALSA, X11 - обязательны даже для headless

    JDK • При кросс-компиляции нужны копии библиотек для host и target 121 ~/jdk$ sudo apt-get install libasound2-dev ~/jdk$ sudo apt-get install libasound2-dev:i386
  72. HotSpot Библиотеки • ALSA, X11 - обязательны даже для headless

    JDK • При кросс-компиляции нужны копии библиотек для host и target • libffi не компилируется под wasm32 • tweag/libffi-wasm32 - «Limited port of libffi to pure wasm32.» 122
  73. Wasm JVM CheerpJ 2.x AOT-компилятор поверх j2cl CheerpJ 3.0 •

    Полноценная JVM на OpenJDK 8, скомпилированная в Wasm • Свой JIT-компилятор • native binding’и для AWT, LWJGL и не только 123
  74. CheerpJ 3.0 126 <script src="https://cjrtnc.leaningtech.com/3.0/cj3loader.js"></script> await cheerpjInit(); const cj =

    await cheerpjRunLibrary("/app/graphql-java-19.2.jar"); // noinspection JSUnresolvedReference const SchemaParserClass = await cj.graphql.schema.idl.SchemaParser; const schemaParser = await new SchemaParserClass(); await schemaParser.parse("type Query { a: String }");
  75. CheerpJ 3.0 Плюсы • Зачастую просто работает (без плагинов) •

    Множество native binding’ов (AWT, LWJGL, etc.) Минусы • No commercial use • Невероятно долгий старт JVM 127
  76. JVM на второй странице Google Search AmazingRise/rise-jvm «Rise JVM is

    a minimal Java VM» 2 года нет коммитов rchaser53/rust-jvm «A toy JVM on Web Browser» 4 года нет коммитов 128
  77. Wasm JVM platypusguy/jacobin «A more than minimal JVM written in

    Go and capable of running Java 17 classes.» ~100 коммитов/месяц 129
  78. Wasm JVM На стороне JS • Подключить Go syscall.js •

    Переопределить FS 131 import { fs } from "memfs"; globalThis.fs = { writeSync(fd, buf, callback) { fs.writeSync(fd, buf, callback); }, write(fd, buf, offset, length, position, callback) { fs.write(fd, buf, offset, length, position, callback); }, chmod(path, mode, callback) { fs.chmod(path, mode, callback); }, chown(path, uid, gid, callback) { fs.chown(path, uid, gid, callback); }, // ...
  79. Wasm JVM На стороне JS • Подключить Go syscall.js •

    Переопределить FS • Добавить необходимые Java-модули и классы в нашу FS 132
  80. Wasm JVM • Импортировать Wasm 133 const go = new

    Go(); WebAssembly.instantiateStreaming(fetch("jacobin.wasm"), go.importObject).then( (result) => { let instance = result.instance; go.run(instance);
  81. Wasm JVM • Импортировать Wasm • Не забыть про env-переменные

    и аргументы 134 const go = new Go(); go.env = { JAVA_HOME: javaHome, HOME: jacobinHome }; go.argv = ["jacobin", "/Hello.class"]; WebAssembly.instantiateStreaming(fetch("jacobin.wasm"), go.importObject).then( (result) => { let instance = result.instance; go.run(instance);
  82. Jacobin • Работает намного быстрее Минусы • Не полноценная JVM

    • native-методы надо реализовывать либо в Go, либо писать много обвязки на Go и JS, стандартного способа нет 136
  83. JamVM Interpreter-only JVM • Написана на C с небольшим количеством

    assembler • Поддержка JNI, Threads • Использовалась в пакете IcedTea в некоторых Linux-дистрибутивах 139
  84. JamVM emscripten-core/emscripten/issues/3342 Для Wasm • emconfigure и emmake • emcc

    вместо gcc с target=i386 • Отсыпать #ifndef __EMSCRIPTEN__ • fpu_control • Использовать switch-interpreter 140
  85. JamVM Полноценная JVM • За исключением того, что мы выпилили

    Минусы • Java 1.8 • Полноценная Java с classpath -> дольше загрузка 141
  86. Wasm JVM: вместо выводов Wasm JVM позволяет запустить скомпилированный байткод

    без изменений (если это полноценная JVM типа CheerpJ 3.0) Вариантов совсем немного • Хороший - проприетарный и медленный • Есть маленькие open-source реализации, но урезанные • Ни одна из них не использует Wasm GC 142
  87. Выводы • Wasm быстро развивается • За последние год произошло

    несколько крупных изменений • Теперь и для managed-языков благодаря Wasm GC • WASI позволяет запускать Wasm не только в браузере • Kotlin Wasm уже в Alpha и неплохо работает • Всё ещё предстоит долгий путь адаптации Wasm в managed-языках 143
  88. Beyond the GraphQL 145 • Для GraphQL - Data Fetcher

    хорошо обобщаются • Для произвольного кода лучше использовать какой-то стандарт •