// 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 = 11 // Shared closure vars for functions with >4 params var _qop = null var _qop2 = null var _qflags = null 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, 11 ` } var is_text = function(p, v) { return ` %${p}.imm =l and ${v}, 31 %${p}.is_imm =w ceql %${p}.imm, 11 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 jmp @${p}.done @${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 jmp @${p}.done @${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 $qbe_new_float64(l ${ctx}, d ${d}) ` } // ============================================================ // Arithmetic — add/sub/mul/div/mod(p, ctx, a, b) // Simple C call wrappers. Type dispatch is handled in mcode.cm. // ============================================================ var add = function(p, ctx, a, b) { return ` %${p} =l call $qbe_float_add(l ${ctx}, l ${a}, l ${b}) ` } var sub = function(p, ctx, a, b) { return ` %${p} =l call $qbe_float_sub(l ${ctx}, l ${a}, l ${b}) ` } var mul = function(p, ctx, a, b) { return ` %${p} =l call $qbe_float_mul(l ${ctx}, l ${a}, l ${b}) ` } var div = function(p, ctx, a, b) { return ` %${p} =l call $qbe_float_div(l ${ctx}, l ${a}, l ${b}) ` } var mod = function(p, ctx, a, b) { return ` %${p} =l call $qbe_float_mod(l ${ctx}, l ${a}, l ${b}) ` } // ============================================================ // 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 // reads _qflags = {int_cmp_op, float_id, is_eq, is_ne, null_true} from closure var cmp = function(p, ctx, a, b) { var int_cmp_op = _qflags.int_cmp_op var float_cmp_op_id = _qflags.float_id var eq_only = 0 var mismatch_val = js_false var null_val = js_false if (_qflags.is_eq || _qflags.is_ne) { eq_only = 1 } if (_qflags.is_ne) { mismatch_val = js_true } if (_qflags.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} jmp @${p}.done @${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) { _qflags = {int_cmp_op: "ceqw", float_id: 0, is_eq: true, is_ne: false, null_true: true} return cmp(p, ctx, a, b) } var ne = function(p, ctx, a, b) { _qflags = {int_cmp_op: "cnew", float_id: 1, is_eq: false, is_ne: true, null_true: false} return cmp(p, ctx, a, b) } var lt = function(p, ctx, a, b) { _qflags = {int_cmp_op: "csltw", float_id: 2, is_eq: false, is_ne: false, null_true: false} return cmp(p, ctx, a, b) } var le = function(p, ctx, a, b) { _qflags = {int_cmp_op: "cslew", float_id: 3, is_eq: false, is_ne: false, null_true: true} return cmp(p, ctx, a, b) } var gt = function(p, ctx, a, b) { _qflags = {int_cmp_op: "csgtw", float_id: 4, is_eq: false, is_ne: false, null_true: false} return cmp(p, ctx, a, b) } var ge = function(p, ctx, a, b) { _qflags = {int_cmp_op: "csgew", float_id: 5, is_eq: false, is_ne: false, null_true: true} return cmp(p, ctx, a, b) } // ============================================================ // Unary Ops // ============================================================ // neg(p, ctx, v) — negate via C call (type guards in mcode) var neg = function(p, ctx, v) { return ` %${p} =l call $qbe_float_neg(l ${ctx}, l ${v}) ` } // inc(p, ctx, v) — increment via C call (type guards in mcode) var inc = function(p, ctx, v) { return ` %${p} =l call $qbe_float_inc(l ${ctx}, l ${v}) ` } // dec(p, ctx, v) — decrement via C call (type guards in mcode) var dec = function(p, ctx, v) { return ` %${p} =l call $qbe_float_dec(l ${ctx}, l ${v}) ` } // 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 via C call var bnot = function(p, ctx, v) { return ` %${p} =l call $qbe_bnot(l ${ctx}, l ${v}) ` } // ============================================================ // Bitwise Ops — band, bor, bxor, shl, shr, ushr // Both operands must be numeric. Int fast path, float -> convert to int32. // ============================================================ var band = function(p, ctx, a, b) { return ` %${p} =l call $qbe_bitwise_and(l ${ctx}, l ${a}, l ${b}) ` } var bor = function(p, ctx, a, b) { return ` %${p} =l call $qbe_bitwise_or(l ${ctx}, l ${a}, l ${b}) ` } var bxor = function(p, ctx, a, b) { return ` %${p} =l call $qbe_bitwise_xor(l ${ctx}, l ${a}, l ${b}) ` } var shl = function(p, ctx, a, b) { return ` %${p} =l call $qbe_shift_shl(l ${ctx}, l ${a}, l ${b}) ` } var shr = function(p, ctx, a, b) { return ` %${p} =l call $qbe_shift_sar(l ${ctx}, l ${a}, l ${b}) ` } var ushr = function(p, ctx, a, b) { return ` %${p} =l call $qbe_shift_shr(l ${ctx}, l ${a}, l ${b}) ` } // ============================================================ // Decomposed per-type-path operations // These map directly to the new IR ops emitted by mcode.cm. // ============================================================ // --- Text concat --- var concat = function(p, ctx, a, b) { return ` %${p} =l call $JS_ConcatString(l ${ctx}, l ${a}, l ${b}) ` } // --- Comparisons (int path) --- var cmp_int = function(p, a, b, qbe_op) { return ` %${p}.ia =l sar ${a}, 1 %${p}.ib =l sar ${b}, 1 %${p}.iaw =w copy %${p}.ia %${p}.ibw =w copy %${p}.ib %${p}.cr =w ${qbe_op} %${p}.iaw, %${p}.ibw %${p}.crext =l extuw %${p}.cr %${p}.sh =l shl %${p}.crext, 5 %${p} =l or %${p}.sh, 3 ` } var eq_int = function(p, ctx, a, b) { return cmp_int(p, a, b, "ceqw") } var ne_int = function(p, ctx, a, b) { return cmp_int(p, a, b, "cnew") } var lt_int = function(p, ctx, a, b) { return cmp_int(p, a, b, "csltw") } var le_int = function(p, ctx, a, b) { return cmp_int(p, a, b, "cslew") } var gt_int = function(p, ctx, a, b) { return cmp_int(p, a, b, "csgtw") } var ge_int = function(p, ctx, a, b) { return cmp_int(p, a, b, "csgew") } // --- Comparisons (float path) --- // reads _qop from closure (op_id) var cmp_float = function(p, ctx, a, b) { var op_id = _qop return ` %${p}.fcr =w call $qbe_float_cmp(l ${ctx}, w ${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 ` } var eq_float = function(p, ctx, a, b) { _qop = 0; return cmp_float(p, ctx, a, b) } var ne_float = function(p, ctx, a, b) { _qop = 1; return cmp_float(p, ctx, a, b) } var lt_float = function(p, ctx, a, b) { _qop = 2; return cmp_float(p, ctx, a, b) } var le_float = function(p, ctx, a, b) { _qop = 3; return cmp_float(p, ctx, a, b) } var gt_float = function(p, ctx, a, b) { _qop = 4; return cmp_float(p, ctx, a, b) } var ge_float = function(p, ctx, a, b) { _qop = 5; return cmp_float(p, ctx, a, b) } // --- Comparisons (text path) --- // reads _qop (qbe_op) and _qop2 (eq_only) from closure var cmp_text = function(p, ctx, a, b) { var qbe_op = _qop var eq_only = _qop2 return ` %${p}.scmp =w call $js_string_compare_value(l ${ctx}, l ${a}, l ${b}, w ${eq_only}) %${p}.tcr =w ${qbe_op} %${p}.scmp, 0 %${p}.tcrext =l extuw %${p}.tcr %${p}.tsh =l shl %${p}.tcrext, 5 %${p} =l or %${p}.tsh, 3 ` } var eq_text = function(p, ctx, a, b) { _qop = "ceqw"; _qop2 = 1; return cmp_text(p, ctx, a, b) } var ne_text = function(p, ctx, a, b) { _qop = "cnew"; _qop2 = 1; return cmp_text(p, ctx, a, b) } var lt_text = function(p, ctx, a, b) { _qop = "csltw"; _qop2 = 0; return cmp_text(p, ctx, a, b) } var le_text = function(p, ctx, a, b) { _qop = "cslew"; _qop2 = 0; return cmp_text(p, ctx, a, b) } var gt_text = function(p, ctx, a, b) { _qop = "csgtw"; _qop2 = 0; return cmp_text(p, ctx, a, b) } var ge_text = function(p, ctx, a, b) { _qop = "csgew"; _qop2 = 0; return cmp_text(p, ctx, a, b) } // --- Comparisons (bool path) --- var eq_bool = function(p, a, b) { return ` %${p}.cr =w ceql ${a}, ${b} %${p}.crext =l extuw %${p}.cr %${p}.sh =l shl %${p}.crext, 5 %${p} =l or %${p}.sh, 3 ` } var ne_bool = function(p, a, b) { return ` %${p}.cr =w cnel ${a}, ${b} %${p}.crext =l extuw %${p}.cr %${p}.sh =l shl %${p}.crext, 5 %${p} =l or %${p}.sh, 3 ` } // --- Type guard: is_identical (chases forwarding pointers via C helper) --- var is_identical = function(p, a, b) { return ` %${p} =l call $cell_rt_is_identical(l %ctx, l ${a}, l ${b}) ` } // ============================================================ // 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, // text concat concat: concat, // decomposed comparisons (int) eq_int: eq_int, ne_int: ne_int, lt_int: lt_int, le_int: le_int, gt_int: gt_int, ge_int: ge_int, // decomposed comparisons (float) eq_float: eq_float, ne_float: ne_float, lt_float: lt_float, le_float: le_float, gt_float: gt_float, ge_float: ge_float, // decomposed comparisons (text) eq_text: eq_text, ne_text: ne_text, lt_text: lt_text, le_text: le_text, gt_text: gt_text, ge_text: ge_text, // decomposed comparisons (bool) eq_bool: eq_bool, ne_bool: ne_bool, // type guard is_identical: is_identical }