From patchwork Thu Feb 15 12:49:50 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Georg-Johann Lay X-Patchwork-Id: 1899316 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@legolas.ozlabs.org Authentication-Results: legolas.ozlabs.org; dkim=pass (2048-bit key; unprotected) header.d=gjlay.de header.i=@gjlay.de header.a=rsa-sha256 header.s=strato-dkim-0002 header.b=bwVRkOAe; dkim=pass header.d=gjlay.de header.i=@gjlay.de header.a=ed25519-sha256 header.s=strato-dkim-0003 header.b=EGod3Z5k; dkim-atps=neutral Authentication-Results: legolas.ozlabs.org; spf=pass (sender SPF authorized) smtp.mailfrom=gcc.gnu.org (client-ip=2620:52:3:1:0:246e:9693:128c; helo=server2.sourceware.org; envelope-from=gcc-patches-bounces+incoming=patchwork.ozlabs.org@gcc.gnu.org; receiver=patchwork.ozlabs.org) Received: from server2.sourceware.org (server2.sourceware.org [IPv6:2620:52:3:1:0:246e:9693:128c]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature ECDSA (secp384r1) server-digest SHA384) (No client certificate requested) by legolas.ozlabs.org (Postfix) with ESMTPS id 4TbFLr1rGYz23gM for ; Thu, 15 Feb 2024 23:51:48 +1100 (AEDT) Received: from server2.sourceware.org (localhost [IPv6:::1]) by sourceware.org (Postfix) with ESMTP id 280F5384CBB0 for ; Thu, 15 Feb 2024 12:51:46 +0000 (GMT) X-Original-To: gcc-patches@gcc.gnu.org Delivered-To: gcc-patches@gcc.gnu.org Received: from mo4-p00-ob.smtp.rzone.de (mo4-p00-ob.smtp.rzone.de [85.215.255.23]) by sourceware.org (Postfix) with ESMTPS id 57709385022E for ; Thu, 15 Feb 2024 12:49:53 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org 57709385022E Authentication-Results: sourceware.org; dmarc=none (p=none dis=none) header.from=gjlay.de Authentication-Results: sourceware.org; spf=none smtp.mailfrom=gjlay.de ARC-Filter: OpenARC Filter v1.0.0 sourceware.org 57709385022E Authentication-Results: server2.sourceware.org; arc=pass smtp.remote-ip=85.215.255.23 ARC-Seal: i=2; a=rsa-sha256; d=sourceware.org; s=key; t=1708001401; cv=pass; b=mSrJhhp0PRu+R7HOWm70IFzg3EfqshicRNb6jYEIiDXjQbMFunBw1IIKsQ9/yWevQXnnmIXiIvmj5Ziu3Njh/ly+rYO3hWh6P1Mc9tFC6v+mugE9cy1Y411UlMIcw+bqGb1Dr2upHamCX2KOMFTo3et5/Qk/mgnAVp8HSNbk5jE= ARC-Message-Signature: i=2; a=rsa-sha256; d=sourceware.org; s=key; t=1708001401; c=relaxed/simple; bh=OzBOgSzsYvyaL3gW1IVyWnv6n5Ps5dMmzY9q1lpZEF0=; h=DKIM-Signature:DKIM-Signature:Message-ID:Date:MIME-Version:From: To:Subject; b=kbDtJ5hBKw8PgLrqlQb0gGuLPAIrfdfj/3IUNPh5GMhMLWoYCk2fZaisFDV3+BHHEy20Du66Tc53pOnEZGIDoF5w87OlbMlPPGPZRwbi5jUZbjmAUTo5FDCP2v4HwbjNjneU/bZz13EUve5zfF1YxifN/SmHkDAvPZeXArHohxM= ARC-Authentication-Results: i=2; server2.sourceware.org ARC-Seal: i=1; a=rsa-sha256; t=1708001392; cv=none; d=strato.com; s=strato-dkim-0002; b=PmEI2Ay0RetZjhIn88xmwaQXzfKdHJeEHPMS2LcSbOpkwf99ngU8fE8mj/Epc/vEI8 HQvar9N84f73xrOJ3aRhC8rmqaNmxDLNOPez2A3pIqmjwTH3yO3f+gKNsv+hXxxt9+7j 1EOO+gCS1z4pHpBFVJ73nz8XXvBWCunBYGlOtA38wokaqnqPG5YNNCGgfd6qwH7xcwby sYEaaFJLlPe3p0GMFRK3NNnuRpU0KfhKJnFgds3vm/iiKmH2LCt6HhbE9g0OphQHskvg 50U/VJncE6xEuVMQwle3KUfYWEnfFSQtmbdYYwBEURwYkO/Kjb2/cGZ66Kz4TEau3EzL A7Ew== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; t=1708001392; s=strato-dkim-0002; d=strato.com; h=Subject:To:From:Date:Message-ID:Cc:Date:From:Subject:Sender; bh=Rm+cDmSpNlqeYUn9+cCuyJbmnADTnku6FJcEZoQ+F8c=; b=J5Ee0UZuvtIjGlXcBBtSfIDtm3tbTFIVNheGCAIO0vdrfFiuWbICvAZl7X03ydiwCW GJYc6np/xFzJLu1GEV7uFQ4IOYMH++fn+aqkW9BseItxHCsUj6xdHnwZogYOfNoqWvTR ZT980yIxQlMgViVe0RgqwX9TRsmgMvFvRSh+MzLlIOOA5ESoiPpBCh2rPuDCyoB6n2HT IFf6Se0068CJpeKgiM9gkitZGjgPgZUB1+jsExVi5v/WmGuFnL8Fc94iA9qLwCzcrhM8 ZjuitUYib4bggHSgxUK496BJZ3z/OsZA+7tvBcybfaW+OScZe8TxQX8lIW+zexIdQTWo vdCw== ARC-Authentication-Results: i=1; strato.com; arc=none; dkim=none X-RZG-CLASS-ID: mo00 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; t=1708001392; s=strato-dkim-0002; d=gjlay.de; h=Subject:To:From:Date:Message-ID:Cc:Date:From:Subject:Sender; bh=Rm+cDmSpNlqeYUn9+cCuyJbmnADTnku6FJcEZoQ+F8c=; b=bwVRkOAeWdWWbK45bd200Q7Rq2OSMDRkoehz90DQBVbrYItQJIJLVCzpghHCXAYOPJ eG0Lw5Is4lHlMYC8w3z0B9gFHjib0zDnYfILMcfRn4PisFFHGB0dvz7at3u74Kokno0T OGGT4+kkBPZ9NJLoZ/ipDZa7j/gRdFejntmH9vU55g4cvHXVjOy77dl2v6Caa8R4tHHw FIv2xqQjkDHux/6+j2riNb/LPOUi2tuMTAux7of4vNaGvbtAPcai6YT4wlQT1I9ou8nU +qDJf2lYnmQVLHh+uLz5uan2PgIVVPL4KFX6VidzOmT0fJqVMPavjK5oC+kAMSGt2NzY xgYQ== DKIM-Signature: v=1; a=ed25519-sha256; c=relaxed/relaxed; t=1708001392; s=strato-dkim-0003; d=gjlay.de; h=Subject:To:From:Date:Message-ID:Cc:Date:From:Subject:Sender; bh=Rm+cDmSpNlqeYUn9+cCuyJbmnADTnku6FJcEZoQ+F8c=; b=EGod3Z5kz09xSXFVl7CwMzGlR0tme97dVwogO5LwPuTsqfQJkGzHO9TPHauifdG8vN wqaXwcN9v0NcfkWjhGAg== X-RZG-AUTH: ":LXoWVUeid/7A29J/hMvvT3koxZnKT7Qq0xotTetVnKkSgcSjpmy9KwoMJ/K0VA==" Received: from [192.168.2.102] by smtp.strato.de (RZmta 49.11.2 DYNA|AUTH) with ESMTPSA id Lb68c601FCnp78B (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256 bits)) (Client did not present a certificate) for ; Thu, 15 Feb 2024 13:49:51 +0100 (CET) Message-ID: Date: Thu, 15 Feb 2024 13:49:50 +0100 MIME-Version: 1.0 User-Agent: Mozilla Thunderbird Content-Language: en-US From: Georg-Johann Lay To: gcc-patches@gcc.gnu.org Subject: [patch,avr,applied] Fix PR target/113927: Simple code triggers a stack frame X-Spam-Status: No, score=-10.7 required=5.0 tests=BAYES_00, DKIM_SIGNED, DKIM_VALID, DKIM_VALID_AU, DKIM_VALID_EF, GIT_PATCH_0, RCVD_IN_DNSWL_NONE, SPF_HELO_PASS, SPF_NONE, TXREP, T_SCC_BODY_TEXT_LINE autolearn=ham autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on server2.sourceware.org X-BeenThere: gcc-patches@gcc.gnu.org X-Mailman-Version: 2.1.30 Precedence: list List-Id: Gcc-patches mailing list List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: gcc-patches-bounces+incoming=patchwork.ozlabs.org@gcc.gnu.org Applied this patch Johann --- AVR: target 113927 - Simple code triggers stack frame for Reduced Tiny. The -mmcu=avrtiny cores have no ADIW and SBIW instructions. This was implemented by clearing all regs out of regclass ADDW_REGS so that constraint "w" never matched. This corrupted the subset relations of the register classes as they appear in enum reg_class. This patch keeps ADDW_REGS like for all other cores, i.e. it contains R24...R31. Instead of tests like test_hard_reg_class (ADDW_REGS, *) the code now uses avr_adiw_reg_p (*). And all insns with constraint "w" get "isa" insn attribute value of "adiw". Plus, a new built-in macro __AVR_HAVE_ADIW__ is provided, which is more specific than __AVR_TINY__. gcc/ PR target/113927 * config/avr/avr.h (AVR_HAVE_ADIW): New macro. * config/avr/avr-protos.h (avr_adiw_reg_p): New proto. * config/avr/avr.cc (avr_adiw_reg_p): New function. (avr_conditional_register_usage) [AVR_TINY]: Don't clear ADDW_REGS. Replace test_hard_reg_class (ADDW_REGS, ...) with calls to * config/avr/avr.md: Same. (attr "isa") : Remove. : Add. (define_insn, define_insn_and_split): When an alternative has constraint "w", then set attribute "isa" to "adiw". * config/avr/avr-c.cc (avr_cpu_cpp_builtins) [AVR_HAVE_ADIW]: Built-in define __AVR_HAVE_ADIW__. * doc/invoke.texi (AVR Options): Document it. diff --git a/gcc/config/avr/avr-c.cc b/gcc/config/avr/avr-c.cc index 60905a76556..5e7f759ed73 100644 --- a/gcc/config/avr/avr-c.cc +++ b/gcc/config/avr/avr-c.cc @@ -307,6 +307,7 @@ avr_cpu_cpp_builtins (struct cpp_reader *pfile) if (AVR_HAVE_ELPMX) cpp_define (pfile, "__AVR_HAVE_ELPMX__"); if (AVR_HAVE_MOVW) cpp_define (pfile, "__AVR_HAVE_MOVW__"); if (AVR_HAVE_LPMX) cpp_define (pfile, "__AVR_HAVE_LPMX__"); + if (AVR_HAVE_ADIW) cpp_define (pfile, "__AVR_HAVE_ADIW__"); if (avr_arch->asm_only) cpp_define (pfile, "__AVR_ASM_ONLY__"); diff --git a/gcc/config/avr/avr-protos.h b/gcc/config/avr/avr-protos.h index 46b75f96b9c..7d1f815c664 100644 --- a/gcc/config/avr/avr-protos.h +++ b/gcc/config/avr/avr-protos.h @@ -123,6 +123,7 @@ extern enum reg_class avr_mode_code_base_reg_class (machine_mode, addr_space_t, extern bool avr_regno_mode_code_ok_for_base_p (int, machine_mode, addr_space_t, RTX_CODE, RTX_CODE); extern rtx avr_incoming_return_addr_rtx (void); extern rtx avr_legitimize_reload_address (rtx*, machine_mode, int, int, int, int, rtx (*)(rtx,int)); +extern bool avr_adiw_reg_p (rtx); extern bool avr_mem_flash_p (rtx); extern bool avr_mem_memx_p (rtx); extern bool avr_load_libgcc_p (rtx); diff --git a/gcc/config/avr/avr.cc b/gcc/config/avr/avr.cc index d21b286ed8b..4a55f14bff7 100644 --- a/gcc/config/avr/avr.cc +++ b/gcc/config/avr/avr.cc @@ -292,6 +292,17 @@ avr_to_int_mode (rtx x) : simplify_gen_subreg (int_mode_for_mode (mode).require (), x, mode, 0); } + +/* Return true if hard register REG supports the ADIW and SBIW instructions. */ + +bool +avr_adiw_reg_p (rtx reg) +{ + return (AVR_HAVE_ADIW + && test_hard_reg_class (ADDW_REGS, reg)); +} + + namespace { static const pass_data avr_pass_data_recompute_notes = @@ -6272,7 +6283,7 @@ avr_out_compare (rtx_insn *insn, rtx *xop, int *plen) /* Word registers >= R24 can use SBIW/ADIW with 0..63. */ if (i == 0 - && test_hard_reg_class (ADDW_REGS, reg8)) + && avr_adiw_reg_p (reg8)) { int val16 = trunc_int_for_mode (INTVAL (xval), HImode); @@ -8186,7 +8197,7 @@ avr_out_plus_1 (rtx *xop, int *plen, enum rtx_code code, int *pcc, if (!started && i % 2 == 0 && i + 2 <= n_bytes - && test_hard_reg_class (ADDW_REGS, reg8)) + && avr_adiw_reg_p (reg8)) { rtx xval16 = simplify_gen_subreg (HImode, xval, imode, i); unsigned int val16 = UINTVAL (xval16) & GET_MODE_MASK (HImode); @@ -8678,7 +8689,7 @@ avr_out_plus_set_ZN (rtx *xop, int *plen) } if (n_bytes == 2 - && test_hard_reg_class (ADDW_REGS, xreg) + && avr_adiw_reg_p (xreg) && IN_RANGE (INTVAL (xval), 1, 63)) { // Add 16-bit value in [1..63] to a w register. @@ -8705,7 +8716,7 @@ avr_out_plus_set_ZN (rtx *xop, int *plen) if (i == 0 && n_bytes >= 2 - && test_hard_reg_class (ADDW_REGS, op[0])) + && avr_adiw_reg_p (op[0])) { op[1] = simplify_gen_subreg (HImode, xval, mode, 0); if (IN_RANGE (INTVAL (op[1]), 0, 63)) @@ -13312,7 +13323,6 @@ avr_conditional_register_usage (void) reg_alloc_order[i] = tiny_reg_alloc_order[i]; } - CLEAR_HARD_REG_SET (reg_class_contents[(int) ADDW_REGS]); CLEAR_HARD_REG_SET (reg_class_contents[(int) NO_LD_REGS]); } } @@ -14043,7 +14053,7 @@ avr_out_cpymem (rtx_insn *insn ATTRIBUTE_UNUSED, rtx *op, int *plen) { addr_space_t as = (addr_space_t) INTVAL (op[0]); machine_mode loop_mode = GET_MODE (op[1]); - bool sbiw_p = test_hard_reg_class (ADDW_REGS, op[1]); + bool sbiw_p = avr_adiw_reg_p (op[1]); rtx xop[3]; if (plen) diff --git a/gcc/config/avr/avr.h b/gcc/config/avr/avr.h index 7f7e23183b2..ff2738df78c 100644 --- a/gcc/config/avr/avr.h +++ b/gcc/config/avr/avr.h @@ -65,6 +65,7 @@ enum #define AVR_HAVE_JMP_CALL (avr_arch->have_jmp_call && ! AVR_SHORT_CALLS) #define AVR_HAVE_MUL (avr_arch->have_mul) #define AVR_HAVE_MOVW (avr_arch->have_movw_lpmx) +#define AVR_HAVE_ADIW (!AVR_TINY) #define AVR_HAVE_LPM (!AVR_TINY) #define AVR_HAVE_LPMX (avr_arch->have_movw_lpmx) #define AVR_HAVE_ELPM (avr_arch->have_elpm) diff --git a/gcc/config/avr/avr.md b/gcc/config/avr/avr.md index 68470b12e3d..49e586723ab 100644 --- a/gcc/config/avr/avr.md +++ b/gcc/config/avr/avr.md @@ -176,10 +176,11 @@ (define_attr "adjust_len" ;; lpm : ISA has no LPMX lpmx : ISA has LPMX ;; elpm : ISA has ELPM but no ELPMX elpmx : ISA has ELPMX ;; no_xmega: non-XMEGA core xmega : XMEGA core -;; no_tiny: non-TINY core tiny : TINY core +;; no_adiw: ISA has no ADIW, SBIW adiw : ISA has ADIW, SBIW (define_attr "isa" - "mov,movw, rjmp,jmp, ijmp,eijmp, lpm,lpmx, elpm,elpmx, no_xmega,xmega, no_tiny,tiny, + "mov,movw, rjmp,jmp, ijmp,eijmp, lpm,lpmx, elpm,elpmx, no_xmega,xmega, + no_adiw,adiw, standard" (const_string "standard")) @@ -231,16 +232,16 @@ (define_attr "enabled" "" (match_test "AVR_XMEGA")) (const_int 1) - (and (eq_attr "isa" "tiny") - (match_test "AVR_TINY")) - (const_int 1) - (and (eq_attr "isa" "no_xmega") (match_test "!AVR_XMEGA")) (const_int 1) - (and (eq_attr "isa" "no_tiny") - (match_test "!AVR_TINY")) + (and (eq_attr "isa" "adiw") + (match_test "AVR_HAVE_ADIW")) + (const_int 1) + + (and (eq_attr "isa" "no_adiw") + (match_test "!AVR_HAVE_ADIW")) (const_int 1) ] (const_int 0))) @@ -1325,7 +1326,9 @@ (define_insn_and_split "*clrmemhi_split" (use (match_dup 2)) (clobber (match_dup 3)) (clobber (match_dup 4)) - (clobber (reg:CC REG_CC))])]) + (clobber (reg:CC REG_CC))])] + "" + [(set_attr "isa" "adiw,*")]) (define_insn "*clrmemhi" @@ -1340,7 +1343,8 @@ (define_insn "*clrmemhi" "@ 0:\;st %a0+,__zero_reg__\;sbiw %A1,1\;brne 0b 0:\;st %a0+,__zero_reg__\;subi %A1,1\;sbci %B1,0\;brne 0b" - [(set_attr "length" "3,4")]) + [(set_attr "length" "3,4") + (set_attr "isa" "adiw,*")]) (define_expand "strlenhi" [(set (match_dup 4) @@ -1627,7 +1631,9 @@ (define_insn_and_split "*add3_split" [(parallel [(set (match_dup 0) (plus:ALL2 (match_dup 1) (match_dup 2))) - (clobber (reg:CC REG_CC))])]) + (clobber (reg:CC REG_CC))])] + "" + [(set_attr "isa" "*,*,adiw,*")]) (define_insn "*add3" [(set (match_operand:ALL2 0 "register_operand" "=??r,d,!w ,d") @@ -1639,6 +1645,7 @@ (define_insn "*add3" return avr_out_plus (insn, operands); } [(set_attr "length" "2") + (set_attr "isa" "*,*,adiw,*") (set_attr "adjust_len" "plus")]) ;; Adding a constant to NO_LD_REGS might have lead to a reload of @@ -2306,7 +2313,9 @@ (define_insn_and_split "*addhi3.lt0_split" (const_int 0)) (match_dup 2))) (clobber (match_dup 3)) - (clobber (reg:CC REG_CC))])]) + (clobber (reg:CC REG_CC))])] + "" + [(set_attr "isa" "adiw,*")]) (define_insn "*addhi3.lt0" [(set (match_operand:HI 0 "register_operand" "=w,r") @@ -2319,7 +2328,8 @@ (define_insn "*addhi3.lt0" "@ sbrc %1,7\;adiw %0,1 lsl %1\;adc %A0,__zero_reg__\;adc %B0,__zero_reg__" - [(set_attr "length" "2,3")]) + [(set_attr "length" "2,3") + (set_attr "isa" "adiw,*")]) (define_insn_and_split "*addpsi3.lt0_split" [(set (match_operand:PSI 0 "register_operand" "=r") @@ -6634,6 +6644,7 @@ (define_insn "cmp3" return avr_out_compare (insn, operands, NULL); } [(set_attr "length" "2,2,2,3,4,2,4") + (set_attr "isa" "adiw,*,*,*,*,*,*") (set_attr "adjust_len" "tsthi,tsthi,*,*,*,compare,compare")]) (define_insn "*cmppsi" @@ -7607,7 +7618,7 @@ (define_peephole ; "*dec-and-branchsi!=-1.d.clobber" { const char *op; int jump_mode; - if (test_hard_reg_class (ADDW_REGS, operands[0])) + if (avr_adiw_reg_p (operands[0])) output_asm_insn ("sbiw %0,1" CR_TAB "sbc %C0,__zero_reg__" CR_TAB "sbc %D0,__zero_reg__", operands); @@ -7650,7 +7661,7 @@ (define_peephole ; "*dec-and-branchhi!=-1" { const char *op; int jump_mode; - if (test_hard_reg_class (ADDW_REGS, operands[0])) + if (avr_adiw_reg_p (operands[0])) output_asm_insn ("sbiw %0,1", operands); else output_asm_insn ("subi %A0,1" CR_TAB @@ -7691,7 +7702,7 @@ (define_peephole ; "*dec-and-branchhi!=-1.d.clobber" { const char *op; int jump_mode; - if (test_hard_reg_class (ADDW_REGS, operands[0])) + if (avr_adiw_reg_p (operands[0])) output_asm_insn ("sbiw %0,1", operands); else output_asm_insn ("subi %A0,1" CR_TAB @@ -8065,7 +8076,7 @@ (define_insn_and_split "delay_cycles_2" (clobber (match_dup 2)) (clobber (reg:CC REG_CC))])] "" - [(set_attr "isa" "no_tiny,tiny")]) + [(set_attr "isa" "adiw,no_adiw")]) (define_insn "*delay_cycles_2" [(unspec_volatile [(match_operand:HI 0 "const_int_operand" "n,n") @@ -8080,7 +8091,7 @@ (define_insn "*delay_cycles_2" ldi %A2,lo8(%0)\;ldi %B2,hi8(%0)\n1: sbiw %A2,1\;brne 1b ldi %A2,lo8(%0)\;ldi %B2,hi8(%0)\n1: subi %A2,1\;sbci %B2,0\;brne 1b" [(set_attr "length" "4,5") - (set_attr "isa" "no_tiny,tiny")]) + (set_attr "isa" "adiw,no_adiw")]) (define_insn_and_split "delay_cycles_3" [(unspec_volatile [(match_operand:SI 0 "const_int_operand" "n") diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi index 0de184f6241..511114cde59 100644 --- a/gcc/doc/invoke.texi +++ b/gcc/doc/invoke.texi @@ -24146,6 +24146,9 @@ If @var{device} is not a device but only a core architecture like @item __AVR_XMEGA__ The device / architecture belongs to the XMEGA family of devices. +@item __AVR_HAVE_ADIW__ +The device has the @code{ADIW} and @code{SBIW} instructions. + @item __AVR_HAVE_ELPM__ The device has the @code{ELPM} instruction. @@ -24153,14 +24156,14 @@ The device has the @code{ELPM} instruction. The device has the @code{ELPM R@var{n},Z} and @code{ELPM R@var{n},Z+} instructions. -@item __AVR_HAVE_MOVW__ -The device has the @code{MOVW} instruction to perform 16-bit -register-register moves. - @item __AVR_HAVE_LPMX__ The device has the @code{LPM R@var{n},Z} and @code{LPM R@var{n},Z+} instructions. +@item __AVR_HAVE_MOVW__ +The device has the @code{MOVW} instruction to perform 16-bit +register-register moves. + @item __AVR_HAVE_MUL__ The device has a hardware multiplier.