From 34146435e5763bf9ea92b156d3b67de3733efa19 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Thu, 28 Sep 2017 09:05:39 -0700 Subject: [PATCH] Legalize unsigned-to-float conversions for Intel 64. Also make sure we generate type checks for the controlling type variable in legalization patterns. This is not needed for encodings since the encoding tables are already keyed on the controlling type variable. --- cranelift/filetests/wasm/conversions.cton | 24 +++++++- lib/cretonne/meta/cdsl/ast.py | 40 ++++++++++--- lib/cretonne/meta/cdsl/predicates.py | 42 +++++++++++++- lib/cretonne/meta/gen_legalizer.py | 2 +- lib/cretonne/meta/isa/intel/legalize.py | 3 + lib/cretonne/src/isa/intel/enc_tables.rs | 71 +++++++++++++++++++++++ 6 files changed, 172 insertions(+), 10 deletions(-) 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); +}