rustc: Make structured comparison glue instead of emitting it inline

This commit is contained in:
Patrick Walton 2011-04-19 15:22:57 -07:00
parent b7dd75c904
commit 0ee997cee6
4 changed files with 350 additions and 197 deletions

View file

@ -374,7 +374,7 @@ fn T_tydesc(type_names tn) -> TypeRef {
T_ptr(T_nil()),
tydescpp,
pvoid), T_void()));
auto cmp_glue_fn_ty = T_ptr(T_fn(vec(T_ptr(T_i8()),
auto cmp_glue_fn_ty = T_ptr(T_fn(vec(T_ptr(T_i1()),
T_taskptr(tn),
T_ptr(T_nil()),
tydescpp,
@ -887,6 +887,10 @@ fn C_int(int i) -> ValueRef {
ret C_integral(i, T_int());
}
fn C_i8(uint i) -> ValueRef {
ret C_integral(i as int, T_i8());
}
// This is a 'c-like' raw string, which differs from
// our boxed-and-length-annotated strings.
fn C_cstr(@crate_ctxt cx, str s) -> ValueRef {
@ -1730,7 +1734,7 @@ fn make_generic_glue(@local_ctxt cx,
}
case (mgghf_cmp) {
auto llrawptr1 = llvm.LLVMGetParam(llfn, 5u);
auto llval1 = bcx.build.BitCast(llrawptr0, llty);
auto llval1 = bcx.build.BitCast(llrawptr1, llty);
auto llcmpval = llvm.LLVMGetParam(llfn, 6u);
@ -1980,9 +1984,257 @@ fn decr_refcnt_and_if_zero(@block_ctxt cx,
ret res(next_cx, phi);
}
fn make_cmp_glue(@block_ctxt cx, ValueRef v0, ValueRef v1, @ty.t t,
ValueRef llop) {
cx.build.RetVoid(); // TODO
// Structural comparison: a rather involved form of glue.
fn make_cmp_glue(@block_ctxt cx,
ValueRef lhs0,
ValueRef rhs0,
@ty.t t,
ValueRef llop) {
auto lhs = load_if_immediate(cx, lhs0, t);
auto rhs = load_if_immediate(cx, rhs0, t);
if (ty.type_is_scalar(t)) {
make_scalar_cmp_glue(cx, lhs, rhs, t, llop);
} else if (ty.type_is_box(t)) {
lhs = cx.build.GEP(lhs, vec(C_int(0), C_int(abi.box_rc_field_body)));
rhs = cx.build.GEP(rhs, vec(C_int(0), C_int(abi.box_rc_field_body)));
auto rslt = call_cmp_glue(cx, lhs, rhs, t, llop);
rslt.bcx.build.Store(rslt.val, cx.fcx.llretptr);
rslt.bcx.build.RetVoid();
} else if (ty.type_is_structural(t)
|| ty.type_is_sequence(t)) {
auto scx = new_sub_block_ctxt(cx, "structural compare start");
auto next = new_sub_block_ctxt(cx, "structural compare end");
cx.build.Br(scx.llbb);
/*
* We're doing lexicographic comparison here. We start with the
* assumption that the two input elements are equal. Depending on
* operator, this means that the result is either true or false;
* equality produces 'true' for ==, <= and >=. It produces 'false' for
* !=, < and >.
*
* We then move one element at a time through the structure checking
* for pairwise element equality. If we have equality, our assumption
* about overall sequence equality is not modified, so we have to move
* to the next element.
*
* If we do not have pairwise element equality, we have reached an
* element that 'decides' the lexicographic comparison. So we exit the
* loop with a flag that indicates the true/false sense of that
* decision, by testing the element again with the operator we're
* interested in.
*
* When we're lucky, LLVM should be able to fold some of these two
* tests together (as they're applied to the same operands and in some
* cases are sometimes redundant). But we don't bother trying to
* optimize combinations like that, at this level.
*/
auto flag = alloca(scx, T_i1());
llvm.LLVMSetValueName(flag, _str.buf("flag"));
auto r;
if (ty.type_is_sequence(t)) {
// If we hit == all the way through the minimum-shared-length
// section, default to judging the relative sequence lengths.
r = compare_integral_values(scx,
vec_fill(scx, lhs),
vec_fill(scx, rhs),
false,
llop);
r.bcx.build.Store(r.val, flag);
} else {
// == and <= default to true if they find == all the way. <
// defaults to false if it finds == all the way.
auto result_if_equal = scx.build.ICmp(lib.llvm.LLVMIntNE, llop,
C_i8(abi.cmp_glue_op_lt));
scx.build.Store(result_if_equal, flag);
r = res(scx, C_nil());
}
fn inner(@block_ctxt last_cx,
bool load_inner,
ValueRef flag,
ValueRef llop,
@block_ctxt cx,
ValueRef av0,
ValueRef bv0,
@ty.t t) -> result {
auto cnt_cx = new_sub_block_ctxt(cx, "continue_comparison");
auto stop_cx = new_sub_block_ctxt(cx, "stop_comparison");
auto av = av0;
auto bv = bv0;
if (load_inner) {
// If `load_inner` is true, then the pointer type will always
// be i8, because the data part of a vector always has type
// i8[]. So we need to cast it to the proper type.
if (!ty.type_has_dynamic_size(t)) {
auto llelemty = T_ptr(type_of(last_cx.fcx.lcx.ccx, t));
av = cx.build.PointerCast(av, llelemty);
bv = cx.build.PointerCast(bv, llelemty);
}
av = load_if_immediate(cx, av, t);
bv = load_if_immediate(cx, bv, t);
}
// First 'eq' comparison: if so, continue to next elts.
auto eq_r = call_cmp_glue(cx, av, bv, t,
C_i8(abi.cmp_glue_op_eq));
eq_r.bcx.build.CondBr(eq_r.val, cnt_cx.llbb, stop_cx.llbb);
// Second 'op' comparison: find out how this elt-pair decides.
auto stop_r = call_cmp_glue(stop_cx, av, bv, t, llop);
stop_r.bcx.build.Store(stop_r.val, flag);
stop_r.bcx.build.Br(last_cx.llbb);
ret res(cnt_cx, C_nil());
}
if (ty.type_is_structural(t)) {
r = iter_structural_ty_full(r.bcx, lhs, rhs, t,
bind inner(next, false, flag, llop,
_, _, _, _));
} else {
auto lhs_p0 = vec_p0(r.bcx, lhs);
auto rhs_p0 = vec_p0(r.bcx, rhs);
auto min_len = umin(r.bcx, vec_fill(r.bcx, lhs),
vec_fill(r.bcx, rhs));
auto rhs_lim = r.bcx.build.GEP(rhs_p0, vec(min_len));
auto elt_ty = ty.sequence_element_type(t);
r = size_of(r.bcx, elt_ty);
r = iter_sequence_raw(r.bcx, lhs_p0, rhs_p0, rhs_lim, r.val,
bind inner(next, true, flag, llop,
_, _, _, elt_ty));
}
r.bcx.build.Br(next.llbb);
auto v = next.build.Load(flag);
next.build.Store(v, cx.fcx.llretptr);
next.build.RetVoid();
} else {
// FIXME: compare obj, fn by pointer?
trans_fail(cx, none[common.span],
"attempt to compare values of type " + ty.ty_to_str(t));
}
}
// A helper function to create scalar comparison glue.
fn make_scalar_cmp_glue(@block_ctxt cx, ValueRef lhs, ValueRef rhs, @ty.t t,
ValueRef llop) {
if (ty.type_is_fp(t)) {
make_fp_cmp_glue(cx, lhs, rhs, t, llop);
ret;
}
if (ty.type_is_integral(t) || ty.type_is_bool(t)) {
make_integral_cmp_glue(cx, lhs, rhs, t, llop);
ret;
}
if (ty.type_is_nil(t)) {
cx.build.Store(C_bool(true), cx.fcx.llretptr);
cx.build.RetVoid();
ret;
}
trans_fail(cx, none[common.span],
"attempt to compare values of type " + ty.ty_to_str(t));
}
// A helper function to create floating point comparison glue.
fn make_fp_cmp_glue(@block_ctxt cx, ValueRef lhs, ValueRef rhs, @ty.t fptype,
ValueRef llop) {
auto last_cx = new_sub_block_ctxt(cx, "last");
auto eq_cx = new_sub_block_ctxt(cx, "eq");
auto eq_result = eq_cx.build.FCmp(lib.llvm.LLVMRealUEQ, lhs, rhs);
eq_cx.build.Br(last_cx.llbb);
auto lt_cx = new_sub_block_ctxt(cx, "lt");
auto lt_result = lt_cx.build.FCmp(lib.llvm.LLVMRealULT, lhs, rhs);
lt_cx.build.Br(last_cx.llbb);
auto le_cx = new_sub_block_ctxt(cx, "le");
auto le_result = le_cx.build.FCmp(lib.llvm.LLVMRealULE, lhs, rhs);
le_cx.build.Br(last_cx.llbb);
auto unreach_cx = new_sub_block_ctxt(cx, "unreach");
unreach_cx.build.Unreachable();
auto llswitch = cx.build.Switch(llop, unreach_cx.llbb, 3u);
llvm.LLVMAddCase(llswitch, C_i8(abi.cmp_glue_op_eq), eq_cx.llbb);
llvm.LLVMAddCase(llswitch, C_i8(abi.cmp_glue_op_lt), lt_cx.llbb);
llvm.LLVMAddCase(llswitch, C_i8(abi.cmp_glue_op_le), le_cx.llbb);
auto last_result =
last_cx.build.Phi(T_i1(), vec(eq_result, lt_result, le_result),
vec(eq_cx.llbb, lt_cx.llbb, le_cx.llbb));
last_cx.build.Store(last_result, cx.fcx.llretptr);
last_cx.build.RetVoid();
}
// A helper function to compare integral values. This is used by both
// `make_integral_cmp_glue` and `make_cmp_glue`.
fn compare_integral_values(@block_ctxt cx, ValueRef lhs, ValueRef rhs,
bool signed, ValueRef llop) -> result {
auto lt_cmp; auto le_cmp;
if (signed) {
lt_cmp = lib.llvm.LLVMIntSLT;
le_cmp = lib.llvm.LLVMIntSLE;
} else {
lt_cmp = lib.llvm.LLVMIntULT;
le_cmp = lib.llvm.LLVMIntULE;
}
auto last_cx = new_sub_block_ctxt(cx, "last");
auto eq_cx = new_sub_block_ctxt(cx, "eq");
auto eq_result = eq_cx.build.ICmp(lib.llvm.LLVMIntEQ, lhs, rhs);
eq_cx.build.Br(last_cx.llbb);
auto lt_cx = new_sub_block_ctxt(cx, "lt");
auto lt_result = lt_cx.build.ICmp(lt_cmp, lhs, rhs);
lt_cx.build.Br(last_cx.llbb);
auto le_cx = new_sub_block_ctxt(cx, "le");
auto le_result = le_cx.build.ICmp(le_cmp, lhs, rhs);
le_cx.build.Br(last_cx.llbb);
auto unreach_cx = new_sub_block_ctxt(cx, "unreach");
unreach_cx.build.Unreachable();
auto llswitch = cx.build.Switch(llop, unreach_cx.llbb, 3u);
llvm.LLVMAddCase(llswitch, C_i8(abi.cmp_glue_op_eq), eq_cx.llbb);
llvm.LLVMAddCase(llswitch, C_i8(abi.cmp_glue_op_lt), lt_cx.llbb);
llvm.LLVMAddCase(llswitch, C_i8(abi.cmp_glue_op_le), le_cx.llbb);
auto last_result =
last_cx.build.Phi(T_i1(), vec(eq_result, lt_result, le_result),
vec(eq_cx.llbb, lt_cx.llbb, le_cx.llbb));
ret res(last_cx, last_result);
}
// A helper function to create integral comparison glue.
fn make_integral_cmp_glue(@block_ctxt cx, ValueRef lhs, ValueRef rhs,
@ty.t intype, ValueRef llop) {
auto r = compare_integral_values(cx, lhs, rhs, ty.type_is_signed(intype),
llop);
r.bcx.build.Store(r.val, r.bcx.fcx.llretptr);
r.bcx.build.RetVoid();
}
@ -2403,6 +2655,42 @@ fn call_tydesc_glue(@block_ctxt cx, ValueRef v, @ty.t t, int field) {
td.val, field);
}
fn call_cmp_glue(@block_ctxt cx, ValueRef lhs, ValueRef rhs, @ty.t t,
ValueRef llop) -> result {
// We can't use call_tydesc_glue_full() and friends here because compare
// glue has a special signature.
auto lllhs = spill_if_immediate(cx, lhs, t);
auto llrhs = spill_if_immediate(cx, rhs, t);
auto llrawlhsptr = cx.build.BitCast(lllhs, T_ptr(T_i8()));
auto llrawrhsptr = cx.build.BitCast(llrhs, T_ptr(T_i8()));
auto r = get_tydesc(cx, t);
auto lltydescs =
r.bcx.build.GEP(r.val, vec(C_int(0),
C_int(abi.tydesc_field_first_param)));
lltydescs = r.bcx.build.Load(lltydescs);
auto llfnptr =
r.bcx.build.GEP(r.val, vec(C_int(0),
C_int(abi.tydesc_field_cmp_glue)));
auto llfn = r.bcx.build.Load(llfnptr);
auto llcmpresultptr = r.bcx.build.Alloca(T_i1());
let vec[ValueRef] llargs = vec(llcmpresultptr,
r.bcx.fcx.lltaskptr,
C_null(T_ptr(T_nil())),
lltydescs,
llrawlhsptr,
llrawrhsptr,
llop);
r.bcx.build.FastCall(llfn, llargs);
ret res(r.bcx, r.bcx.build.Load(llcmpresultptr));
}
fn take_ty(@block_ctxt cx, ValueRef v, @ty.t t) -> result {
if (!ty.type_is_scalar(t)) {
call_tydesc_glue(cx, v, t, abi.tydesc_field_take_glue);
@ -2661,7 +2949,7 @@ fn trans_unary(@block_ctxt cx, ast.unop op,
fn trans_compare(@block_ctxt cx0, ast.binop op, @ty.t t0,
ValueRef lhs0, ValueRef rhs0) -> result {
// Autoderef both sides.
auto cx = cx0;
auto lhs_r = autoderef(cx, lhs0, t0);
@ -2674,194 +2962,30 @@ fn trans_compare(@block_ctxt cx0, ast.binop op, @ty.t t0,
auto t = autoderefed_ty(t0);
if (ty.type_is_scalar(t)) {
ret res(cx, trans_scalar_compare(cx, op, t, lhs, rhs));
} else if (ty.type_is_structural(t)
|| ty.type_is_sequence(t)) {
auto scx = new_sub_block_ctxt(cx, "structural compare start");
auto next = new_sub_block_ctxt(cx, "structural compare end");
cx.build.Br(scx.llbb);
/*
* We're doing lexicographic comparison here. We start with the
* assumption that the two input elements are equal. Depending on
* operator, this means that the result is either true or false;
* equality produces 'true' for ==, <= and >=. It produces 'false' for
* !=, < and >.
*
* We then move one element at a time through the structure checking
* for pairwise element equality. If we have equality, our assumption
* about overall sequence equality is not modified, so we have to move
* to the next element.
*
* If we do not have pairwise element equality, we have reached an
* element that 'decides' the lexicographic comparison. So we exit the
* loop with a flag that indicates the true/false sense of that
* decision, by testing the element again with the operator we're
* interested in.
*
* When we're lucky, LLVM should be able to fold some of these two
* tests together (as they're applied to the same operands and in some
* cases are sometimes redundant). But we don't bother trying to
* optimize combinations like that, at this level.
*/
auto flag = alloca(scx, T_i1());
if (ty.type_is_sequence(t)) {
// If we hit == all the way through the minimum-shared-length
// section, default to judging the relative sequence lengths.
auto len_cmp =
trans_integral_compare(scx, op, plain_ty(ty.ty_uint),
vec_fill(scx, lhs),
vec_fill(scx, rhs));
scx.build.Store(len_cmp, flag);
} else {
auto T = C_integral(1, T_i1());
auto F = C_integral(0, T_i1());
alt (op) {
// ==, <= and >= default to true if they find == all the way.
case (ast.eq) { scx.build.Store(T, flag); }
case (ast.le) { scx.build.Store(T, flag); }
case (ast.ge) { scx.build.Store(T, flag); }
case (_) {
// < > default to false if they find == all the way.
scx.build.Store(F, flag);
}
}
}
fn inner(@block_ctxt last_cx,
bool load_inner,
ValueRef flag,
ast.binop op,
@block_ctxt cx,
ValueRef av0,
ValueRef bv0,
@ty.t t) -> result {
auto cnt_cx = new_sub_block_ctxt(cx, "continue comparison");
auto stop_cx = new_sub_block_ctxt(cx, "stop comparison");
auto av = av0;
auto bv = bv0;
if (load_inner) {
av = load_if_immediate(cx, av, t);
bv = load_if_immediate(cx, bv, t);
}
// First 'eq' comparison: if so, continue to next elts.
auto eq_r = trans_compare(cx, ast.eq, t, av, bv);
eq_r.bcx.build.CondBr(eq_r.val, cnt_cx.llbb, stop_cx.llbb);
// Second 'op' comparison: find out how this elt-pair decides.
auto stop_r = trans_compare(stop_cx, op, t, av, bv);
stop_r.bcx.build.Store(stop_r.val, flag);
stop_r.bcx.build.Br(last_cx.llbb);
ret res(cnt_cx, C_nil());
}
auto r;
if (ty.type_is_structural(t)) {
r = iter_structural_ty_full(scx, lhs, rhs, t,
bind inner(next, false, flag, op,
_, _, _, _));
} else {
auto lhs_p0 = vec_p0(scx, lhs);
auto rhs_p0 = vec_p0(scx, rhs);
auto min_len = umin(scx, vec_fill(scx, lhs), vec_fill(scx, rhs));
auto rhs_lim = scx.build.GEP(rhs_p0, vec(min_len));
auto elt_ty = ty.sequence_element_type(t);
auto elt_llsz_r = size_of(scx, elt_ty);
scx = elt_llsz_r.bcx;
r = iter_sequence_raw(scx, lhs_p0, rhs_p0, rhs_lim,
elt_llsz_r.val,
bind inner(next, true, flag, op,
_, _, _, elt_ty));
}
r.bcx.build.Br(next.llbb);
auto v = next.build.Load(flag);
ret res(next, v);
} else {
// FIXME: compare obj, fn by pointer?
cx.fcx.lcx.ccx.sess.unimpl("type in trans_compare");
ret res(cx, C_bool(false));
}
}
fn trans_scalar_compare(@block_ctxt cx, ast.binop op, @ty.t t,
ValueRef lhs, ValueRef rhs) -> ValueRef {
if (ty.type_is_fp(t)) {
ret trans_fp_compare(cx, op, t, lhs, rhs);
} else {
ret trans_integral_compare(cx, op, t, lhs, rhs);
}
}
fn trans_fp_compare(@block_ctxt cx, ast.binop op, @ty.t fptype,
ValueRef lhs, ValueRef rhs) -> ValueRef {
auto cmp = lib.llvm.LLVMIntEQ;
// Determine the operation we need.
// FIXME: Use or-patterns when we have them.
auto llop;
alt (op) {
// FIXME: possibly use the unordered-or-< predicates here,
// for now we're only going with ordered-and-< style (no NaNs).
case (ast.eq) { cmp = lib.llvm.LLVMRealOEQ; }
case (ast.ne) { cmp = lib.llvm.LLVMRealONE; }
case (ast.lt) { cmp = lib.llvm.LLVMRealOLT; }
case (ast.gt) { cmp = lib.llvm.LLVMRealOGT; }
case (ast.le) { cmp = lib.llvm.LLVMRealOLE; }
case (ast.ge) { cmp = lib.llvm.LLVMRealOGE; }
case (ast.eq) { llop = C_i8(abi.cmp_glue_op_eq); }
case (ast.lt) { llop = C_i8(abi.cmp_glue_op_lt); }
case (ast.le) { llop = C_i8(abi.cmp_glue_op_le); }
case (ast.ne) { llop = C_i8(abi.cmp_glue_op_eq); }
case (ast.ge) { llop = C_i8(abi.cmp_glue_op_lt); }
case (ast.gt) { llop = C_i8(abi.cmp_glue_op_le); }
}
ret cx.build.FCmp(cmp, lhs, rhs);
}
auto rslt = call_cmp_glue(cx, lhs, rhs, t, llop);
fn trans_integral_compare(@block_ctxt cx, ast.binop op, @ty.t intype,
ValueRef lhs, ValueRef rhs) -> ValueRef {
auto cmp = lib.llvm.LLVMIntEQ;
// Invert the result if necessary.
// FIXME: Use or-patterns when we have them.
alt (op) {
case (ast.eq) { cmp = lib.llvm.LLVMIntEQ; }
case (ast.ne) { cmp = lib.llvm.LLVMIntNE; }
case (ast.lt) {
if (ty.type_is_signed(intype)) {
cmp = lib.llvm.LLVMIntSLT;
} else {
cmp = lib.llvm.LLVMIntULT;
}
}
case (ast.le) {
if (ty.type_is_signed(intype)) {
cmp = lib.llvm.LLVMIntSLE;
} else {
cmp = lib.llvm.LLVMIntULE;
}
}
case (ast.gt) {
if (ty.type_is_signed(intype)) {
cmp = lib.llvm.LLVMIntSGT;
} else {
cmp = lib.llvm.LLVMIntUGT;
}
}
case (ast.ge) {
if (ty.type_is_signed(intype)) {
cmp = lib.llvm.LLVMIntSGE;
} else {
cmp = lib.llvm.LLVMIntUGE;
}
}
case (ast.eq) { ret res(rslt.bcx, rslt.val); }
case (ast.lt) { ret res(rslt.bcx, rslt.val); }
case (ast.le) { ret res(rslt.bcx, rslt.val); }
case (ast.ne) { ret res(rslt.bcx, rslt.bcx.build.Not(rslt.val)); }
case (ast.ge) { ret res(rslt.bcx, rslt.bcx.build.Not(rslt.val)); }
case (ast.gt) { ret res(rslt.bcx, rslt.bcx.build.Not(rslt.val)); }
}
ret cx.build.ICmp(cmp, lhs, rhs);
}
fn trans_vec_append(@block_ctxt cx, @ty.t t,
@ -3678,7 +3802,7 @@ fn trans_alt(@block_ctxt cx, @ast.expr expr,
}
auto default_cx = this_cx;
auto default_res = trans_fail(default_cx, expr.span,
auto default_res = trans_fail(default_cx, some[common.span](expr.span),
"non-exhaustive match failure");
// FIXME: This isn't quite right, particularly re: dynamic types
@ -3976,7 +4100,8 @@ fn trans_index(@block_ctxt cx, &ast.span sp, @ast.expr base,
bcx.build.CondBr(bounds_check, next_cx.llbb, fail_cx.llbb);
// fail: bad bounds check.
auto fail_res = trans_fail(fail_cx, sp, "bounds check");
auto fail_res = trans_fail(fail_cx, some[common.span](sp),
"bounds check");
auto body = next_cx.build.GEP(v, vec(C_int(0), C_int(abi.vec_elt_data)));
auto elt;
@ -4893,7 +5018,7 @@ fn trans_expr(@block_ctxt cx, @ast.expr e) -> result {
}
case (ast.expr_fail(_)) {
ret trans_fail(cx, e.span, "explicit failure");
ret trans_fail(cx, some[common.span](e.span), "explicit failure");
}
case (ast.expr_log(?lvl, ?a, _)) {
@ -5062,7 +5187,7 @@ fn trans_check_expr(@block_ctxt cx, @ast.expr e) -> result {
auto expr_str = pretty.pprust.expr_to_str(e);
auto fail_cx = new_sub_block_ctxt(cx, "fail");
auto fail_res = trans_fail(fail_cx, e.span, expr_str);
auto fail_res = trans_fail(fail_cx, some[common.span](e.span), expr_str);
auto next_cx = new_sub_block_ctxt(cx, "next");
cond_res.bcx.build.CondBr(cond_res.val,
@ -5071,11 +5196,23 @@ fn trans_check_expr(@block_ctxt cx, @ast.expr e) -> result {
ret res(next_cx, C_nil());
}
fn trans_fail(@block_ctxt cx, common.span sp, str fail_str) -> result {
fn trans_fail(@block_ctxt cx, option.t[common.span] sp_opt, str fail_str)
-> result {
auto V_fail_str = p2i(C_cstr(cx.fcx.lcx.ccx, fail_str));
auto loc = cx.fcx.lcx.ccx.sess.lookup_pos(sp.lo);
auto V_filename = p2i(C_cstr(cx.fcx.lcx.ccx, loc.filename));
auto V_line = loc.line as int;
auto V_filename; auto V_line;
alt (sp_opt) {
case (some[common.span](?sp)) {
auto loc = cx.fcx.lcx.ccx.sess.lookup_pos(sp.lo);
V_filename = p2i(C_cstr(cx.fcx.lcx.ccx, loc.filename));
V_line = loc.line as int;
}
case (none[common.span]) {
V_filename = p2i(C_str(cx.fcx.lcx.ccx, "<runtime>"));
V_line = 0;
}
}
auto args = vec(V_fail_str, V_filename, C_int(V_line));
auto sub = trans_upcall(cx, "upcall_fail", args);

View file

@ -437,6 +437,13 @@ fn type_is_nil(@t ty) -> bool {
fail;
}
fn type_is_bool(@t ty) -> bool {
alt (ty.struct) {
case (ty_bool) { ret true; }
case (_) { ret false; }
}
}
fn type_is_structural(@t ty) -> bool {
alt (ty.struct) {

View file

@ -13,4 +13,4 @@ fn main() {
check (vec(1,2,3,4) > vec(1,2,3));
check (vec(1,2,3) == vec(1,2,3));
check (vec(1,2,3) != vec(1,1,3));
}
}

View file

@ -0,0 +1,9 @@
tag taggy {
foo(@taggy);
bar;
}
fn main() {
check (bar <= bar);
}