Quickjs muutmine rooste funktsioonide importimiseks - uus mõtteviis Krakeni laiendamiseks
Koodihoidla
Genesis
Kraken on suure jõudlusega veebirenderdamise mootor, mis põhineb aadressil Flutter
ja kasutab skriptimootorina quickjs.
Tahtsin kirjutada mõned laiendused Krakenile, kasutades rust
.
Kraken toetab laienduste kirjutamist, kasutades dart
.
Kasutades flutter_rust_bridge
rust
ja dart
.
Neid kahte punkti kombineerides ei ole raske kirjutada Krakeni laiendusi, kasutades rust
.
Selle lahenduse jõudluskoormus tundub siiski suur, kuna dart
kutsub rust
ja quickjs
kutsub dart
, mis omakorda vähendab jõudlust.
Teisalt, kuigi rust
kogukond on rquickjs
sellised üleskutsed quickjs
raamatukogule rust
.
Kuid nad kutsuvad quickjs
asemel embedding quickjs
ja neid ei saa kasutada magic quickjs
.
Selles koodibaasis olen rakendanud uue lahenduse: quickjs
lähtekoodi otsene muutmine, et toetada rust
laiendust.
See on üldine lahendus, mida saab kasutada mitte ainult Krakeni, vaid ka kõigi raamistike ja raamatukogude muutmiseks, mis sisaldavad quickjs
.
Demonstratsioon
Test.js kood on järgmine :
const try_run = (func, ...args) => {
try {
func(...args)
} catch (err) {
console.log('❌', err.message)
console.log(err.stack)
}
};
import * as rust from 'rust'
console.log("export from rust :", Object.keys(rust))
import {
fib,
sleep
} from 'rust'
(async () => {
console.log('begin sleep 2s')
await sleep(2000);
console.log('sleep done')
console.log('fib(3) =', fib(3));
console.log("try catch example :")
try_run(fib);
try_run(fib, '*');
})()
Käivita ./quickjs/qjs test.js
, väljund :
export from rust : fib,sleep
begin sleep 2s
sleep done
fib(3) = 6
try catch example :
❌ miss : args need 1 pass 0
at fib (native)
at try_run (test.js:8)
at <anonymous> (test.js:27)
❌ not number : args position 0
at fib (native)
at try_run (test.js:6)
at <anonymous> (test.js:28)
Kiudude rakendamine roostes
Js-i imporditud fib-funktsioon on pärit aadressilt rust/src/export/fib.rs
ja kood on järgmine :
use crate::js::{self, arg};
use quickjs_ffi::{JSContext, JSValue};
use rust_macro::js;
use std::os::raw::c_int;
#[js]
pub fn fib(n: i64) -> i64 {
if n <= 1 {
return if n == 1 { 1 } else { 0 };
}
n + fib(n - 1)
}
#[no_mangle]
pub extern "C" fn js_fib(
ctx: *mut JSContext,
_this: JSValue,
argc: c_int,
argv: *mut JSValue,
) -> JSValue {
if let Err(err) = arg::arg_miss(ctx, argc, fib_args_len) {
return err;
}
match arg::arg_i64(ctx, argv, 0) {
Err(err) => err,
Ok(n) => js::val(ctx, fib(n)),
}
}
Praegu lisab protseduurimakro #[js]
lihtsalt konstanti fib_args_len
, mis määrab funktsiooni argumentide arvu.
Tulevikus saab kirjutada protseduurimakro ./rust_macro
, et võimaldada täielikult automaatset funktsiooni eksporti.
Unefunktsiooni rakendamine roostes
Js-i imporditud sleep-funktsioon on pärit aadressilt rust/src/export/sleep.rs
ja kood on järgmine :
use crate::js::{self, arg};
use quickjs_ffi::{JSContext, JSValue};
use rust_macro::js;
use std::os::raw::c_int;
#[js]
pub fn fib(n: i64) -> i64 {
if n <= 1 {
return if n == 1 { 1 } else { 0 };
}
n + fib(n - 1)
}
#[no_mangle]
pub extern "C" fn js_fib(
ctx: *mut JSContext,
_this: JSValue,
argc: c_int,
argv: *mut JSValue,
) -> JSValue {
if let Err(err) = arg::arg_miss(ctx, argc, fib_args_len) {
return err;
}
match arg::arg_i64(ctx, argv, 0) {
Err(err) => err,
Ok(n) => js::val(ctx, fib(n)),
}
}
use crate::{js::arg, qjs::run};
use async_io::Timer;
use quickjs_ffi::{JSContext, JSValue};
use rust_macro::js;
use std::{os::raw::c_int, time::Duration};
#[js]
pub async fn sleep(n: u64) {
Timer::after(Duration::from_millis(n)).await;
}
#[no_mangle]
pub extern "C" fn js_sleep(
ctx: *mut JSContext,
_this: JSValue,
argc: c_int,
argv: *mut JSValue,
) -> JSValue {
if let Err(err) = arg::arg_miss(ctx, argc, sleep_args_len) {
return err;
}
match arg::arg_i64(ctx, argv, 0) {
Err(err) => err,
Ok(n) => run(ctx, async move {
sleep(n as u64).await;
Ok(())
}),
}
}
Nagu eespool näha, on kõik eksporditavad funktsioonid määratletud kataloogis ./rust/src/export
. See kataloog mod.rs
luuakse automaatselt, kui käivitatakse ./rust/build.xsh
, eksportides kõik selle all olevad .rs
failid.
Saabuvate js parameetrite lugemine ja valideerimine
Parameetrid loetakse ja valideeritakse aadressil src/js/arg.rs
järgmise koodiga :
use crate::js::{self, arg};
use quickjs_ffi::{JSContext, JSValue};
use rust_macro::js;
use std::os::raw::c_int;
#[js]
pub fn fib(n: i64) -> i64 {
if n <= 1 {
return if n == 1 { 1 } else { 0 };
}
n + fib(n - 1)
}
#[no_mangle]
pub extern "C" fn js_fib(
ctx: *mut JSContext,
_this: JSValue,
argc: c_int,
argv: *mut JSValue,
) -> JSValue {
if let Err(err) = arg::arg_miss(ctx, argc, fib_args_len) {
return err;
}
match arg::arg_i64(ctx, argv, 0) {
Err(err) => err,
Ok(n) => js::val(ctx, fib(n)),
}
}
use crate::{js::arg, qjs::run};
use async_io::Timer;
use quickjs_ffi::{JSContext, JSValue};
use rust_macro::js;
use std::{os::raw::c_int, time::Duration};
#[js]
pub async fn sleep(n: u64) {
Timer::after(Duration::from_millis(n)).await;
}
#[no_mangle]
pub extern "C" fn js_sleep(
ctx: *mut JSContext,
_this: JSValue,
argc: c_int,
argv: *mut JSValue,
) -> JSValue {
if let Err(err) = arg::arg_miss(ctx, argc, sleep_args_len) {
return err;
}
match arg::arg_i64(ctx, argv, 0) {
Err(err) => err,
Ok(n) => run(ctx, async move {
sleep(n as u64).await;
Ok(())
}),
}
}
use crate::js::throw;
use quickjs_ffi::{JSContext, JSValue, JS_IsNumber, JS_ToInt64};
use std::{mem::MaybeUninit, os::raw::c_int};
pub(crate) fn arg_miss(ctx: *mut JSContext, argc: c_int, need: c_int) -> Result<(), JSValue> {
if argc < need {
throw(ctx, format!("miss : args need {need} pass {argc}"))?
}
Ok(())
}
pub(crate) fn arg_i64(ctx: *mut JSContext, argv: *mut JSValue, pos: isize) -> Result<i64, JSValue> {
unsafe {
let val = *argv.offset(pos);
if JS_IsNumber(val) == 0 {
throw(ctx, format!("not number : args position {pos}"))?
}
let mut n = MaybeUninit::uninit();
JS_ToInt64(ctx, n.as_mut_ptr() as _, val);
Ok(n.assume_init())
}
}
Praegu kontrollitakse ainult argumentide arvu ja loetakse i64 tüüpi.
Neid funktsioone saab lisada vastavalt vajadusele, vt qjs_sys funktsioonid alates JS_To
.
Andmetüüpide konverteerimine roostest js-i
Tüübimuutus toimub aadressil src/js/val.rs
järgmise koodiga :
use crate::js::{self, arg};
use quickjs_ffi::{JSContext, JSValue};
use rust_macro::js;
use std::os::raw::c_int;
#[js]
pub fn fib(n: i64) -> i64 {
if n <= 1 {
return if n == 1 { 1 } else { 0 };
}
n + fib(n - 1)
}
#[no_mangle]
pub extern "C" fn js_fib(
ctx: *mut JSContext,
_this: JSValue,
argc: c_int,
argv: *mut JSValue,
) -> JSValue {
if let Err(err) = arg::arg_miss(ctx, argc, fib_args_len) {
return err;
}
match arg::arg_i64(ctx, argv, 0) {
Err(err) => err,
Ok(n) => js::val(ctx, fib(n)),
}
}
use crate::{js::arg, qjs::run};
use async_io::Timer;
use quickjs_ffi::{JSContext, JSValue};
use rust_macro::js;
use std::{os::raw::c_int, time::Duration};
#[js]
pub async fn sleep(n: u64) {
Timer::after(Duration::from_millis(n)).await;
}
#[no_mangle]
pub extern "C" fn js_sleep(
ctx: *mut JSContext,
_this: JSValue,
argc: c_int,
argv: *mut JSValue,
) -> JSValue {
if let Err(err) = arg::arg_miss(ctx, argc, sleep_args_len) {
return err;
}
match arg::arg_i64(ctx, argv, 0) {
Err(err) => err,
Ok(n) => run(ctx, async move {
sleep(n as u64).await;
Ok(())
}),
}
}
use crate::js::throw;
use quickjs_ffi::{JSContext, JSValue, JS_IsNumber, JS_ToInt64};
use std::{mem::MaybeUninit, os::raw::c_int};
pub(crate) fn arg_miss(ctx: *mut JSContext, argc: c_int, need: c_int) -> Result<(), JSValue> {
if argc < need {
throw(ctx, format!("miss : args need {need} pass {argc}"))?
}
Ok(())
}
pub(crate) fn arg_i64(ctx: *mut JSContext, argv: *mut JSValue, pos: isize) -> Result<i64, JSValue> {
unsafe {
let val = *argv.offset(pos);
if JS_IsNumber(val) == 0 {
throw(ctx, format!("not number : args position {pos}"))?
}
let mut n = MaybeUninit::uninit();
JS_ToInt64(ctx, n.as_mut_ptr() as _, val);
Ok(n.assume_init())
}
}
use quickjs_ffi::{JSContext, JSValue, JS_NewInt64, JS_NewString, JS_NULL, JS_UNDEFINED};
use std::ffi::CString;
pub enum Val {
None,
Undefined,
I64(i64),
CString(CString),
}
impl From<()> for Val {
fn from(_: ()) -> Self {
Val::Undefined
}
}
impl From<i64> for Val {
fn from(t: i64) -> Self {
Val::I64(t)
}
}
impl From<CString> for Val {
fn from(t: CString) -> Self {
Val::CString(t)
}
}
pub(crate) fn val(ctx: *mut JSContext, t: impl Into<Val>) -> JSValue {
match t.into() {
Val::None => JS_NULL,
Val::Undefined => JS_UNDEFINED,
Val::I64(n) => unsafe { JS_NewInt64(ctx, n) },
Val::CString(cstr) => unsafe { JS_NewString(ctx, cstr.as_ptr()) },
}
}
Ainult neli tüüpi on määratletud teisendamiseks None
, ()
, i64
ja CStringist js
. Võite lisada nii palju kui soovite.
Rohkem andmetüüpe saab deklareerida qjs_sys funktsioonides, alustades JS_New
.
Arenduskeskkond
Ma arendan Apple'i sülearvutis, rooste kasutab 1.62.0-nightly.
Installige kõigepealt direnv, minge kataloogi ja direnv allow
mõnda aega
Installige python3, seejärel pip3 install -r ./requirements.txt
käivitada ./build.xsh
demo kompileerimiseks ja käivitamiseks.
Vaikimisi kloonitakse ametlik quickjs repositoorium, kui soovite muuta quickjs-i Krakeni repositooriumis, siis esmalt
git clone --recursive git@github.com:openkraken/kraken.git --depth=1
siis tehke järgmist
rm -rf quickjs
ln -s ../kraken/bridge/third_party/quickjs .
Lõpuks käivitage uuesti ./build.xsh
Kataloogi struktuur
./quickjs_rust
Quickjs-koodi c-faili muutmine./quickjs_ffi
Eksportige funktsioonidquickjs
päisefailist aadressilerust
./rust
Kasutagerust
, et rakendada funktsioone aadressilquickjs
../rust/src/qjs.rs
Asünkroonsete kõnede rakendamine. Kunaquickjs
on ühetäheline, kirjutataksequckjs
funktsioonikutsed põhisuunas.
./rust_macro
rust
Menetlusmakro rakendamine#[js]
Tulevikus vt wasmedge-quickjs, et automaatselt eksportida rooste funktsioonid js-funktsioonideks. wasmedge-quickjs → JsFunctionTrampoline
build.xsh
Skriptide ehitamine Ilma pikema jututa läheme otse build.xsh
ehitusskripti lähtekoodi juurde.
#!/usr/bin/env xonsh
from pathlib import Path
from os.path import dirname,abspath,exists,join
PWD = dirname(abspath(__file__))
cd @(PWD)
p".xonshrc".exists() && source .xonshrc
quickjs = 'quickjs'
if not exists(quickjs):
git clone git@github.com:bellard/@(quickjs).git --depth=1
./quickjs_rust/patch.py
./rust/build.xsh
./quickjs_rust/gen.py
def ln_s(li):
for arg in li.split(' '):
fp = join(quickjs,arg)
if not exists(fp):
ln -s @(PWD)/@(arg) @(fp)
ln_s('quickjs_rust rust quickjs_ffi rust_macro')
cd @(quickjs)
make qjs
cd @(PWD)
./quickjs/qjs --unhandled-rejection -m test.js 2>&1 | tee test.js.out
Põhimõtte selgitamine
quickjs_rust/patch.py
Käivitades ./quickjs_rust/patch.py
, tehakse mõned väikesed muudatused quickjs
lähtekoodis.
Üks funktsioonidest JS_AddRust
kasutatakse rooste moodulisse süstimiseks.
rust_run
sisestatakse JS_ExecutePendingJob
, et kutsuda asünkroonseid funktsioone.
Kõigi muudatuste ekraanipilt on esitatud allpool :
quickjs_rust.h
Ülaltoodud muudatustest näete, et oleme lisanud uue päisefaili quickjs_rust.h
järgmise koodiga
#ifndef QUICKJS_RUST_H
#define QUICKJS_RUST_H
#include "../quickjs/quickjs.h"
#include "../rust/rust.h"
#define countof(x) (sizeof(x) / sizeof((x)[0]))
#define JS_RUSTFUNC_DEF(name) JS_CFUNC_DEF(#name, name##_args_len, js_##name)
#include "./js_rust_funcs.h"
static const unsigned int js_rust_funcs_count = countof(js_rust_funcs);
static int
js_rust_init(JSContext* ctx, JSModuleDef* m)
{
return JS_SetModuleExportList(ctx, m, js_rust_funcs,
js_rust_funcs_count);
}
#define JS_INIT_MODULE js_init_module_rust
JSModuleDef* JS_INIT_MODULE(JSContext* ctx, const char* module_name)
{
JSModuleDef* m;
m = JS_NewCModule(ctx, module_name, js_rust_init);
if (!m)
return NULL;
js_rust_init(ctx, m);
return m;
}
void JS_AddRust(JSContext* ctx, JSRuntime* rt)
{
JSModuleDef* m = JS_INIT_MODULE(ctx, "rust");
for (unsigned int i = 0; i < js_rust_funcs_count; i++) {
JS_AddModuleExport(ctx, m, js_rust_funcs[i].name);
}
rust_init(ctx, rt);
}
#endif
rust/rust.h
Näete, et quickjs_rust/quickjs_rust.h
tutvustab quickjs_rust/js_rust_funcs.h
, mis genereeritakse automaatselt rooste ekspordifunktsiooni päisefailist rust/rust.h
ja mida ei tohiks käsitsi muuta.
Ja rust/rust.h
genereeritakse cbindgeni kutsumisega aadressilt ./rust/build.xsh
.
rust/build.xsh
#!/usr/bin/env xonsh
from os.path import dirname,abspath
import platform
PWD = dirname(abspath(__file__))
cd @(PWD)
p"../.xonshrc".exists() && source ../.xonshrc
./src/export/mod.gen.py
system = platform.system().lower()
if system == 'darwin':
system = f'apple-{system}'
TARGET=f'{platform.machine()}-{system}'
def cbindgen():
cbindgen -q --config cbindgen.toml --crate rust --output rust.h
try:
cbindgen()
except:
cargo clean
cbindgen()
cargo build \
--release \
-Z build-std=std,panic_abort \
-Z build-std-features=panic_immediate_abort \
--target @(TARGET)
mv ./target/@(TARGET)/release/librust.a ./target/release
Arengu märkmed
quickjs_ffi
Koodi allikast quijine/main/quijine_core/src/ffi.rs
väikeste muudatustega, asendades
use crate::js::{self, arg};
use quickjs_ffi::{JSContext, JSValue};
use rust_macro::js;
use std::os::raw::c_int;
#[js]
pub fn fib(n: i64) -> i64 {
if n <= 1 {
return if n == 1 { 1 } else { 0 };
}
n + fib(n - 1)
}
#[no_mangle]
pub extern "C" fn js_fib(
ctx: *mut JSContext,
_this: JSValue,
argc: c_int,
argv: *mut JSValue,
) -> JSValue {
if let Err(err) = arg::arg_miss(ctx, argc, fib_args_len) {
return err;
}
match arg::arg_i64(ctx, argv, 0) {
Err(err) => err,
Ok(n) => js::val(ctx, fib(n)),
}
}
use crate::{js::arg, qjs::run};
use async_io::Timer;
use quickjs_ffi::{JSContext, JSValue};
use rust_macro::js;
use std::{os::raw::c_int, time::Duration};
#[js]
pub async fn sleep(n: u64) {
Timer::after(Duration::from_millis(n)).await;
}
#[no_mangle]
pub extern "C" fn js_sleep(
ctx: *mut JSContext,
_this: JSValue,
argc: c_int,
argv: *mut JSValue,
) -> JSValue {
if let Err(err) = arg::arg_miss(ctx, argc, sleep_args_len) {
return err;
}
match arg::arg_i64(ctx, argv, 0) {
Err(err) => err,
Ok(n) => run(ctx, async move {
sleep(n as u64).await;
Ok(())
}),
}
}
use crate::js::throw;
use quickjs_ffi::{JSContext, JSValue, JS_IsNumber, JS_ToInt64};
use std::{mem::MaybeUninit, os::raw::c_int};
pub(crate) fn arg_miss(ctx: *mut JSContext, argc: c_int, need: c_int) -> Result<(), JSValue> {
if argc < need {
throw(ctx, format!("miss : args need {need} pass {argc}"))?
}
Ok(())
}
pub(crate) fn arg_i64(ctx: *mut JSContext, argv: *mut JSValue, pos: isize) -> Result<i64, JSValue> {
unsafe {
let val = *argv.offset(pos);
if JS_IsNumber(val) == 0 {
throw(ctx, format!("not number : args position {pos}"))?
}
let mut n = MaybeUninit::uninit();
JS_ToInt64(ctx, n.as_mut_ptr() as _, val);
Ok(n.assume_init())
}
}
use quickjs_ffi::{JSContext, JSValue, JS_NewInt64, JS_NewString, JS_NULL, JS_UNDEFINED};
use std::ffi::CString;
pub enum Val {
None,
Undefined,
I64(i64),
CString(CString),
}
impl From<()> for Val {
fn from(_: ()) -> Self {
Val::Undefined
}
}
impl From<i64> for Val {
fn from(t: i64) -> Self {
Val::I64(t)
}
}
impl From<CString> for Val {
fn from(t: CString) -> Self {
Val::CString(t)
}
}
pub(crate) fn val(ctx: *mut JSContext, t: impl Into<Val>) -> JSValue {
match t.into() {
Val::None => JS_NULL,
Val::Undefined => JS_UNDEFINED,
Val::I64(n) => unsafe { JS_NewInt64(ctx, n) },
Val::CString(cstr) => unsafe { JS_NewString(ctx, cstr.as_ptr()) },
}
}
pub use libquickjs_sys::*;
aadressile
use crate::js::{self, arg};
use quickjs_ffi::{JSContext, JSValue};
use rust_macro::js;
use std::os::raw::c_int;
#[js]
pub fn fib(n: i64) -> i64 {
if n <= 1 {
return if n == 1 { 1 } else { 0 };
}
n + fib(n - 1)
}
#[no_mangle]
pub extern "C" fn js_fib(
ctx: *mut JSContext,
_this: JSValue,
argc: c_int,
argv: *mut JSValue,
) -> JSValue {
if let Err(err) = arg::arg_miss(ctx, argc, fib_args_len) {
return err;
}
match arg::arg_i64(ctx, argv, 0) {
Err(err) => err,
Ok(n) => js::val(ctx, fib(n)),
}
}
use crate::{js::arg, qjs::run};
use async_io::Timer;
use quickjs_ffi::{JSContext, JSValue};
use rust_macro::js;
use std::{os::raw::c_int, time::Duration};
#[js]
pub async fn sleep(n: u64) {
Timer::after(Duration::from_millis(n)).await;
}
#[no_mangle]
pub extern "C" fn js_sleep(
ctx: *mut JSContext,
_this: JSValue,
argc: c_int,
argv: *mut JSValue,
) -> JSValue {
if let Err(err) = arg::arg_miss(ctx, argc, sleep_args_len) {
return err;
}
match arg::arg_i64(ctx, argv, 0) {
Err(err) => err,
Ok(n) => run(ctx, async move {
sleep(n as u64).await;
Ok(())
}),
}
}
use crate::js::throw;
use quickjs_ffi::{JSContext, JSValue, JS_IsNumber, JS_ToInt64};
use std::{mem::MaybeUninit, os::raw::c_int};
pub(crate) fn arg_miss(ctx: *mut JSContext, argc: c_int, need: c_int) -> Result<(), JSValue> {
if argc < need {
throw(ctx, format!("miss : args need {need} pass {argc}"))?
}
Ok(())
}
pub(crate) fn arg_i64(ctx: *mut JSContext, argv: *mut JSValue, pos: isize) -> Result<i64, JSValue> {
unsafe {
let val = *argv.offset(pos);
if JS_IsNumber(val) == 0 {
throw(ctx, format!("not number : args position {pos}"))?
}
let mut n = MaybeUninit::uninit();
JS_ToInt64(ctx, n.as_mut_ptr() as _, val);
Ok(n.assume_init())
}
}
use quickjs_ffi::{JSContext, JSValue, JS_NewInt64, JS_NewString, JS_NULL, JS_UNDEFINED};
use std::ffi::CString;
pub enum Val {
None,
Undefined,
I64(i64),
CString(CString),
}
impl From<()> for Val {
fn from(_: ()) -> Self {
Val::Undefined
}
}
impl From<i64> for Val {
fn from(t: i64) -> Self {
Val::I64(t)
}
}
impl From<CString> for Val {
fn from(t: CString) -> Self {
Val::CString(t)
}
}
pub(crate) fn val(ctx: *mut JSContext, t: impl Into<Val>) -> JSValue {
match t.into() {
Val::None => JS_NULL,
Val::Undefined => JS_UNDEFINED,
Val::I64(n) => unsafe { JS_NewInt64(ctx, n) },
Val::CString(cstr) => unsafe { JS_NewString(ctx, cstr.as_ptr()) },
}
}
pub use libquickjs_sys::*;
#![allow(non_upper_case_globals)]
#![allow(non_camel_case_types)]
#![allow(non_snake_case)]
include!(concat!(env!("OUT_DIR"), "/c.rs"));
Undefined symbols for architecture x86_64: "_JS_ToInt32"
Muuda '. /rust/Cargo.toml' järgmiselt, säilitades ainult staticlib'i.
[lib]
#crate-type = ["lib", "cdylib", "staticlib"]
crate-type = ["staticlib"]
Viited
- Alates
JS
mootorist kuniJS
runtime'ini (üleval) (all) - Natiivse mooduli arendamine
QuickJS
jaoks C keeles - Kasutage Rusti JS API rakendamiseks
- QuickJS näited
- roost-bindgen
- Kuidas luua asünkroonne kood
QuickJS
jaoks. - rquickjs → JS_NewPromiseCapability
- wasmedge-quickjs → new_promise
- wasmedge-quickjs → JsMethod
- wasmedge-quickjs → kõne
- Märkamatu lõks - lukud Rustis
kohta
See projekt on osa rmw.link ( rmw.link ) koodiprojektist.