add compile script
This commit is contained in:
100
compile.ce
Normal file
100
compile.ce
Normal file
@@ -0,0 +1,100 @@
|
||||
// compile.ce — compile a .cm module to native .dylib via QBE
|
||||
//
|
||||
// Usage:
|
||||
// cell --core . compile.ce <file.cm>
|
||||
//
|
||||
// Produces <file>.dylib in the current directory.
|
||||
|
||||
var fd = use('fd')
|
||||
var os = use('os')
|
||||
|
||||
if (length(args) < 1) {
|
||||
print('usage: cell --core . compile.ce <file.cm>')
|
||||
return
|
||||
}
|
||||
|
||||
var file = args[0]
|
||||
var base = file
|
||||
if (ends_with(base, '.cm')) {
|
||||
base = text(base, 0, length(base) - 3)
|
||||
}
|
||||
|
||||
var safe = replace(replace(base, '/', '_'), '-', '_')
|
||||
var symbol = 'js_' + safe + '_use'
|
||||
var tmp = '/tmp/qbe_' + safe
|
||||
var ssa_path = tmp + '.ssa'
|
||||
var s_path = tmp + '.s'
|
||||
var o_path = tmp + '.o'
|
||||
var rt_o_path = '/tmp/qbe_rt.o'
|
||||
var dylib_path = base + '.dylib'
|
||||
var cwd = fd.getcwd()
|
||||
var rc = 0
|
||||
|
||||
// Step 1: emit QBE IL
|
||||
print('emit qbe...')
|
||||
rc = os.system('cd ' + cwd + ' && ./cell --core . --emit-qbe ' + file + ' > ' + ssa_path)
|
||||
if (rc != 0) {
|
||||
print('failed to emit qbe il')
|
||||
return
|
||||
}
|
||||
|
||||
// Step 2: post-process — insert dead labels after ret/jmp, append wrapper
|
||||
// Use awk via shell to avoid blob/slurpwrite issues with long strings
|
||||
print('post-process...')
|
||||
var awk_cmd = `awk '
|
||||
/^[[:space:]]*ret / || /^[[:space:]]*jmp / { need_label=1; print; next }
|
||||
need_label && /^[[:space:]]*[^@}]/ && NF > 0 {
|
||||
print "@_dead_" dead_id; dead_id++; need_label=0
|
||||
}
|
||||
/^@/ || /^}/ || NF==0 { need_label=0 }
|
||||
{ print }
|
||||
' ` + ssa_path + ` > ` + tmp + `_fixed.ssa`
|
||||
rc = os.system(awk_cmd)
|
||||
if (rc != 0) {
|
||||
print('post-process failed')
|
||||
return
|
||||
}
|
||||
|
||||
// Append wrapper function
|
||||
var wrapper_cmd = `printf '\nexport function l $` + symbol + `(l %%ctx) {\n@entry\n %%frame =l alloc8 4096\n %%result =l call $cell_main(l %%ctx, l %%frame)\n ret %%result\n}\n' >> ` + tmp + `_fixed.ssa`
|
||||
rc = os.system(wrapper_cmd)
|
||||
if (rc != 0) {
|
||||
print('wrapper append failed')
|
||||
return
|
||||
}
|
||||
|
||||
// Step 3: compile QBE IL to assembly
|
||||
print('qbe compile...')
|
||||
rc = os.system('~/.local/bin/qbe -o ' + s_path + ' ' + tmp + '_fixed.ssa')
|
||||
if (rc != 0) {
|
||||
print('qbe compilation failed')
|
||||
return
|
||||
}
|
||||
|
||||
// Step 4: assemble
|
||||
print('assemble...')
|
||||
rc = os.system('cc -c ' + s_path + ' -o ' + o_path)
|
||||
if (rc != 0) {
|
||||
print('assembly failed')
|
||||
return
|
||||
}
|
||||
|
||||
// Step 5: compile runtime stubs (cached — skip if already built)
|
||||
if (!fd.is_file(rt_o_path)) {
|
||||
print('compile runtime stubs...')
|
||||
rc = os.system('cc -c ' + cwd + '/qbe_rt.c -o ' + rt_o_path + ' -fPIC')
|
||||
if (rc != 0) {
|
||||
print('runtime stubs compilation failed')
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Step 6: link dylib
|
||||
print('link...')
|
||||
rc = os.system('cc -shared -fPIC -undefined dynamic_lookup ' + o_path + ' ' + rt_o_path + ' -o ' + cwd + '/' + dylib_path)
|
||||
if (rc != 0) {
|
||||
print('linking failed')
|
||||
return
|
||||
}
|
||||
|
||||
print('built: ' + dylib_path)
|
||||
154
qbe_rt.c
Normal file
154
qbe_rt.c
Normal file
@@ -0,0 +1,154 @@
|
||||
/*
|
||||
* qbe_rt.c - Runtime support for QBE-compiled ƿit modules
|
||||
*
|
||||
* Provides non-inline versions of static-inline quickjs functions
|
||||
* (which QBE-generated code calls as external symbols) and stub
|
||||
* implementations of cell_rt_* helper functions.
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <math.h>
|
||||
|
||||
typedef uint64_t JSValue;
|
||||
typedef struct JSContext JSContext;
|
||||
|
||||
#define JS_TAG_SHORT_FLOAT 5
|
||||
#define JS_TAG_NULL 7
|
||||
#define JS_VAL_NULL 7
|
||||
|
||||
/* ============================================================
|
||||
Non-inline wrappers for static-inline quickjs functions
|
||||
============================================================ */
|
||||
|
||||
/*
|
||||
* __JS_NewFloat64 — encode double as tagged JSValue
|
||||
* Short float: [sign:1][exp:8][mantissa:52][tag:3]
|
||||
* Returns tagged int if value is an exact integer in int32 range
|
||||
*/
|
||||
JSValue __JS_NewFloat64(JSContext *ctx, double d) {
|
||||
union { double d; uint64_t u; } u;
|
||||
u.d = d;
|
||||
|
||||
uint64_t sign = u.u >> 63;
|
||||
int exp = (u.u >> 52) & 0x7FF;
|
||||
uint64_t mantissa = u.u & ((1ULL << 52) - 1);
|
||||
|
||||
/* Zero */
|
||||
if (exp == 0 && mantissa == 0)
|
||||
return JS_TAG_SHORT_FLOAT;
|
||||
/* NaN/Inf → null */
|
||||
if (exp == 0x7FF)
|
||||
return JS_VAL_NULL;
|
||||
/* Subnormals → zero */
|
||||
if (exp == 0)
|
||||
return (sign << 63) | JS_TAG_SHORT_FLOAT;
|
||||
|
||||
int short_exp = exp - 1023 + 127;
|
||||
if (short_exp < 1 || short_exp > 254)
|
||||
return JS_VAL_NULL;
|
||||
|
||||
/* Prefer integer if exact */
|
||||
if (d >= (double)(-2147483647 - 1) && d <= (double)2147483647) {
|
||||
int32_t i = (int32_t)d;
|
||||
if ((double)i == d)
|
||||
return (uint64_t)(uint32_t)i << 1;
|
||||
}
|
||||
|
||||
return (sign << 63)
|
||||
| ((uint64_t)short_exp << 55)
|
||||
| (mantissa << 3)
|
||||
| JS_TAG_SHORT_FLOAT;
|
||||
}
|
||||
|
||||
/*
|
||||
* JS_IsNumber — check if value is tagged int or short float
|
||||
*/
|
||||
int JS_IsNumber(JSValue v) {
|
||||
int is_int = (v & 1) == 0;
|
||||
int is_float = (v & 7) == JS_TAG_SHORT_FLOAT;
|
||||
return is_int || is_float;
|
||||
}
|
||||
|
||||
/*
|
||||
* JS_NewString — create string from C string (wraps JS_NewStringLen)
|
||||
*/
|
||||
extern JSValue JS_NewStringLen(JSContext *ctx, const char *str, size_t len);
|
||||
|
||||
JSValue JS_NewString(JSContext *ctx, const char *str) {
|
||||
return JS_NewStringLen(ctx, str, strlen(str));
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
cell_rt_* stubs — error/fallback paths for QBE-compiled code
|
||||
These are called from type-mismatch branches that should not
|
||||
be reached in pure numeric code.
|
||||
============================================================ */
|
||||
|
||||
extern JSValue JS_ThrowTypeError(JSContext *ctx, const char *fmt, ...);
|
||||
|
||||
void cell_rt_disrupt(JSContext *ctx) {
|
||||
JS_ThrowTypeError(ctx, "type error in native code");
|
||||
}
|
||||
|
||||
JSValue cell_rt_lt_text(JSContext *ctx, JSValue a, JSValue b) {
|
||||
return JS_VAL_NULL;
|
||||
}
|
||||
JSValue cell_rt_gt_text(JSContext *ctx, JSValue a, JSValue b) {
|
||||
return JS_VAL_NULL;
|
||||
}
|
||||
JSValue cell_rt_le_text(JSContext *ctx, JSValue a, JSValue b) {
|
||||
return JS_VAL_NULL;
|
||||
}
|
||||
JSValue cell_rt_ge_text(JSContext *ctx, JSValue a, JSValue b) {
|
||||
return JS_VAL_NULL;
|
||||
}
|
||||
JSValue cell_rt_eq_tol(JSContext *ctx, JSValue a, JSValue b) {
|
||||
return JS_VAL_NULL;
|
||||
}
|
||||
JSValue cell_rt_ne_tol(JSContext *ctx, JSValue a, JSValue b) {
|
||||
return JS_VAL_NULL;
|
||||
}
|
||||
|
||||
JSValue cell_rt_get_intrinsic(JSContext *ctx, const char *name) {
|
||||
return JS_VAL_NULL;
|
||||
}
|
||||
JSValue cell_rt_load_field(JSContext *ctx, JSValue obj, const char *name) {
|
||||
return JS_VAL_NULL;
|
||||
}
|
||||
JSValue cell_rt_load_dynamic(JSContext *ctx, JSValue obj, JSValue key) {
|
||||
return JS_VAL_NULL;
|
||||
}
|
||||
JSValue cell_rt_load_index(JSContext *ctx, JSValue arr, JSValue idx) {
|
||||
return JS_VAL_NULL;
|
||||
}
|
||||
void cell_rt_store_field(JSContext *ctx, JSValue val, JSValue obj,
|
||||
const char *name) {}
|
||||
void cell_rt_store_dynamic(JSContext *ctx, JSValue val, JSValue obj,
|
||||
JSValue key) {}
|
||||
void cell_rt_store_index(JSContext *ctx, JSValue val, JSValue arr,
|
||||
JSValue idx) {}
|
||||
JSValue cell_rt_get_closure(JSContext *ctx, void *fp, int64_t depth,
|
||||
int64_t index) {
|
||||
return JS_VAL_NULL;
|
||||
}
|
||||
void cell_rt_put_closure(JSContext *ctx, void *fp, JSValue val, int64_t depth,
|
||||
int64_t index) {}
|
||||
JSValue cell_rt_frame(JSContext *ctx, JSValue fn, int64_t nargs) {
|
||||
return JS_VAL_NULL;
|
||||
}
|
||||
void cell_rt_setarg(JSValue frame, int64_t idx, JSValue val) {}
|
||||
JSValue cell_rt_invoke(JSContext *ctx, JSValue frame) { return JS_VAL_NULL; }
|
||||
JSValue cell_rt_goframe(JSContext *ctx, JSValue fn, int64_t nargs) {
|
||||
return JS_VAL_NULL;
|
||||
}
|
||||
void cell_rt_goinvoke(JSContext *ctx, JSValue frame) {}
|
||||
JSValue cell_rt_make_function(JSContext *ctx, int64_t fn_idx) {
|
||||
return JS_VAL_NULL;
|
||||
}
|
||||
void cell_rt_push(JSContext *ctx, JSValue arr, JSValue val) {}
|
||||
JSValue cell_rt_pop(JSContext *ctx, JSValue arr) { return JS_VAL_NULL; }
|
||||
JSValue cell_rt_delete(JSContext *ctx, JSValue obj, JSValue key) {
|
||||
return JS_VAL_NULL;
|
||||
}
|
||||
JSValue cell_rt_typeof(JSContext *ctx, JSValue val) { return JS_VAL_NULL; }
|
||||
69
run_native.ce
Normal file
69
run_native.ce
Normal file
@@ -0,0 +1,69 @@
|
||||
// run_native.ce — load a module both interpreted and native, compare speed
|
||||
//
|
||||
// Usage:
|
||||
// cell --core . run_native.ce <module>
|
||||
//
|
||||
// Loads <module>.cm via use() (interpreted) and <module>.dylib (native),
|
||||
// runs both and compares results and timing.
|
||||
|
||||
var os = use('os')
|
||||
|
||||
if (length(args) < 1) {
|
||||
print('usage: cell --core . run_native.ce <module>')
|
||||
print(' e.g. cell --core . run_native.ce num_torture')
|
||||
return
|
||||
}
|
||||
|
||||
var name = args[0]
|
||||
if (ends_with(name, '.cm')) {
|
||||
name = text(name, 0, length(name) - 3)
|
||||
}
|
||||
|
||||
var safe = replace(replace(name, '/', '_'), '-', '_')
|
||||
var symbol = 'js_' + safe + '_use'
|
||||
var dylib_path = './' + name + '.dylib'
|
||||
var fd = use('fd')
|
||||
|
||||
// --- Interpreted run ---
|
||||
print('--- interpreted ---')
|
||||
var t1 = os.now()
|
||||
var result_interp = use(name)
|
||||
var t2 = os.now()
|
||||
var ms_interp = (t2 - t1) / 1000000
|
||||
print('result: ' + text(result_interp))
|
||||
print('time: ' + text(ms_interp) + ' ms')
|
||||
|
||||
// --- Native run ---
|
||||
if (!fd.is_file(dylib_path)) {
|
||||
print('\nno ' + dylib_path + ' found — run compile.ce first')
|
||||
return
|
||||
}
|
||||
|
||||
print('\n--- native ---')
|
||||
var t3 = os.now()
|
||||
var lib = os.dylib_open(dylib_path)
|
||||
var t4 = os.now()
|
||||
var result_native = os.dylib_symbol(lib, symbol)
|
||||
var t5 = os.now()
|
||||
var ms_load = (t4 - t3) / 1000000
|
||||
var ms_exec = (t5 - t4) / 1000000
|
||||
var ms_native = (t5 - t3) / 1000000
|
||||
print('result: ' + text(result_native))
|
||||
print('load: ' + text(ms_load) + ' ms')
|
||||
print('exec: ' + text(ms_exec) + ' ms')
|
||||
print('total: ' + text(ms_native) + ' ms')
|
||||
|
||||
// --- Comparison ---
|
||||
print('\n--- comparison ---')
|
||||
var match = result_interp == result_native
|
||||
var speedup = 0
|
||||
var speedup_exec = 0
|
||||
print('match: ' + text(match))
|
||||
if (ms_native > 0) {
|
||||
speedup = ms_interp / ms_native
|
||||
print('speedup: ' + text(speedup) + 'x (total)')
|
||||
}
|
||||
if (ms_exec > 0) {
|
||||
speedup_exec = ms_interp / ms_exec
|
||||
print('speedup: ' + text(speedup_exec) + 'x (exec only)')
|
||||
}
|
||||
Reference in New Issue
Block a user