Skip to content

Commit d7b8ac5

Browse files
committed
feat: support bytecode kbc1 format
1 parent 45748e8 commit d7b8ac5

File tree

11 files changed

+163
-72
lines changed

11 files changed

+163
-72
lines changed

app/src/androidTest/java/com/shiqi/testquickjs/ExampleInstrumentedTest.kt

Lines changed: 0 additions & 24 deletions
This file was deleted.

app/src/main/assets/sonic.kbc1

231 KB
Binary file not shown.

app/src/main/assets/test.kbc1

83 Bytes
Binary file not shown.

app/src/main/java/com/shiqi/testquickjs/MainActivity.kt

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package com.shiqi.testquickjs
22

33
import android.os.Bundle
4-
import android.util.Log
54
import androidx.activity.ComponentActivity
65
import androidx.activity.compose.setContent
76
import androidx.compose.foundation.layout.fillMaxSize
@@ -11,7 +10,6 @@ import androidx.compose.material3.Text
1110
import androidx.compose.runtime.Composable
1211
import androidx.compose.ui.Modifier
1312
import androidx.compose.ui.tooling.preview.Preview
14-
import com.shiqi.quickjs.QuickJS
1513
import com.shiqi.testquickjs.ui.theme.QuickJSTheme
1614

1715
class MainActivity : ComponentActivity() {
@@ -20,9 +18,11 @@ class MainActivity : ComponentActivity() {
2018

2119
val quickJSEngine = QuickJsEngine(baseContext)
2220
quickJSEngine.init()
23-
quickJSEngine.runJsFile("asset:/JsEngineSonicBridge.js", true)
24-
quickJSEngine.runJsFile("asset:/sonic.js", true)
25-
quickJSEngine.runJsFile("asset:/test.js", true)
21+
quickJSEngine.runJsFileFromAsset("asset:/JsEngineSonicBridge.js")
22+
quickJSEngine.runJsFileFromAsset("asset:/sonic.kbc1")
23+
quickJSEngine.runJsFileFromAsset("asset:/test.kbc1")
24+
quickJSEngine.runJsFileFromAsset("asset:/sonic.js")
25+
// quickJSEngine.runJsFileFromAsset("asset:/test.js")
2626

2727
setContent {
2828
QuickJSTheme {

app/src/main/java/com/shiqi/testquickjs/QuickJsEngine.kt

Lines changed: 68 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -71,34 +71,49 @@ class QuickJsEngine(private val context: Context) {
7171
}
7272

7373
/**
74-
* 执行 JS 脚本的方法实现
75-
* @param jsFilePath JS 脚本文件的目录
76-
* @param isAssetFile 是否为 Asset 目录中的文件
77-
* @param
74+
* Executes a JS script or bytecode file.
75+
* @param filePath JS script or bytecode file directory.
7876
*/
79-
fun runJsFile(jsFilePath: String, isAssetFile: Boolean) {
80-
Log.i(TAG, "runJsFile: jsFilePath=$jsFilePath")
81-
val script = if (isAssetFile || jsFilePath.startsWith(URI_SCHEME_ASSETS)) {
82-
loadScriptFromAsset(jsFilePath)
77+
fun runJsFileFromAsset(filePath: String) {
78+
Log.i(TAG, "runFile: filePath=$filePath")
79+
val isBytecode = filePath.endsWith(".kbc1")
80+
81+
if (isBytecode) {
82+
val bytecode = loadBytecodeFromAsset(filePath)
83+
if (bytecode == null || bytecode.isEmpty()) {
84+
Log.e(TAG, "runFile: bytecode is null or empty")
85+
return
86+
}
87+
val start = System.currentTimeMillis()
88+
Log.i(TAG, "runFile: start at $start")
89+
try {
90+
jsContext.evaluateBytecode(bytecode)
91+
Log.i(TAG, "runFile: run bytecode finished")
92+
} catch (e: Throwable) {
93+
Log.e(TAG, "runFile: failed to run bytecode", e)
94+
} finally {
95+
val currentTime = System.currentTimeMillis()
96+
Log.i(TAG, "runFile: currentTime: ${currentTime}, cost ${currentTime - start} ms")
97+
}
8398
} else {
84-
loadScriptFromFile(jsFilePath)
85-
}
86-
if (script.isNullOrEmpty()) {
87-
Log.e(TAG, "runJsFile: script is null or empty")
88-
return
89-
}
90-
val start = System.currentTimeMillis()
91-
Log.i(TAG, "runJsFile: start at $start")
92-
if (jsFilePath.contains("source")) {
93-
Log.i(TAG, "runJsFile: ${script.length}")
94-
}
95-
try {
96-
jsContext.evaluate(script, "file.js")
97-
Log.i(TAG, "runJsFile: run js finished")
98-
} catch (e: Throwable) {
99-
Log.e(TAG, "runJsFile: failed to run js", e)
100-
} finally {
101-
Log.i(TAG, "runJsFile: cost ${System.currentTimeMillis() - start} ms")
99+
val script = loadScriptFromAsset(filePath)
100+
if (script.isNullOrEmpty()) {
101+
Log.e(TAG, "runFile: script is null or empty")
102+
return
103+
}
104+
val start = System.currentTimeMillis()
105+
Log.i(TAG, "runFile: start at $start")
106+
if (filePath.contains("source")) {
107+
Log.i(TAG, "runFile: ${script.length}")
108+
}
109+
try {
110+
jsContext.evaluate(script, "file.js")
111+
Log.i(TAG, "runFile: run js finished")
112+
} catch (e: Throwable) {
113+
Log.e(TAG, "runFile: failed to run js", e)
114+
} finally {
115+
Log.i(TAG, "runFile: cost ${System.currentTimeMillis() - start} ms")
116+
}
102117
}
103118
}
104119

@@ -165,6 +180,33 @@ class QuickJsEngine(private val context: Context) {
165180
}
166181
}
167182

183+
/**
184+
* Loads bytecode from an asset.
185+
* @param fileName File name.
186+
*/
187+
private fun loadBytecodeFromAsset(fileName: String): ByteArray? {
188+
val start = System.currentTimeMillis()
189+
Log.i(TAG, "loadBytecodeFromAsset: start at $start")
190+
return if (!fileName.startsWith(URI_SCHEME_ASSETS)) {
191+
Log.e(TAG, "loadBytecodeFromAsset: failed to load bytecode from asset")
192+
null
193+
} else {
194+
val realFileName = fileName.replaceFirst(URI_SCHEME_ASSETS, "")
195+
.replaceFirst(File.separator, "")
196+
try {
197+
val inputStream = context.assets.open(realFileName)
198+
val buffer = ByteArray(inputStream.available())
199+
inputStream.read(buffer)
200+
inputStream.close()
201+
Log.i(TAG, "loadBytecodeFromAsset: end cost ${System.currentTimeMillis() - start}ms")
202+
return buffer
203+
} catch (e: IOException) {
204+
Log.e(TAG, "loadBytecodeFromAsset: failed to load", e)
205+
return null
206+
}
207+
}
208+
}
209+
168210
fun callJs(callbackId: String, params: String) {
169211
jsHandler.post {
170212
val callJSFunction = try {

app/src/test/java/com/shiqi/testquickjs/ExampleUnitTest.kt

Lines changed: 0 additions & 17 deletions
This file was deleted.

quickjs-android/src/main/c/quickjs-jni.c

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1153,6 +1153,32 @@ Java_com_shiqi_quickjs_QuickJS_evaluate(
11531153
return (jlong) result;
11541154
}
11551155

1156+
JNIEXPORT void JNICALL
1157+
Java_com_shiqi_quickjs_QuickJS_evaluateBytecode(
1158+
JNIEnv *env,
1159+
jclass __unused clazz,
1160+
jlong context,
1161+
jbyteArray bytecode
1162+
) {
1163+
JSContext *ctx = (JSContext *) context;
1164+
CHECK_NULL(env, ctx, MSG_NULL_JS_CONTEXT);
1165+
CHECK_NULL(env, bytecode, "Null bytecode");
1166+
1167+
uint8_t *buf = NULL;
1168+
jsize buf_len = 0;
1169+
1170+
buf = (uint8_t *)(*env)->GetByteArrayElements(env, bytecode, NULL);
1171+
buf_len = (*env)->GetArrayLength(env, bytecode);
1172+
1173+
if (buf != NULL) {
1174+
JS_EvalBytecode(ctx, buf, buf_len);
1175+
}
1176+
1177+
if (buf != NULL) {
1178+
(*env)->ReleaseByteArrayElements(env, bytecode, (jbyte *)buf, 0);
1179+
}
1180+
}
1181+
11561182
JNIEXPORT jint JNICALL
11571183
Java_com_shiqi_quickjs_QuickJS_executePendingJob(JNIEnv *env, jclass clazz, jlong context) {
11581184
JSContext *ctx = (JSContext *) context;

quickjs-android/src/main/java/com/shiqi/quickjs/JSContext.java

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,13 @@ public void evaluate(String script, String fileName) {
9999
evaluateInternal(script, fileName, EVAL_TYPE_GLOBAL, 0, null);
100100
}
101101

102+
/**
103+
* Evaluates the bytecode in this JSContext.
104+
*/
105+
public void evaluateBytecode(byte[] bytecode) {
106+
evaluateBytecodeInternal(bytecode, 0, null);
107+
}
108+
102109
/**
103110
* Evaluates the script in this JSContext.
104111
*
@@ -185,6 +192,35 @@ private <T> T evaluateInternal(String script, String fileName, int type, int fla
185192
}
186193
}
187194

195+
private <T> T evaluateBytecodeInternal(byte[] bytecode, int flags, @Nullable TypeAdapter<T> adapter) {
196+
if ((flags & (~EVAL_FLAG_MASK)) != 0) {
197+
throw new IllegalArgumentException("Invalid flags: " + flags);
198+
}
199+
200+
synchronized (jsRuntime) {
201+
checkClosed();
202+
203+
QuickJS.evaluateBytecode(pointer, bytecode, flags);
204+
205+
long value = QuickJS.getGlobalObject(pointer);
206+
207+
if (adapter != null) {
208+
JSValue jsValue = wrapAsJSValue(value);
209+
return adapter.fromJSValue(this, jsValue);
210+
} else {
211+
// Only check exception
212+
try {
213+
if (QuickJS.getValueTag(value) == TYPE_EXCEPTION) {
214+
throw new JSEvaluationException(QuickJS.getException(pointer));
215+
}
216+
} finally {
217+
QuickJS.destroyValue(pointer, value);
218+
}
219+
return null;
220+
}
221+
}
222+
}
223+
188224
/**
189225
* Execute next pending job. Returns {@code false} if it has no pending job.
190226
*/

quickjs-android/src/main/java/com/shiqi/quickjs/QuickJS.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,5 +168,7 @@ public QuickJS build() {
168168
static native long getGlobalObject(long context);
169169

170170
static native long evaluate(long context, String sourceCode, String fileName, int flags);
171+
172+
static native void evaluateBytecode(long context, byte[] bytecode, int flags);
171173
static native int executePendingJob(long context);
172174
}

quickjs/include/quickjs/quickjs.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -758,6 +758,7 @@ JSValue JS_CallConstructor2(JSContext* ctx, JSValueConst func_obj, JSValueConst
758758
JS_BOOL JS_DetectModule(const char* input, size_t input_len);
759759
/* 'input' must be zero terminated i.e. input[input_len] = '\0'. */
760760
JSValue JS_Eval(JSContext* ctx, const char* input, size_t input_len, const char* filename, int eval_flags);
761+
JSValue JS_EvalBytecode(JSContext* ctx, const uint8_t *buf, size_t buf_len);
761762
/* same as JS_Eval() but with an explicit 'this_obj' parameter */
762763
JSValue JS_EvalThis(JSContext* ctx, JSValueConst this_obj, const char* input, size_t input_len, const char* filename, int eval_flags);
763764
JSValue JS_GetGlobalObject(JSContext* ctx);

0 commit comments

Comments
 (0)