qbe macros
This commit is contained in:
@@ -53,6 +53,7 @@ src += [ # core
|
||||
]
|
||||
|
||||
src += ['scheduler.c']
|
||||
src += ['qbe_helpers.c']
|
||||
|
||||
scripts = [
|
||||
'debug/js.c',
|
||||
|
||||
764
qbe.cm
Normal file
764
qbe.cm
Normal file
@@ -0,0 +1,764 @@
|
||||
// QBE IL Macro Layer
|
||||
// Maps mcode VM operations to QBE intermediate language fragments.
|
||||
// Each macro returns a backtick template string of QBE IL.
|
||||
// Convention: p = unique prefix for temporaries/labels, result in %{p}
|
||||
|
||||
// ============================================================
|
||||
// Constants
|
||||
// ============================================================
|
||||
|
||||
def js_null = 7
|
||||
def js_false = 3
|
||||
def js_true = 35
|
||||
def js_exception = 15
|
||||
def js_empty_text = 27
|
||||
|
||||
def int32_min = -2147483648
|
||||
def int32_max = 2147483647
|
||||
def mantissa_mask = 4503599627370495
|
||||
|
||||
// ============================================================
|
||||
// Type Checks — each takes (p, v), produces %{p} as w (0 or 1)
|
||||
// ============================================================
|
||||
|
||||
var is_int = function(p, v) {
|
||||
return ` %${p}.t =l and ${v}, 1
|
||||
%${p} =w ceql %${p}.t, 0
|
||||
`
|
||||
}
|
||||
|
||||
var is_number = function(p, v) {
|
||||
return ` %${p}.t1 =l and ${v}, 1
|
||||
%${p}.ii =w ceql %${p}.t1, 0
|
||||
%${p}.t2 =l and ${v}, 7
|
||||
%${p}.fi =w ceql %${p}.t2, 5
|
||||
%${p} =w or %${p}.ii, %${p}.fi
|
||||
`
|
||||
}
|
||||
|
||||
var is_null = function(p, v) {
|
||||
return ` %${p}.t =l and ${v}, 31
|
||||
%${p} =w ceql %${p}.t, 7
|
||||
`
|
||||
}
|
||||
|
||||
var is_bool = function(p, v) {
|
||||
return ` %${p}.t =l and ${v}, 31
|
||||
%${p} =w ceql %${p}.t, 3
|
||||
`
|
||||
}
|
||||
|
||||
var is_exception = function(p, v) {
|
||||
return ` %${p}.t =l and ${v}, 31
|
||||
%${p} =w ceql %${p}.t, 15
|
||||
`
|
||||
}
|
||||
|
||||
var is_ptr = function(p, v) {
|
||||
return ` %${p}.t =l and ${v}, 7
|
||||
%${p} =w ceql %${p}.t, 1
|
||||
`
|
||||
}
|
||||
|
||||
var is_imm_text = function(p, v) {
|
||||
return ` %${p}.t =l and ${v}, 31
|
||||
%${p} =w ceql %${p}.t, 27
|
||||
`
|
||||
}
|
||||
|
||||
var is_text = function(p, v) {
|
||||
return ` %${p}.imm =l and ${v}, 31
|
||||
%${p}.is_imm =w ceql %${p}.imm, 27
|
||||
jnz %${p}.is_imm, @${p}.yes, @${p}.chk_ptr
|
||||
@${p}.chk_ptr
|
||||
%${p}.ptag =l and ${v}, 7
|
||||
%${p}.is_ptr =w ceql %${p}.ptag, 1
|
||||
jnz %${p}.is_ptr, @${p}.load_hdr, @${p}.no
|
||||
@${p}.load_hdr
|
||||
%${p}.ptr =l and ${v}, -8
|
||||
%${p}.hdr =l loadl %${p}.ptr
|
||||
@${p}.chase
|
||||
%${p}.ht =l and %${p}.hdr, 7
|
||||
%${p}.is_fwd =w ceql %${p}.ht, 7
|
||||
jnz %${p}.is_fwd, @${p}.follow, @${p}.chk_type
|
||||
@${p}.follow
|
||||
%${p}.ptr =l shr %${p}.hdr, 3
|
||||
%${p}.hdr =l loadl %${p}.ptr
|
||||
jmp @${p}.chase
|
||||
@${p}.chk_type
|
||||
%${p} =w ceql %${p}.ht, 2
|
||||
jmp @${p}.done
|
||||
@${p}.yes
|
||||
%${p} =w copy 1
|
||||
jmp @${p}.done
|
||||
@${p}.no
|
||||
%${p} =w copy 0
|
||||
@${p}.done
|
||||
`
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Value Extraction
|
||||
// ============================================================
|
||||
|
||||
// get_int(p, v) — extract int32 as w from tagged int. Result: %{p}
|
||||
var get_int = function(p, v) {
|
||||
return ` %${p}.sl =l sar ${v}, 1
|
||||
%${p} =w copy %${p}.sl
|
||||
`
|
||||
}
|
||||
|
||||
// get_bool(p, v) — extract bool as w. Result: %{p}
|
||||
var get_bool = function(p, v) {
|
||||
return ` %${p}.sl =l shr ${v}, 5
|
||||
%${p} =w and %${p}.sl, 1
|
||||
`
|
||||
}
|
||||
|
||||
// get_ptr(p, v) — extract pointer as l. Result: %{p}
|
||||
var get_ptr = function(p, v) {
|
||||
return ` %${p} =l and ${v}, -8
|
||||
`
|
||||
}
|
||||
|
||||
// get_float64(p, v) — decode short float to d. Result: %{p} as d
|
||||
// Caller must handle zero check (sexp == 0 -> 0.0) before calling.
|
||||
var get_float64 = function(p, v) {
|
||||
return ` %${p}.sign =l shr ${v}, 63
|
||||
%${p}.sexp =l shr ${v}, 55
|
||||
%${p}.sexp =l and %${p}.sexp, 255
|
||||
%${p}.mant =l shr ${v}, 3
|
||||
%${p}.mant =l and %${p}.mant, ${mantissa_mask}
|
||||
%${p}.dexp =l sub %${p}.sexp, 127
|
||||
%${p}.dexp =l add %${p}.dexp, 1023
|
||||
%${p}.s63 =l shl %${p}.sign, 63
|
||||
%${p}.e52 =l shl %${p}.dexp, 52
|
||||
%${p}.bits =l or %${p}.s63, %${p}.e52
|
||||
%${p}.bits =l or %${p}.bits, %${p}.mant
|
||||
%${p} =d cast %${p}.bits
|
||||
`
|
||||
}
|
||||
|
||||
// to_float64(p, v) — convert any numeric value (int or short float) to d.
|
||||
// Result: %{p} as d
|
||||
var to_float64 = function(p, v) {
|
||||
return ` %${p}.tag =l and ${v}, 1
|
||||
%${p}.is_int =w ceql %${p}.tag, 0
|
||||
jnz %${p}.is_int, @${p}.from_int, @${p}.from_float
|
||||
@${p}.from_int
|
||||
%${p}.isl =l sar ${v}, 1
|
||||
%${p}.iw =w copy %${p}.isl
|
||||
%${p} =d swtof %${p}.iw
|
||||
jmp @${p}.done
|
||||
@${p}.from_float
|
||||
%${p}.fsexp =l shr ${v}, 55
|
||||
%${p}.fsexp =l and %${p}.fsexp, 255
|
||||
%${p}.is_zero =w ceql %${p}.fsexp, 0
|
||||
jnz %${p}.is_zero, @${p}.fzero, @${p}.fdecode
|
||||
@${p}.fzero
|
||||
%${p} =d copy d_0.0
|
||||
jmp @${p}.done
|
||||
@${p}.fdecode
|
||||
%${p}.fsign =l shr ${v}, 63
|
||||
%${p}.fmant =l shr ${v}, 3
|
||||
%${p}.fmant =l and %${p}.fmant, ${mantissa_mask}
|
||||
%${p}.fdexp =l sub %${p}.fsexp, 127
|
||||
%${p}.fdexp =l add %${p}.fdexp, 1023
|
||||
%${p}.fs63 =l shl %${p}.fsign, 63
|
||||
%${p}.fe52 =l shl %${p}.fdexp, 52
|
||||
%${p}.fbits =l or %${p}.fs63, %${p}.fe52
|
||||
%${p}.fbits =l or %${p}.fbits, %${p}.fmant
|
||||
%${p} =d cast %${p}.fbits
|
||||
@${p}.done
|
||||
`
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Value Creation
|
||||
// ============================================================
|
||||
|
||||
// new_int(p, i) — tag int32 w as JSValue l. Result: %{p}
|
||||
var new_int = function(p, i) {
|
||||
return ` %${p}.ext =l extuw ${i}
|
||||
%${p} =l shl %${p}.ext, 1
|
||||
`
|
||||
}
|
||||
|
||||
// new_bool(p, b) — tag bool w as JSValue l. Result: %{p}
|
||||
var new_bool = function(p, b) {
|
||||
return ` %${p}.ext =l extuw ${b}
|
||||
%${p}.sh =l shl %${p}.ext, 5
|
||||
%${p} =l or %${p}.sh, 3
|
||||
`
|
||||
}
|
||||
|
||||
// new_float64 — C call to __JS_NewFloat64(ctx, val). Result: %{p}
|
||||
var new_float64 = function(p, ctx, d) {
|
||||
return ` %${p} =l call $__JS_NewFloat64(l ${ctx}, d ${d})
|
||||
`
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Arithmetic — add(p, ctx, a, b)
|
||||
// Int fast path inline, text concat and float as C calls.
|
||||
// Jumps to @disrupt on type mismatch.
|
||||
// ============================================================
|
||||
|
||||
var add = function(p, ctx, a, b) {
|
||||
return `@${p}.start
|
||||
%${p}.at =l and ${a}, 1
|
||||
%${p}.bt =l and ${b}, 1
|
||||
%${p}.not_int =l or %${p}.at, %${p}.bt
|
||||
jnz %${p}.not_int, @${p}.not_both_int, @${p}.int_path
|
||||
@${p}.int_path
|
||||
%${p}.ia =l sar ${a}, 1
|
||||
%${p}.ib =l sar ${b}, 1
|
||||
%${p}.sum =l add %${p}.ia, %${p}.ib
|
||||
%${p}.lo =w csltl %${p}.sum, ${int32_min}
|
||||
%${p}.hi =w csgtl %${p}.sum, ${int32_max}
|
||||
%${p}.ov =w or %${p}.lo, %${p}.hi
|
||||
jnz %${p}.ov, @${p}.int_overflow, @${p}.int_ok
|
||||
@${p}.int_ok
|
||||
%${p}.rw =w copy %${p}.sum
|
||||
%${p}.rext =l extuw %${p}.rw
|
||||
%${p} =l shl %${p}.rext, 1
|
||||
jmp @${p}.done
|
||||
@${p}.int_overflow
|
||||
%${p}.fd =d sltof %${p}.sum
|
||||
%${p} =l call $__JS_NewFloat64(l ${ctx}, d %${p}.fd)
|
||||
jmp @${p}.done
|
||||
@${p}.not_both_int
|
||||
%${p}.a_is_text =w call $JS_IsText(l ${a})
|
||||
%${p}.b_is_text =w call $JS_IsText(l ${b})
|
||||
%${p}.both_text =w and %${p}.a_is_text, %${p}.b_is_text
|
||||
jnz %${p}.both_text, @${p}.text_path, @${p}.chk_num
|
||||
@${p}.text_path
|
||||
%${p} =l call $JS_ConcatString(l ${ctx}, l ${a}, l ${b})
|
||||
jmp @${p}.done
|
||||
@${p}.chk_num
|
||||
%${p}.a_is_num =w call $JS_IsNumber(l ${a})
|
||||
%${p}.b_is_num =w call $JS_IsNumber(l ${b})
|
||||
%${p}.both_num =w and %${p}.a_is_num, %${p}.b_is_num
|
||||
jnz %${p}.both_num, @${p}.float_path, @disrupt
|
||||
@${p}.float_path
|
||||
%${p} =l call $qbe_float_add(l ${ctx}, l ${a}, l ${b})
|
||||
@${p}.done
|
||||
`
|
||||
}
|
||||
|
||||
var sub = function(p, ctx, a, b) {
|
||||
return `@${p}.start
|
||||
%${p}.at =l and ${a}, 1
|
||||
%${p}.bt =l and ${b}, 1
|
||||
%${p}.not_int =l or %${p}.at, %${p}.bt
|
||||
jnz %${p}.not_int, @${p}.not_both_int, @${p}.int_path
|
||||
@${p}.int_path
|
||||
%${p}.ia =l sar ${a}, 1
|
||||
%${p}.ib =l sar ${b}, 1
|
||||
%${p}.diff =l sub %${p}.ia, %${p}.ib
|
||||
%${p}.lo =w csltl %${p}.diff, ${int32_min}
|
||||
%${p}.hi =w csgtl %${p}.diff, ${int32_max}
|
||||
%${p}.ov =w or %${p}.lo, %${p}.hi
|
||||
jnz %${p}.ov, @${p}.int_overflow, @${p}.int_ok
|
||||
@${p}.int_ok
|
||||
%${p}.rw =w copy %${p}.diff
|
||||
%${p}.rext =l extuw %${p}.rw
|
||||
%${p} =l shl %${p}.rext, 1
|
||||
jmp @${p}.done
|
||||
@${p}.int_overflow
|
||||
%${p}.fd =d sltof %${p}.diff
|
||||
%${p} =l call $__JS_NewFloat64(l ${ctx}, d %${p}.fd)
|
||||
jmp @${p}.done
|
||||
@${p}.not_both_int
|
||||
%${p}.a_is_num =w call $JS_IsNumber(l ${a})
|
||||
%${p}.b_is_num =w call $JS_IsNumber(l ${b})
|
||||
%${p}.both_num =w and %${p}.a_is_num, %${p}.b_is_num
|
||||
jnz %${p}.both_num, @${p}.float_path, @disrupt
|
||||
@${p}.float_path
|
||||
%${p} =l call $qbe_float_sub(l ${ctx}, l ${a}, l ${b})
|
||||
@${p}.done
|
||||
`
|
||||
}
|
||||
|
||||
var mul = function(p, ctx, a, b) {
|
||||
return `@${p}.start
|
||||
%${p}.at =l and ${a}, 1
|
||||
%${p}.bt =l and ${b}, 1
|
||||
%${p}.not_int =l or %${p}.at, %${p}.bt
|
||||
jnz %${p}.not_int, @${p}.not_both_int, @${p}.int_path
|
||||
@${p}.int_path
|
||||
%${p}.ia =l sar ${a}, 1
|
||||
%${p}.ib =l sar ${b}, 1
|
||||
%${p}.prod =l mul %${p}.ia, %${p}.ib
|
||||
%${p}.lo =w csltl %${p}.prod, ${int32_min}
|
||||
%${p}.hi =w csgtl %${p}.prod, ${int32_max}
|
||||
%${p}.ov =w or %${p}.lo, %${p}.hi
|
||||
jnz %${p}.ov, @${p}.int_overflow, @${p}.int_ok
|
||||
@${p}.int_ok
|
||||
%${p}.rw =w copy %${p}.prod
|
||||
%${p}.rext =l extuw %${p}.rw
|
||||
%${p} =l shl %${p}.rext, 1
|
||||
jmp @${p}.done
|
||||
@${p}.int_overflow
|
||||
%${p}.fd =d sltof %${p}.prod
|
||||
%${p} =l call $__JS_NewFloat64(l ${ctx}, d %${p}.fd)
|
||||
jmp @${p}.done
|
||||
@${p}.not_both_int
|
||||
%${p}.a_is_num =w call $JS_IsNumber(l ${a})
|
||||
%${p}.b_is_num =w call $JS_IsNumber(l ${b})
|
||||
%${p}.both_num =w and %${p}.a_is_num, %${p}.b_is_num
|
||||
jnz %${p}.both_num, @${p}.float_path, @disrupt
|
||||
@${p}.float_path
|
||||
%${p} =l call $qbe_float_mul(l ${ctx}, l ${a}, l ${b})
|
||||
@${p}.done
|
||||
`
|
||||
}
|
||||
|
||||
var div = function(p, ctx, a, b) {
|
||||
return `@${p}.start
|
||||
%${p}.at =l and ${a}, 1
|
||||
%${p}.bt =l and ${b}, 1
|
||||
%${p}.not_int =l or %${p}.at, %${p}.bt
|
||||
jnz %${p}.not_int, @${p}.not_both_int, @${p}.int_path
|
||||
@${p}.int_path
|
||||
%${p}.ia =w copy 0
|
||||
%${p}.tmp =l sar ${a}, 1
|
||||
%${p}.ia =w copy %${p}.tmp
|
||||
%${p}.ib =w copy 0
|
||||
%${p}.tmp2 =l sar ${b}, 1
|
||||
%${p}.ib =w copy %${p}.tmp2
|
||||
%${p}.div0 =w ceqw %${p}.ib, 0
|
||||
jnz %${p}.div0, @${p}.ret_null, @${p}.chk_exact
|
||||
@${p}.ret_null
|
||||
%${p} =l copy ${js_null}
|
||||
jmp @${p}.done
|
||||
@${p}.chk_exact
|
||||
%${p}.rem =w rem %${p}.ia, %${p}.ib
|
||||
%${p}.exact =w ceqw %${p}.rem, 0
|
||||
jnz %${p}.exact, @${p}.int_div, @${p}.int_to_float
|
||||
@${p}.int_div
|
||||
%${p}.q =w div %${p}.ia, %${p}.ib
|
||||
%${p}.qext =l extuw %${p}.q
|
||||
%${p} =l shl %${p}.qext, 1
|
||||
jmp @${p}.done
|
||||
@${p}.int_to_float
|
||||
%${p}.da =d swtof %${p}.ia
|
||||
%${p}.db =d swtof %${p}.ib
|
||||
%${p}.dr =d div %${p}.da, %${p}.db
|
||||
%${p} =l call $__JS_NewFloat64(l ${ctx}, d %${p}.dr)
|
||||
jmp @${p}.done
|
||||
@${p}.not_both_int
|
||||
%${p}.a_is_num =w call $JS_IsNumber(l ${a})
|
||||
%${p}.b_is_num =w call $JS_IsNumber(l ${b})
|
||||
%${p}.both_num =w and %${p}.a_is_num, %${p}.b_is_num
|
||||
jnz %${p}.both_num, @${p}.float_path, @disrupt
|
||||
@${p}.float_path
|
||||
%${p} =l call $qbe_float_div(l ${ctx}, l ${a}, l ${b})
|
||||
@${p}.done
|
||||
`
|
||||
}
|
||||
|
||||
var mod = function(p, ctx, a, b) {
|
||||
return `@${p}.start
|
||||
%${p}.at =l and ${a}, 1
|
||||
%${p}.bt =l and ${b}, 1
|
||||
%${p}.not_int =l or %${p}.at, %${p}.bt
|
||||
jnz %${p}.not_int, @${p}.not_both_int, @${p}.int_path
|
||||
@${p}.int_path
|
||||
%${p}.ia =w copy 0
|
||||
%${p}.tmp =l sar ${a}, 1
|
||||
%${p}.ia =w copy %${p}.tmp
|
||||
%${p}.ib =w copy 0
|
||||
%${p}.tmp2 =l sar ${b}, 1
|
||||
%${p}.ib =w copy %${p}.tmp2
|
||||
%${p}.div0 =w ceqw %${p}.ib, 0
|
||||
jnz %${p}.div0, @${p}.ret_null, @${p}.do_mod
|
||||
@${p}.ret_null
|
||||
%${p} =l copy ${js_null}
|
||||
jmp @${p}.done
|
||||
@${p}.do_mod
|
||||
%${p}.r =w rem %${p}.ia, %${p}.ib
|
||||
%${p}.rext =l extuw %${p}.r
|
||||
%${p} =l shl %${p}.rext, 1
|
||||
jmp @${p}.done
|
||||
@${p}.not_both_int
|
||||
%${p}.a_is_num =w call $JS_IsNumber(l ${a})
|
||||
%${p}.b_is_num =w call $JS_IsNumber(l ${b})
|
||||
%${p}.both_num =w and %${p}.a_is_num, %${p}.b_is_num
|
||||
jnz %${p}.both_num, @${p}.float_path, @disrupt
|
||||
@${p}.float_path
|
||||
%${p} =l call $qbe_float_mod(l ${ctx}, l ${a}, l ${b})
|
||||
@${p}.done
|
||||
`
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Comparisons — eq, ne, lt, le, gt, ge
|
||||
// Each takes (p, ctx, a, b), produces %{p} as l (tagged JSValue bool)
|
||||
// ============================================================
|
||||
|
||||
// Helper: generate comparison for a given op string and int comparison QBE op
|
||||
// null_true: whether null==null returns true (eq, le, ge) or false (ne, lt, gt)
|
||||
var cmp = function(p, ctx, a, b, int_cmp_op, float_cmp_op_id, is_eq, is_ne, null_true) {
|
||||
var eq_only = 0
|
||||
if (is_eq || is_ne) {
|
||||
eq_only = 1
|
||||
}
|
||||
var mismatch_val = js_false
|
||||
if (is_ne) {
|
||||
mismatch_val = js_true
|
||||
}
|
||||
var null_val = js_false
|
||||
if (null_true) {
|
||||
null_val = js_true
|
||||
}
|
||||
return `@${p}.start
|
||||
%${p}.at =l and ${a}, 1
|
||||
%${p}.bt =l and ${b}, 1
|
||||
%${p}.not_int =l or %${p}.at, %${p}.bt
|
||||
jnz %${p}.not_int, @${p}.not_both_int, @${p}.int_path
|
||||
@${p}.int_path
|
||||
%${p}.ia =l sar ${a}, 1
|
||||
%${p}.ib =l sar ${b}, 1
|
||||
%${p}.iw =w copy %${p}.ia
|
||||
%${p}.ibw =w copy %${p}.ib
|
||||
%${p}.cr =w ${int_cmp_op} %${p}.iw, %${p}.ibw
|
||||
%${p}.crext =l extuw %${p}.cr
|
||||
%${p}.sh =l shl %${p}.crext, 5
|
||||
%${p} =l or %${p}.sh, 3
|
||||
jmp @${p}.done
|
||||
@${p}.not_both_int
|
||||
%${p}.a_tag5 =l and ${a}, 31
|
||||
%${p}.b_tag5 =l and ${b}, 31
|
||||
%${p}.a_is_null =w ceql %${p}.a_tag5, 7
|
||||
%${p}.b_is_null =w ceql %${p}.b_tag5, 7
|
||||
%${p}.both_null =w and %${p}.a_is_null, %${p}.b_is_null
|
||||
jnz %${p}.both_null, @${p}.null_path, @${p}.chk_bool
|
||||
@${p}.null_path
|
||||
%${p} =l copy ${null_val}
|
||||
jmp @${p}.done
|
||||
@${p}.chk_bool
|
||||
%${p}.a_is_bool =w ceql %${p}.a_tag5, 3
|
||||
%${p}.b_is_bool =w ceql %${p}.b_tag5, 3
|
||||
%${p}.both_bool =w and %${p}.a_is_bool, %${p}.b_is_bool
|
||||
jnz %${p}.both_bool, @${p}.bool_path, @${p}.chk_num
|
||||
@${p}.bool_path
|
||||
%${p}.ba =l shr ${a}, 5
|
||||
%${p}.baw =w and %${p}.ba, 1
|
||||
%${p}.bb =l shr ${b}, 5
|
||||
%${p}.bbw =w and %${p}.bb, 1
|
||||
%${p}.bcr =w ${int_cmp_op} %${p}.baw, %${p}.bbw
|
||||
%${p}.bcrext =l extuw %${p}.bcr
|
||||
%${p}.bsh =l shl %${p}.bcrext, 5
|
||||
%${p} =l or %${p}.bsh, 3
|
||||
jmp @${p}.done
|
||||
@${p}.chk_num
|
||||
%${p}.a_is_num =w call $JS_IsNumber(l ${a})
|
||||
%${p}.b_is_num =w call $JS_IsNumber(l ${b})
|
||||
%${p}.both_num =w and %${p}.a_is_num, %${p}.b_is_num
|
||||
jnz %${p}.both_num, @${p}.num_path, @${p}.chk_text
|
||||
@${p}.num_path
|
||||
%${p}.fcr =w call $qbe_float_cmp(l ${ctx}, w ${float_cmp_op_id}, l ${a}, l ${b})
|
||||
%${p}.fcrext =l extuw %${p}.fcr
|
||||
%${p}.fsh =l shl %${p}.fcrext, 5
|
||||
%${p} =l or %${p}.fsh, 3
|
||||
jmp @${p}.done
|
||||
@${p}.chk_text
|
||||
%${p}.a_is_text =w call $JS_IsText(l ${a})
|
||||
%${p}.b_is_text =w call $JS_IsText(l ${b})
|
||||
%${p}.both_text =w and %${p}.a_is_text, %${p}.b_is_text
|
||||
jnz %${p}.both_text, @${p}.text_path, @${p}.mismatch
|
||||
@${p}.text_path
|
||||
%${p}.scmp =w call $js_string_compare_value(l ${ctx}, l ${a}, l ${b}, w ${eq_only})
|
||||
%${p}.tcr =w ${int_cmp_op} %${p}.scmp, 0
|
||||
%${p}.tcrext =l extuw %${p}.tcr
|
||||
%${p}.tsh =l shl %${p}.tcrext, 5
|
||||
%${p} =l or %${p}.tsh, 3
|
||||
jmp @${p}.done
|
||||
@${p}.mismatch
|
||||
%${p} =l copy ${mismatch_val}
|
||||
@${p}.done
|
||||
`
|
||||
}
|
||||
|
||||
// Comparison op IDs matching MACH_EQ..MACH_GE order for qbe_float_cmp
|
||||
// MACH_EQ=0, NEQ=1, LT=2, LE=3, GT=4, GE=5
|
||||
// null_true: eq, le, ge return true for null==null; ne, lt, gt return false
|
||||
var eq = function(p, ctx, a, b) {
|
||||
return cmp(p, ctx, a, b, "ceqw", 0, true, false, true)
|
||||
}
|
||||
|
||||
var ne = function(p, ctx, a, b) {
|
||||
return cmp(p, ctx, a, b, "cnew", 1, false, true, false)
|
||||
}
|
||||
|
||||
var lt = function(p, ctx, a, b) {
|
||||
return cmp(p, ctx, a, b, "csltw", 2, false, false, false)
|
||||
}
|
||||
|
||||
var le = function(p, ctx, a, b) {
|
||||
return cmp(p, ctx, a, b, "cslew", 3, false, false, true)
|
||||
}
|
||||
|
||||
var gt = function(p, ctx, a, b) {
|
||||
return cmp(p, ctx, a, b, "csgtw", 4, false, false, false)
|
||||
}
|
||||
|
||||
var ge = function(p, ctx, a, b) {
|
||||
return cmp(p, ctx, a, b, "csgew", 5, false, false, true)
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Unary Ops
|
||||
// ============================================================
|
||||
|
||||
// neg(p, ctx, v) — negate. Int fast path (INT32_MIN edge case), else C call.
|
||||
var neg = function(p, ctx, v) {
|
||||
return `@${p}.start
|
||||
%${p}.tag =l and ${v}, 1
|
||||
%${p}.is_int =w ceql %${p}.tag, 0
|
||||
jnz %${p}.is_int, @${p}.int_path, @${p}.float_path
|
||||
@${p}.int_path
|
||||
%${p}.sl =l sar ${v}, 1
|
||||
%${p}.iw =w copy %${p}.sl
|
||||
%${p}.is_min =w ceqw %${p}.iw, ${int32_min}
|
||||
jnz %${p}.is_min, @${p}.min_overflow, @${p}.int_ok
|
||||
@${p}.min_overflow
|
||||
%${p}.fd =d swtof %${p}.iw
|
||||
%${p}.fdn =d neg %${p}.fd
|
||||
%${p} =l call $__JS_NewFloat64(l ${ctx}, d %${p}.fdn)
|
||||
jmp @${p}.done
|
||||
@${p}.int_ok
|
||||
%${p}.ni =w sub 0, %${p}.iw
|
||||
%${p}.niext =l extuw %${p}.ni
|
||||
%${p} =l shl %${p}.niext, 1
|
||||
jmp @${p}.done
|
||||
@${p}.float_path
|
||||
%${p} =l call $qbe_float_neg(l ${ctx}, l ${v})
|
||||
@${p}.done
|
||||
`
|
||||
}
|
||||
|
||||
// inc(p, ctx, v) — increment. Int fast path (INT32_MAX edge case), else C call.
|
||||
var inc = function(p, ctx, v) {
|
||||
return `@${p}.start
|
||||
%${p}.tag =l and ${v}, 1
|
||||
%${p}.is_int =w ceql %${p}.tag, 0
|
||||
jnz %${p}.is_int, @${p}.int_path, @${p}.float_path
|
||||
@${p}.int_path
|
||||
%${p}.sl =l sar ${v}, 1
|
||||
%${p}.iw =w copy %${p}.sl
|
||||
%${p}.is_max =w ceqw %${p}.iw, ${int32_max}
|
||||
jnz %${p}.is_max, @${p}.max_overflow, @${p}.int_ok
|
||||
@${p}.max_overflow
|
||||
%${p}.fd =d swtof %${p}.iw
|
||||
%${p}.fd1 =d add %${p}.fd, d_1.0
|
||||
%${p} =l call $__JS_NewFloat64(l ${ctx}, d %${p}.fd1)
|
||||
jmp @${p}.done
|
||||
@${p}.int_ok
|
||||
%${p}.ni =w add %${p}.iw, 1
|
||||
%${p}.niext =l extuw %${p}.ni
|
||||
%${p} =l shl %${p}.niext, 1
|
||||
jmp @${p}.done
|
||||
@${p}.float_path
|
||||
%${p} =l call $qbe_float_inc(l ${ctx}, l ${v})
|
||||
@${p}.done
|
||||
`
|
||||
}
|
||||
|
||||
// dec(p, ctx, v) — decrement. Int fast path (INT32_MIN edge case), else C call.
|
||||
var dec = function(p, ctx, v) {
|
||||
return `@${p}.start
|
||||
%${p}.tag =l and ${v}, 1
|
||||
%${p}.is_int =w ceql %${p}.tag, 0
|
||||
jnz %${p}.is_int, @${p}.int_path, @${p}.float_path
|
||||
@${p}.int_path
|
||||
%${p}.sl =l sar ${v}, 1
|
||||
%${p}.iw =w copy %${p}.sl
|
||||
%${p}.is_min =w ceqw %${p}.iw, ${int32_min}
|
||||
jnz %${p}.is_min, @${p}.min_overflow, @${p}.int_ok
|
||||
@${p}.min_overflow
|
||||
%${p}.fd =d swtof %${p}.iw
|
||||
%${p}.fd1 =d sub %${p}.fd, d_1.0
|
||||
%${p} =l call $__JS_NewFloat64(l ${ctx}, d %${p}.fd1)
|
||||
jmp @${p}.done
|
||||
@${p}.int_ok
|
||||
%${p}.ni =w sub %${p}.iw, 1
|
||||
%${p}.niext =l extuw %${p}.ni
|
||||
%${p} =l shl %${p}.niext, 1
|
||||
jmp @${p}.done
|
||||
@${p}.float_path
|
||||
%${p} =l call $qbe_float_dec(l ${ctx}, l ${v})
|
||||
@${p}.done
|
||||
`
|
||||
}
|
||||
|
||||
// lnot(p, ctx, v) — logical not. C call to JS_ToBool, then negate inline.
|
||||
var lnot = function(p, ctx, v) {
|
||||
return ` %${p}.bval =w call $JS_ToBool(l ${ctx}, l ${v})
|
||||
%${p}.neg =w ceqw %${p}.bval, 0
|
||||
%${p}.nex =l extuw %${p}.neg
|
||||
%${p}.sh =l shl %${p}.nex, 5
|
||||
%${p} =l or %${p}.sh, 3
|
||||
`
|
||||
}
|
||||
|
||||
// bnot(p, ctx, v) — bitwise not. Convert to int32, ~, re-tag.
|
||||
var bnot = function(p, ctx, v) {
|
||||
return `@${p}.start
|
||||
%${p}.tag =l and ${v}, 1
|
||||
%${p}.is_int =w ceql %${p}.tag, 0
|
||||
jnz %${p}.is_int, @${p}.int_path, @${p}.slow_path
|
||||
@${p}.int_path
|
||||
%${p}.sl =l sar ${v}, 1
|
||||
%${p}.iw =w copy %${p}.sl
|
||||
%${p}.nw =w xor %${p}.iw, -1
|
||||
%${p}.nex =l extuw %${p}.nw
|
||||
%${p} =l shl %${p}.nex, 1
|
||||
jmp @${p}.done
|
||||
@${p}.slow_path
|
||||
%${p} =l call $qbe_bnot(l ${ctx}, l ${v})
|
||||
@${p}.done
|
||||
`
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Bitwise Ops — band, bor, bxor, shl, shr, ushr
|
||||
// Both operands must be numeric. Int fast path, float -> convert to int32.
|
||||
// ============================================================
|
||||
|
||||
var bitwise_op = function(p, ctx, a, b, qbe_op) {
|
||||
return `@${p}.start
|
||||
%${p}.at =l and ${a}, 1
|
||||
%${p}.bt =l and ${b}, 1
|
||||
%${p}.not_int =l or %${p}.at, %${p}.bt
|
||||
jnz %${p}.not_int, @${p}.slow_path, @${p}.int_path
|
||||
@${p}.int_path
|
||||
%${p}.ia =l sar ${a}, 1
|
||||
%${p}.iaw =w copy %${p}.ia
|
||||
%${p}.ib =l sar ${b}, 1
|
||||
%${p}.ibw =w copy %${p}.ib
|
||||
%${p}.rw =w ${qbe_op} %${p}.iaw, %${p}.ibw
|
||||
%${p}.rext =l extuw %${p}.rw
|
||||
%${p} =l shl %${p}.rext, 1
|
||||
jmp @${p}.done
|
||||
@${p}.slow_path
|
||||
%${p}.a_is_num =w call $JS_IsNumber(l ${a})
|
||||
%${p}.b_is_num =w call $JS_IsNumber(l ${b})
|
||||
%${p}.both_num =w and %${p}.a_is_num, %${p}.b_is_num
|
||||
jnz %${p}.both_num, @${p}.float_to_int, @disrupt
|
||||
@${p}.float_to_int
|
||||
%${p} =l call $qbe_bitwise_${qbe_op}(l ${ctx}, l ${a}, l ${b})
|
||||
@${p}.done
|
||||
`
|
||||
}
|
||||
|
||||
var band = function(p, ctx, a, b) {
|
||||
return bitwise_op(p, ctx, a, b, "and")
|
||||
}
|
||||
|
||||
var bor = function(p, ctx, a, b) {
|
||||
return bitwise_op(p, ctx, a, b, "or")
|
||||
}
|
||||
|
||||
var bxor = function(p, ctx, a, b) {
|
||||
return bitwise_op(p, ctx, a, b, "xor")
|
||||
}
|
||||
|
||||
// Shift ops: mask shift amount to 5 bits (& 31)
|
||||
var shift_op = function(p, ctx, a, b, qbe_op) {
|
||||
return `@${p}.start
|
||||
%${p}.at =l and ${a}, 1
|
||||
%${p}.bt =l and ${b}, 1
|
||||
%${p}.not_int =l or %${p}.at, %${p}.bt
|
||||
jnz %${p}.not_int, @${p}.slow_path, @${p}.int_path
|
||||
@${p}.int_path
|
||||
%${p}.ia =l sar ${a}, 1
|
||||
%${p}.iaw =w copy %${p}.ia
|
||||
%${p}.ib =l sar ${b}, 1
|
||||
%${p}.ibw =w copy %${p}.ib
|
||||
%${p}.sh =w and %${p}.ibw, 31
|
||||
%${p}.rw =w ${qbe_op} %${p}.iaw, %${p}.sh
|
||||
%${p}.rext =l extuw %${p}.rw
|
||||
%${p} =l shl %${p}.rext, 1
|
||||
jmp @${p}.done
|
||||
@${p}.slow_path
|
||||
%${p}.a_is_num =w call $JS_IsNumber(l ${a})
|
||||
%${p}.b_is_num =w call $JS_IsNumber(l ${b})
|
||||
%${p}.both_num =w and %${p}.a_is_num, %${p}.b_is_num
|
||||
jnz %${p}.both_num, @${p}.float_to_int, @disrupt
|
||||
@${p}.float_to_int
|
||||
%${p} =l call $qbe_shift_${qbe_op}(l ${ctx}, l ${a}, l ${b})
|
||||
@${p}.done
|
||||
`
|
||||
}
|
||||
|
||||
var shl = function(p, ctx, a, b) {
|
||||
return shift_op(p, ctx, a, b, "shl")
|
||||
}
|
||||
|
||||
var shr = function(p, ctx, a, b) {
|
||||
return shift_op(p, ctx, a, b, "sar")
|
||||
}
|
||||
|
||||
var ushr = function(p, ctx, a, b) {
|
||||
return shift_op(p, ctx, a, b, "shr")
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Module export
|
||||
// ============================================================
|
||||
|
||||
return {
|
||||
// constants
|
||||
js_null: js_null,
|
||||
js_false: js_false,
|
||||
js_true: js_true,
|
||||
js_exception: js_exception,
|
||||
js_empty_text: js_empty_text,
|
||||
// type checks
|
||||
is_int: is_int,
|
||||
is_number: is_number,
|
||||
is_null: is_null,
|
||||
is_bool: is_bool,
|
||||
is_exception: is_exception,
|
||||
is_ptr: is_ptr,
|
||||
is_imm_text: is_imm_text,
|
||||
is_text: is_text,
|
||||
// value extraction
|
||||
get_int: get_int,
|
||||
get_bool: get_bool,
|
||||
get_ptr: get_ptr,
|
||||
get_float64: get_float64,
|
||||
to_float64: to_float64,
|
||||
// value creation
|
||||
new_int: new_int,
|
||||
new_bool: new_bool,
|
||||
new_float64: new_float64,
|
||||
// arithmetic
|
||||
add: add,
|
||||
sub: sub,
|
||||
mul: mul,
|
||||
div: div,
|
||||
mod: mod,
|
||||
// comparisons
|
||||
eq: eq,
|
||||
ne: ne,
|
||||
lt: lt,
|
||||
le: le,
|
||||
gt: gt,
|
||||
ge: ge,
|
||||
// unary
|
||||
neg: neg,
|
||||
inc: inc,
|
||||
dec: dec,
|
||||
lnot: lnot,
|
||||
bnot: bnot,
|
||||
// bitwise
|
||||
band: band,
|
||||
bor: bor,
|
||||
bxor: bxor,
|
||||
shl: shl,
|
||||
shr: shr,
|
||||
ushr: ushr
|
||||
}
|
||||
194
source/qbe_helpers.c
Normal file
194
source/qbe_helpers.c
Normal file
@@ -0,0 +1,194 @@
|
||||
/*
|
||||
* QBE Helper Functions
|
||||
*
|
||||
* Thin C wrappers called from QBE-generated code for operations
|
||||
* that are too complex to inline: float arithmetic, float comparison,
|
||||
* string comparison, bitwise ops on floats, and boolean conversion.
|
||||
*/
|
||||
|
||||
#include "quickjs-internal.h"
|
||||
#include <math.h>
|
||||
|
||||
/* Comparison op IDs (must match qbe.cm float_cmp_op_id values) */
|
||||
enum {
|
||||
QBE_CMP_EQ = 0,
|
||||
QBE_CMP_NE = 1,
|
||||
QBE_CMP_LT = 2,
|
||||
QBE_CMP_LE = 3,
|
||||
QBE_CMP_GT = 4,
|
||||
QBE_CMP_GE = 5
|
||||
};
|
||||
|
||||
/* ============================================================
|
||||
Float binary arithmetic
|
||||
============================================================ */
|
||||
|
||||
static inline JSValue qbe_float_binop(JSContext *ctx, JSValue a, JSValue b,
|
||||
double (*op)(double, double)) {
|
||||
double da, db;
|
||||
JS_ToFloat64(ctx, &da, a);
|
||||
JS_ToFloat64(ctx, &db, b);
|
||||
double r = op(da, db);
|
||||
if (!isfinite(r))
|
||||
return JS_NULL;
|
||||
return JS_NewFloat64(ctx, r);
|
||||
}
|
||||
|
||||
static double op_add(double a, double b) { return a + b; }
|
||||
static double op_sub(double a, double b) { return a - b; }
|
||||
static double op_mul(double a, double b) { return a * b; }
|
||||
|
||||
JSValue qbe_float_add(JSContext *ctx, JSValue a, JSValue b) {
|
||||
return qbe_float_binop(ctx, a, b, op_add);
|
||||
}
|
||||
|
||||
JSValue qbe_float_sub(JSContext *ctx, JSValue a, JSValue b) {
|
||||
return qbe_float_binop(ctx, a, b, op_sub);
|
||||
}
|
||||
|
||||
JSValue qbe_float_mul(JSContext *ctx, JSValue a, JSValue b) {
|
||||
return qbe_float_binop(ctx, a, b, op_mul);
|
||||
}
|
||||
|
||||
JSValue qbe_float_div(JSContext *ctx, JSValue a, JSValue b) {
|
||||
double da, db;
|
||||
JS_ToFloat64(ctx, &da, a);
|
||||
JS_ToFloat64(ctx, &db, b);
|
||||
if (db == 0.0)
|
||||
return JS_NULL;
|
||||
double r = da / db;
|
||||
if (!isfinite(r))
|
||||
return JS_NULL;
|
||||
return JS_NewFloat64(ctx, r);
|
||||
}
|
||||
|
||||
JSValue qbe_float_mod(JSContext *ctx, JSValue a, JSValue b) {
|
||||
double da, db;
|
||||
JS_ToFloat64(ctx, &da, a);
|
||||
JS_ToFloat64(ctx, &db, b);
|
||||
if (db == 0.0)
|
||||
return JS_NULL;
|
||||
double r = fmod(da, db);
|
||||
if (!isfinite(r))
|
||||
return JS_NULL;
|
||||
return JS_NewFloat64(ctx, r);
|
||||
}
|
||||
|
||||
JSValue qbe_float_pow(JSContext *ctx, JSValue a, JSValue b) {
|
||||
double da, db;
|
||||
JS_ToFloat64(ctx, &da, a);
|
||||
JS_ToFloat64(ctx, &db, b);
|
||||
double r = pow(da, db);
|
||||
if (!isfinite(r) && isfinite(da) && isfinite(db))
|
||||
return JS_NULL;
|
||||
return JS_NewFloat64(ctx, r);
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
Float unary ops
|
||||
============================================================ */
|
||||
|
||||
JSValue qbe_float_neg(JSContext *ctx, JSValue v) {
|
||||
double d;
|
||||
JS_ToFloat64(ctx, &d, v);
|
||||
return JS_NewFloat64(ctx, -d);
|
||||
}
|
||||
|
||||
JSValue qbe_float_inc(JSContext *ctx, JSValue v) {
|
||||
double d;
|
||||
JS_ToFloat64(ctx, &d, v);
|
||||
return JS_NewFloat64(ctx, d + 1);
|
||||
}
|
||||
|
||||
JSValue qbe_float_dec(JSContext *ctx, JSValue v) {
|
||||
double d;
|
||||
JS_ToFloat64(ctx, &d, v);
|
||||
return JS_NewFloat64(ctx, d - 1);
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
Float comparison — returns 0 or 1 for QBE branching
|
||||
============================================================ */
|
||||
|
||||
int qbe_float_cmp(JSContext *ctx, int op, JSValue a, JSValue b) {
|
||||
double da, db;
|
||||
JS_ToFloat64(ctx, &da, a);
|
||||
JS_ToFloat64(ctx, &db, b);
|
||||
switch (op) {
|
||||
case QBE_CMP_EQ: return da == db;
|
||||
case QBE_CMP_NE: return da != db;
|
||||
case QBE_CMP_LT: return da < db;
|
||||
case QBE_CMP_LE: return da <= db;
|
||||
case QBE_CMP_GT: return da > db;
|
||||
case QBE_CMP_GE: return da >= db;
|
||||
default: return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
Boolean conversion wrapper
|
||||
============================================================ */
|
||||
|
||||
int qbe_to_bool(JSContext *ctx, JSValue v) {
|
||||
return JS_ToBool(ctx, v);
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
Bitwise not on non-int (float -> int32 -> ~)
|
||||
============================================================ */
|
||||
|
||||
JSValue qbe_bnot(JSContext *ctx, JSValue v) {
|
||||
int32_t i;
|
||||
JS_ToInt32(ctx, &i, v);
|
||||
return JS_NewInt32(ctx, ~i);
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
Bitwise binary ops on floats (convert both to int32, apply, re-tag)
|
||||
============================================================ */
|
||||
|
||||
JSValue qbe_bitwise_and(JSContext *ctx, JSValue a, JSValue b) {
|
||||
int32_t ia, ib;
|
||||
JS_ToInt32(ctx, &ia, a);
|
||||
JS_ToInt32(ctx, &ib, b);
|
||||
return JS_NewInt32(ctx, ia & ib);
|
||||
}
|
||||
|
||||
JSValue qbe_bitwise_or(JSContext *ctx, JSValue a, JSValue b) {
|
||||
int32_t ia, ib;
|
||||
JS_ToInt32(ctx, &ia, a);
|
||||
JS_ToInt32(ctx, &ib, b);
|
||||
return JS_NewInt32(ctx, ia | ib);
|
||||
}
|
||||
|
||||
JSValue qbe_bitwise_xor(JSContext *ctx, JSValue a, JSValue b) {
|
||||
int32_t ia, ib;
|
||||
JS_ToInt32(ctx, &ia, a);
|
||||
JS_ToInt32(ctx, &ib, b);
|
||||
return JS_NewInt32(ctx, ia ^ ib);
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
Shift ops on floats (convert to int32, shift, re-tag)
|
||||
============================================================ */
|
||||
|
||||
JSValue qbe_shift_shl(JSContext *ctx, JSValue a, JSValue b) {
|
||||
int32_t ia, ib;
|
||||
JS_ToInt32(ctx, &ia, a);
|
||||
JS_ToInt32(ctx, &ib, b);
|
||||
return JS_NewInt32(ctx, ia << (ib & 31));
|
||||
}
|
||||
|
||||
JSValue qbe_shift_sar(JSContext *ctx, JSValue a, JSValue b) {
|
||||
int32_t ia, ib;
|
||||
JS_ToInt32(ctx, &ia, a);
|
||||
JS_ToInt32(ctx, &ib, b);
|
||||
return JS_NewInt32(ctx, ia >> (ib & 31));
|
||||
}
|
||||
|
||||
JSValue qbe_shift_shr(JSContext *ctx, JSValue a, JSValue b) {
|
||||
int32_t ia, ib;
|
||||
JS_ToInt32(ctx, &ia, a);
|
||||
JS_ToInt32(ctx, &ib, b);
|
||||
return JS_NewInt32(ctx, (uint32_t)ia >> (ib & 31));
|
||||
}
|
||||
Reference in New Issue
Block a user