Skip to content

Commit 33db13e

Browse files
committed
feat: add compileJsToBytecode
1 parent d7b8ac5 commit 33db13e

File tree

10 files changed

+160
-38
lines changed

10 files changed

+160
-38
lines changed

app/src/main/assets/index.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
const fs = require("fs");
2+
3+
const buffer = fs.readFileSync("out.c");
4+
5+
console.log(buffer)
6+
7+
fs.writeFileSync("test.kbc1", buffer);

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

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
package com.shiqi.testquickjs
22

33
import android.os.Bundle
4+
import android.util.Log
45
import androidx.activity.ComponentActivity
56
import androidx.activity.compose.setContent
67
import androidx.compose.foundation.layout.fillMaxSize
8+
import androidx.compose.material3.Button
79
import androidx.compose.material3.MaterialTheme
810
import androidx.compose.material3.Surface
911
import androidx.compose.material3.Text
@@ -13,15 +15,14 @@ import androidx.compose.ui.tooling.preview.Preview
1315
import com.shiqi.testquickjs.ui.theme.QuickJSTheme
1416

1517
class MainActivity : ComponentActivity() {
18+
companion object {
19+
const val TAG = "QuickJs"
20+
}
1621
override fun onCreate(savedInstanceState: Bundle?) {
1722
super.onCreate(savedInstanceState)
1823

1924
val quickJSEngine = QuickJsEngine(baseContext)
2025
quickJSEngine.init()
21-
quickJSEngine.runJsFileFromAsset("asset:/JsEngineSonicBridge.js")
22-
quickJSEngine.runJsFileFromAsset("asset:/sonic.kbc1")
23-
quickJSEngine.runJsFileFromAsset("asset:/test.kbc1")
24-
quickJSEngine.runJsFileFromAsset("asset:/sonic.js")
2526
// quickJSEngine.runJsFileFromAsset("asset:/test.js")
2627

2728
setContent {
@@ -32,6 +33,17 @@ class MainActivity : ComponentActivity() {
3233
color = MaterialTheme.colorScheme.background
3334
) {
3435
Greeting("Android")
36+
Button(onClick = {
37+
quickJSEngine.runJsFileFromAsset("asset:/JsEngineSonicBridge.js")
38+
// quickJSEngine.runJsFileFromAsset("asset:/sonic.kbc1")
39+
quickJSEngine.runJsFileFromAsset("asset:/sonic.js")
40+
val bytes = quickJSEngine.getJsContext().compileJsToBytecode("console.log('Hello, Shiqi!')")
41+
quickJSEngine.getJsContext().evaluateBytecode(bytes)
42+
43+
Log.i(TAG, "bytes: ${bytes?.size}")
44+
}) {
45+
Text("Click Me")
46+
}
3547
}
3648
}
3749
}

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

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,10 @@ class QuickJsEngine(private val context: Context) {
7070
Log.i(TAG, "init: quick js init succeed, cost=${System.currentTimeMillis() - start}")
7171
}
7272

73+
fun getJsContext(): JSContext {
74+
return jsContext
75+
}
76+
7377
/**
7478
* Executes a JS script or bytecode file.
7579
* @param filePath JS script or bytecode file directory.
@@ -101,13 +105,14 @@ class QuickJsEngine(private val context: Context) {
101105
Log.e(TAG, "runFile: script is null or empty")
102106
return
103107
}
108+
val bytes = jsContext.compileJsToBytecode(script)
104109
val start = System.currentTimeMillis()
105110
Log.i(TAG, "runFile: start at $start")
106111
if (filePath.contains("source")) {
107112
Log.i(TAG, "runFile: ${script.length}")
108113
}
109114
try {
110-
jsContext.evaluate(script, "file.js")
115+
jsContext.evaluateBytecode(bytes)
111116
Log.i(TAG, "runFile: run js finished")
112117
} catch (e: Throwable) {
113118
Log.e(TAG, "runFile: failed to run js", e)

quickjs-android/CMakeLists.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,5 +25,6 @@ else ()
2525
endif (LEAK_TRIGGER)
2626

2727
add_library(quickjs-android SHARED ${QUICKJS_ANDROID_SOURCES})
28+
find_library(log-lib log)
2829
target_compile_options(quickjs-android PRIVATE ${COMMON_FLAGS})
29-
target_link_libraries(quickjs-android PRIVATE quickjs mimalloc)
30+
target_link_libraries(quickjs-android PRIVATE quickjs mimalloc ${log-lib})

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

Lines changed: 60 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,16 @@
1212
#define MSG_NULL_JS_CONTEXT "Null JSContext"
1313
#define MSG_NULL_JS_VALUE "Null JSValue"
1414

15+
#ifndef NODE_GYP
16+
#include <android/log.h>
17+
#define LOG_TAG "QuickJs"
18+
#define ALOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG,__VA_ARGS__)
19+
#define ALOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG,__VA_ARGS__)
20+
#else
21+
#define ALOGI(...) ((void)0)
22+
#define ALOGE(...) ((void)0)
23+
#endif
24+
1525
static jmethodID on_interrupt_method;
1626

1727
typedef struct InterruptData {
@@ -1158,7 +1168,8 @@ Java_com_shiqi_quickjs_QuickJS_evaluateBytecode(
11581168
JNIEnv *env,
11591169
jclass __unused clazz,
11601170
jlong context,
1161-
jbyteArray bytecode
1171+
jbyteArray bytecode,
1172+
jint flags
11621173
) {
11631174
JSContext *ctx = (JSContext *) context;
11641175
CHECK_NULL(env, ctx, MSG_NULL_JS_CONTEXT);
@@ -1179,6 +1190,54 @@ Java_com_shiqi_quickjs_QuickJS_evaluateBytecode(
11791190
}
11801191
}
11811192

1193+
JNIEXPORT jbyteArray JNICALL
1194+
Java_com_shiqi_quickjs_QuickJS_compileJsToBytecode(
1195+
JNIEnv *env,
1196+
jclass __unused clazz,
1197+
jlong context,
1198+
jstring code
1199+
) {
1200+
JSContext *ctx = (JSContext *) context;
1201+
CHECK_NULL_RET(env, ctx, MSG_NULL_JS_CONTEXT);
1202+
CHECK_NULL_RET(env, code, "Null code");
1203+
1204+
const char *code_str = (*env)->GetStringUTFChars(env, code, NULL);
1205+
1206+
// Create a JSValue that holds the compiled bytecode
1207+
JSValue compiled = JS_Eval(ctx, code_str, strlen(code_str), "<unknown>",
1208+
JS_EVAL_TYPE_GLOBAL | JS_EVAL_FLAG_COMPILE_ONLY);
1209+
1210+
// Release the code_str
1211+
(*env)->ReleaseStringUTFChars(env, code, code_str);
1212+
1213+
if (JS_IsException(compiled)) {
1214+
JSValue error = JS_GetException(ctx);
1215+
ALOGE("error: %s", JS_ToCString(ctx, error));
1216+
return NULL;
1217+
}
1218+
1219+
// Write the bytecode to a buffer
1220+
size_t byteCodeLength;
1221+
uint8_t *bytes = JS_WriteObject(ctx, &byteCodeLength, compiled, JS_WRITE_OBJ_BYTECODE);
1222+
1223+
// Free the compiled JSValue
1224+
JS_FreeValue(ctx, compiled);
1225+
1226+
// Create a new Java byte array
1227+
jbyteArray result = (*env)->NewByteArray(env, byteCodeLength);
1228+
if (result == NULL) {
1229+
return NULL; // OutOfMemoryError already thrown
1230+
}
1231+
1232+
// Move the bytes from C++ to Java
1233+
(*env)->SetByteArrayRegion(env, result, 0, byteCodeLength, (jbyte*)bytes);
1234+
1235+
// Free the bytes buffer
1236+
js_free(ctx, bytes);
1237+
1238+
return result;
1239+
}
1240+
11821241
JNIEXPORT jint JNICALL
11831242
Java_com_shiqi_quickjs_QuickJS_executePendingJob(JNIEnv *env, jclass clazz, jlong context) {
11841243
JSContext *ctx = (JSContext *) context;

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

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717

1818
package com.shiqi.quickjs;
1919

20+
import android.util.Log;
21+
2022
import androidx.annotation.Nullable;
2123

2224
import java.io.Closeable;
@@ -69,6 +71,7 @@ public class JSContext implements Closeable {
6971
final QuickJS quickJS;
7072
final JSRuntime jsRuntime;
7173
private final NativeCleaner<JSValue> cleaner;
74+
private static final String TAG = "QuickJs JSContext";
7275

7376
JSContext(long pointer, QuickJS quickJS, JSRuntime jsRuntime) {
7477
this.pointer = pointer;
@@ -106,6 +109,16 @@ public void evaluateBytecode(byte[] bytecode) {
106109
evaluateBytecodeInternal(bytecode, 0, null);
107110
}
108111

112+
/**
113+
* Compiles the given JavaScript code in this JSContext to bytecode.
114+
*/
115+
public byte[] compileJsToBytecode(String code) {
116+
synchronized (jsRuntime) {
117+
checkClosed();
118+
return QuickJS.compileJsToBytecode(pointer, code);
119+
}
120+
}
121+
109122
/**
110123
* Evaluates the script in this JSContext.
111124
*
@@ -192,7 +205,7 @@ private <T> T evaluateInternal(String script, String fileName, int type, int fla
192205
}
193206
}
194207

195-
private <T> T evaluateBytecodeInternal(byte[] bytecode, int flags, @Nullable TypeAdapter<T> adapter) {
208+
private <T> void evaluateBytecodeInternal(byte[] bytecode, int flags, @Nullable TypeAdapter<T> adapter) {
196209
if ((flags & (~EVAL_FLAG_MASK)) != 0) {
197210
throw new IllegalArgumentException("Invalid flags: " + flags);
198211
}
@@ -206,17 +219,16 @@ private <T> T evaluateBytecodeInternal(byte[] bytecode, int flags, @Nullable Typ
206219

207220
if (adapter != null) {
208221
JSValue jsValue = wrapAsJSValue(value);
209-
return adapter.fromJSValue(this, jsValue);
222+
adapter.fromJSValue(this, jsValue);
210223
} else {
211224
// Only check exception
212225
try {
213-
if (QuickJS.getValueTag(value) == TYPE_EXCEPTION) {
214-
throw new JSEvaluationException(QuickJS.getException(pointer));
215-
}
226+
throw new JSEvaluationException(QuickJS.getException(pointer));
227+
} catch (Exception e) {
228+
Log.e(TAG, "evaluateBytecodeInternal: " + e.getMessage());
216229
} finally {
217230
QuickJS.destroyValue(pointer, value);
218231
}
219-
return null;
220232
}
221233
}
222234
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,5 +170,6 @@ public QuickJS build() {
170170
static native long evaluate(long context, String sourceCode, String fileName, int flags);
171171

172172
static native void evaluateBytecode(long context, byte[] bytecode, int flags);
173+
static native byte[] compileJsToBytecode(long context, String code);
173174
static native int executePendingJob(long context);
174175
}

quickjs/quickjs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
quickjs

quickjs/src/core/bytecode.c

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,16 @@
3838
#include "shape.h"
3939
#include "string.h"
4040

41+
#ifndef NODE_GYP
42+
#include <android/log.h>
43+
#define LOG_TAG "QuickJs"
44+
#define ALOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG,__VA_ARGS__)
45+
#define ALOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG,__VA_ARGS__)
46+
#else
47+
#define ALOGI(...) ((void)0)
48+
#define ALOGE(...) ((void)0)
49+
#endif
50+
4151
void free_function_bytecode(JSRuntime* rt, JSFunctionBytecode* b) {
4252
int i;
4353

@@ -581,9 +591,9 @@ static int JS_WriteFunctionTag(BCWriterState* s, JSValueConst obj) {
581591
bc_put_leb128(s, b->debug.line_num);
582592
bc_put_leb128(s, b->debug.pc2line_len);
583593
dbuf_put(&s->dbuf, b->debug.pc2line_buf, b->debug.pc2line_len);
584-
/**
594+
/**
585595
* purely for compatibility with WebF/Kraken V1 quickjs compiler (kbc1 file format).
586-
* determination of whether a column number is available by
596+
* determination of whether a column number is available by
587597
* adding a special sequence of characters.
588598
*/
589599
dbuf_putc(&s->dbuf, 255);
@@ -594,9 +604,9 @@ static int JS_WriteFunctionTag(BCWriterState* s, JSValueConst obj) {
594604
bc_put_leb128(s, b->debug.pc2column_len);
595605
dbuf_put(&s->dbuf, b->debug.pc2column_buf, b->debug.pc2column_len);
596606

597-
/**
607+
/**
598608
* purely for compatibility with WebF/Kraken V1 quickjs compiler (kbc1 file format).
599-
* determination of whether a Self PolyIC is available by
609+
* determination of whether a Self PolyIC is available by
600610
* adding a special sequence of characters.
601611
*/
602612
dbuf_putc(&s->dbuf, 255);
@@ -2105,6 +2115,7 @@ static int JS_ReadObjectAtoms(BCReaderState* s) {
21052115

21062116
if (bc_get_u8(s, &v8))
21072117
return -1;
2118+
ALOGI("JS_ReadObjectAtoms v8=%d BC_VERSION=%d", v8, BC_VERSION);
21082119
/* XXX: could support byte swapped input */
21092120
if (v8 != BC_VERSION) {
21102121
JS_ThrowSyntaxError(s->ctx, "invalid version (%d expected=%d)", v8, BC_VERSION);
@@ -2173,4 +2184,4 @@ JSValue JS_ReadObject(JSContext* ctx, const uint8_t* buf, size_t buf_len, int fl
21732184
}
21742185
bc_reader_free(s);
21752186
return obj;
2176-
}
2187+
}

0 commit comments

Comments
 (0)