diff --git a/cranelift/filetests/wasm/conversions.cton b/cranelift/filetests/wasm/conversions.cton index e742ebba27..a9bf625ca1 100644 --- a/cranelift/filetests/wasm/conversions.cton +++ b/cranelift/filetests/wasm/conversions.cton @@ -49,25 +49,47 @@ ebb0(v0: i32): return v1 } +function %f32_convert_u_i32(i32) -> f32 { +ebb0(v0: i32): + v1 = fcvt_from_uint.f32 v0 + return v1 +} + function %f64_convert_s_i32(i32) -> f64 { ebb0(v0: i32): v1 = fcvt_from_sint.f64 v0 return v1 } +function %f64_convert_u_i32(i32) -> f64 { +ebb0(v0: i32): + v1 = fcvt_from_uint.f64 v0 + return v1 +} + function %f32_convert_s_i64(i64) -> f32 { ebb0(v0: i64): v1 = fcvt_from_sint.f32 v0 return v1 } +function %f32_convert_u_i64(i64) -> f32 { +ebb0(v0: i64): + v1 = fcvt_from_uint.f32 v0 + return v1 +} + function %f64_convert_s_i64(i64) -> f64 { ebb0(v0: i64): v1 = fcvt_from_sint.f64 v0 return v1 } -; TODO: f*_convert_u_i* (Don't exist on Intel). +function %f64_convert_u_i64(i64) -> f64 { +ebb0(v0: i64): + v1 = fcvt_from_uint.f64 v0 + return v1 +} function %i32_reinterpret_f32(f32) -> i32 { ebb0(v0: f32): diff --git a/lib/cretonne/meta/cdsl/ast.py b/lib/cretonne/meta/cdsl/ast.py index 619c2d36a2..d6c0f42952 100644 --- a/lib/cretonne/meta/cdsl/ast.py +++ b/lib/cretonne/meta/cdsl/ast.py @@ -7,7 +7,7 @@ for patern matching an rewriting of cretonne instructions. from __future__ import absolute_import from . import instructions from .typevar import TypeVar -from .predicates import IsEqual, And, TypePredicate +from .predicates import IsEqual, And, TypePredicate, CtrlTypePredicate try: from typing import Union, Tuple, Sequence, TYPE_CHECKING, Dict, List # noqa @@ -383,12 +383,38 @@ class Apply(Expr): pred = And.combine(pred, IsEqual(ffield, arg)) - # Add checks for any bound type variables. - for bound_ty, tv in zip(self.typevars, self.inst.all_typevars()): - if bound_ty is None: - continue - type_chk = TypePredicate.typevar_check(self.inst, tv, bound_ty) - pred = And.combine(pred, type_chk) + # Add checks for any bound secondary type variables. + # We can't check the controlling type variable this way since it may + # not appear as the type of an operand. + if len(self.typevars) > 1: + for bound_ty, tv in zip(self.typevars[1:], + self.inst.other_typevars): + if bound_ty is None: + continue + type_chk = TypePredicate.typevar_check(self.inst, tv, bound_ty) + pred = And.combine(pred, type_chk) + + return pred + + def inst_predicate_with_ctrl_typevar(self): + # type: () -> PredNode + """ + Same as `inst_predicate()`, but also check the controlling type + variable. + """ + pred = self.inst_predicate() + + if len(self.typevars) > 0: + bound_ty = self.typevars[0] + type_chk = None # type: PredNode + if bound_ty is not None: + # Prefer to look at the types of input operands. + if self.inst.use_typevar_operand: + type_chk = TypePredicate.typevar_check( + self.inst, self.inst.ctrl_typevar, bound_ty) + else: + type_chk = CtrlTypePredicate(bound_ty) + pred = And.combine(pred, type_chk) return pred diff --git a/lib/cretonne/meta/cdsl/predicates.py b/lib/cretonne/meta/cdsl/predicates.py index f1f5232272..9a28fd9945 100644 --- a/lib/cretonne/meta/cdsl/predicates.py +++ b/lib/cretonne/meta/cdsl/predicates.py @@ -35,7 +35,8 @@ try: from .typevar import TypeVar # noqa PredContext = Union[SettingGroup, InstructionFormat, InstructionContext] - PredLeaf = Union[BoolSetting, 'FieldPredicate', 'TypePredicate'] + PredLeaf = Union[BoolSetting, 'FieldPredicate', 'TypePredicate', + 'CtrlTypePredicate'] PredNode = Union[PredLeaf, 'Predicate'] # A predicate key is a (recursive) tuple of primitive types that # uniquely describes a predicate. It is used for interning. @@ -373,3 +374,42 @@ class TypePredicate(object): """ return 'dfg.value_type(args[{}]) == {}'.format( self.value_arg, self.value_type.rust_name()) + + +class CtrlTypePredicate(object): + """ + An instruction predicate that checks the controlling type variable + + :param value_type: The required value type. + """ + + def __init__(self, value_type): + # type: (ValueType) -> None + assert value_type is not None + self.value_type = value_type + + def __str__(self): + # type: () -> str + return 'ctrl_typevar:{}'.format(self.value_type) + + def predicate_context(self): + # type: () -> PredContext + return instruction_context + + def predicate_key(self): + # type: () -> PredKey + return ('ctrltypecheck', self.value_type.name) + + def predicate_leafs(self, leafs): + # type: (Set[PredLeaf]) -> None + leafs.add(self) + + def rust_predicate(self, prec): + # type: (int) -> str + """ + Return Rust code for evaluating this predicate. + + It is assumed that the context has `dfg` and `inst` variables. + """ + return 'dfg.ctrl_typevar(inst) == {}'.format( + self.value_type.rust_name()) diff --git a/lib/cretonne/meta/gen_legalizer.py b/lib/cretonne/meta/gen_legalizer.py index 1b39918658..c27df484cf 100644 --- a/lib/cretonne/meta/gen_legalizer.py +++ b/lib/cretonne/meta/gen_legalizer.py @@ -199,7 +199,7 @@ def unwrap_inst(iref, node, fmt): n = expr.inst.value_opnums.index(opnum) fmt.format('dfg.resolve_aliases(args[{}]),', n) # Evaluate the instruction predicate, if any. - instp = expr.inst_predicate() + instp = expr.inst_predicate_with_ctrl_typevar() fmt.line(instp.rust_predicate(0) if instp else 'true') fmt.outdented_line('} else {') fmt.line('unreachable!("bad instruction format")') diff --git a/lib/cretonne/meta/isa/intel/legalize.py b/lib/cretonne/meta/isa/intel/legalize.py index 8d93337c69..d698208ccc 100644 --- a/lib/cretonne/meta/isa/intel/legalize.py +++ b/lib/cretonne/meta/isa/intel/legalize.py @@ -96,3 +96,6 @@ for cc, rev_cc in [ # We need to modify the CFG for min/max legalization. intel_expand.custom_legalize(insts.fmin, 'expand_minmax') intel_expand.custom_legalize(insts.fmax, 'expand_minmax') + +# Conversions from unsigned need special handling. +intel_expand.custom_legalize(insts.fcvt_from_uint, 'expand_fcvt_from_uint') diff --git a/lib/cretonne/src/isa/intel/enc_tables.rs b/lib/cretonne/src/isa/intel/enc_tables.rs index 4c8c428bfc..56ccdd15b2 100644 --- a/lib/cretonne/src/isa/intel/enc_tables.rs +++ b/lib/cretonne/src/isa/intel/enc_tables.rs @@ -62,6 +62,7 @@ fn expand_minmax(inst: ir::Inst, func: &mut ir::Function, cfg: &mut ControlFlowG // Test for case 1) ordered and not equal. let mut pos = FuncCursor::new(func).at_inst(inst); + pos.use_srcloc(inst); let cmp_ueq = pos.ins().fcmp(FloatCC::UnorderedOrEqual, x, y); pos.ins().brnz(cmp_ueq, ueq_ebb, &[]); @@ -101,3 +102,73 @@ fn expand_minmax(inst: ir::Inst, func: &mut ir::Function, cfg: &mut ControlFlowG cfg.recompute_ebb(pos.func, uno_ebb); cfg.recompute_ebb(pos.func, done); } + +/// Intel has no unsigned-to-float conversions. We handle the easy case of zero-extending i32 to +/// i64 with a pattern, the rest needs more code. +fn expand_fcvt_from_uint(inst: ir::Inst, func: &mut ir::Function, cfg: &mut ControlFlowGraph) { + use ir::condcodes::IntCC; + + let x; + match func.dfg[inst] { + ir::InstructionData::Unary { + opcode: ir::Opcode::FcvtFromUint, + arg, + } => x = arg, + _ => panic!("Need fcvt_from_uint: {}", func.dfg.display_inst(inst, None)), + } + let xty = func.dfg.value_type(x); + let result = func.dfg.first_result(inst); + let ty = func.dfg.value_type(result); + let mut pos = FuncCursor::new(func).at_inst(inst); + pos.use_srcloc(inst); + + // Conversion from unsigned 32-bit is easy on x86-64. + // TODO: This should be guarded by an ISA check. + if xty == ir::types::I32 { + let wide = pos.ins().uextend(ir::types::I64, x); + pos.func.dfg.replace(inst).fcvt_from_sint(ty, wide); + return; + } + + let old_ebb = pos.func.layout.pp_ebb(inst); + + // EBB handling the case where x < 0. + let neg_ebb = pos.func.dfg.make_ebb(); + + // Final EBB with one argument representing the final result value. + let done = pos.func.dfg.make_ebb(); + + // Move the `inst` result value onto the `done` EBB. + pos.func.dfg.clear_results(inst); + pos.func.dfg.attach_ebb_arg(done, result); + + // If x as a signed int is not negative, we can use the existing `fcvt_from_sint` instruction. + let is_neg = pos.ins().icmp_imm(IntCC::SignedLessThan, x, 0); + pos.ins().brnz(is_neg, neg_ebb, &[]); + + // Easy case: just use a signed conversion. + let posres = pos.ins().fcvt_from_sint(ty, x); + pos.ins().jump(done, &[posres]); + + // Now handle the negative case. + pos.insert_ebb(neg_ebb); + + // Divide x by two to get it in range for the signed conversion, keep the LSB, and scale it + // back up on the FP side. + let ihalf = pos.ins().ushr_imm(x, 1); + let lsb = pos.ins().band_imm(x, 1); + let ifinal = pos.ins().bor(ihalf, lsb); + let fhalf = pos.ins().fcvt_from_sint(ty, ifinal); + let negres = pos.ins().fadd(fhalf, fhalf); + + // Recycle the original instruction as a jump. + pos.func.dfg.replace(inst).jump(done, &[negres]); + + // Finally insert a label for the completion. + pos.next_inst(); + pos.insert_ebb(done); + + cfg.recompute_ebb(pos.func, old_ebb); + cfg.recompute_ebb(pos.func, neg_ebb); + cfg.recompute_ebb(pos.func, done); +}