United StatesChange Country, Oracle Worldwide Web Sites Communities I am a... I want to...
Bug ID: JDK-5032515 Rematerialization of constants may produce multiple copes
JDK-5032515 : Rematerialization of constants may produce multiple copes

Details
Type:
Bug
Submit Date:
2004-04-15
Status:
Resolved
Updated Date:
2010-05-20
Project Name:
JDK
Resolved Date:
2006-12-02
Component:
hotspot
OS:
solaris_8
Sub-Component:
compiler
CPU:
sparc
Priority:
P4
Resolution:
Fixed
Affected Versions:
5.0
Fixed Versions:
hs10 (b03)

Related Reports
Backport:
Backport:
Relates:

Sub Tasks

Description
  Freq: 5.47292e-10
23d   	MOV    ECX,#-10
242   	NOP    # Pad for loops and calls
243   	CALL,static  wrapper for: uncommon_trap
        # zero::main @ bci:31  L0=_ L1=_ L2=_ STK0=EBP STK1=#0
        # EBP=Oop [8]=Callers_EBP [12]=Callers_EDI [16]=Callers_ESI 
248   	INT3   ; ShouldNotReachHere
248

</opto_assembly>
<writer thread='1'/>
--- Compiler Statistics ---
Methods seen: 3  Methods parsed: 3  Nodes created: 3832
32 original NULL checks - 29 elided (90%); optimizer leaves 27,
1 made implicit ( 3%)
CCP: 3  constants found: 0
Total frameslots = 234, Max frameslots = 12
Inserted 0 spill loads, 0 spill stores, 0 mem-mem moves and 0 copies.
Total load cost=      0, store cost =      0, mem-mem cost =  0.00, copy cost =     0.
Adjusted spill cost =       0.
Conservatively coalesced 21 copies, 0 pairs, 197 post alloc.
Average allocation trips 0.103448
High Pressure Blocks = 0, Low Pressure Blocks = 0
Nops added 0 bytes to total of 5781 bytes, for 0.00%
Deoptimization traps recorded:
     1 (100.0%) total
            unloaded/reinterpret/getstatic: 1 (100.0%)
    0 inline cache miss in compiled
    0 wrong method
    0 unresolved static call site
    0 unresolved virtual call site
    3 unresolved opt virtual call site
    1 removed paired I2C/C2I adapters during deopt
Peephole: peephole rules applied: 0
PhaseIdealLoop=4, sum _unique=732
<tty_done stamp='7.355'/>
</tty>
<hotspot_log_done stamp='7.356'/>
</hotspot_log>


Blocks 16, 17, 18 and 8 all show the problem I'm talking about.  I've seen the same problem on sparc, in particular when using tiered compilation which produces a lot of references to constants representing methodDataOops.  I can show you how to reproduce those as well if you are interested.

Part of the problem with longs appears to be the misnamed value short_spill_offset_limit which from it's name you'd assume has something to do with spilling but as far as I can tell it's only purpose it to determine which long constants get forced into registers.  On intel this can greatly increase register pressure.

###@###.### 2004-04-15
while looking at code generated for other things I noticed some bad handling of constant values.  It seems that when constants are commoned up in a compilation if the register pressure is high enough you end up with multiple copies of the same constant being produced repeatedly within a block.  You also seem to end up with instruction variants which expect the values to be in registers instead of versions which could handle it as a constant.  The problem is particularly bad on intel where there are few registers and in general when long constants are used.  Here's a program which shows the problem.  It's not interesting by itself, just for showing the problem.

public class zero {
  int a;
  int b;
  long c;
  long d;
  public static void main(String[] args) {
    zero zero = new zero();
    System.out.println(zero);
    for (int i = 0; i < 1000000; i++) {
    }
    zero.a = 0;
    zero.b = 1;
    zero.c = 0xffffffffffL;
    zero.d = 0xfffffffff0L;
    System.out.println(zero);
    if (args.length == 0) {
      zero.a = 0;
      zero.b = 0;
      zero.c = 0xffffffffffL;
      zero.d = 0xfffffffff0L;
    } else if (args.length == 2) {
      zero.a = 1;
      zero.b = 1;
      zero.c = 0xffffffffffL;
      zero.d = 0xffffffffffL;
    } else if (args.length == 3) {
      zero.a = 0;
      zero.b = 0;
      zero.c = 0xfffffffff0L;
      zero.d = 0xfffffffff0L;
    } else if (args.length == 4) {
      zero.a = 1;
      zero.b = 1;
      zero.c = 0xffffffffffL;
      zero.d = 0xfffffffff0L;
    } else if (args.length == 5) {
      zero.a = 0;
      zero.b = 1;
      zero.c = 0xffffffffffL;
      zero.d = 0xfffffffff0L;
    }
  }
}

running it using java_g -Xbatch -Xcomp -XX:CompileOnly=zero -XX:+PrintOptoAssembly zero you get the following output.

<?xml version='1.0' encoding='UTF-8'?>
<hotspot_log version='142 1' process='22460' time_ms='1082065721374'>
<vm_version>
<name>
Java HotSpot(TM) Server VM
</name>
<release>
1.5.0-never-intrinsic-jvmg2-debug
</release>
<info>
Java HotSpot(TM) Server VM (1.5.0-never-intrinsic-jvmg2) for solaris-x86, built on Apr 14 2004 10:15:40 by unknown with unknown Workshop:0x550
</info>
</vm_version>
<tty>
Java HotSpot(TM) Server VM warning: Attempt to use MPSS failed.
<writer thread='12'/>
<opto_assembly compile_id='1'>
{method} 
 - klass: {other class}
 - method holder:     &apos;zero&apos;
 - constants:         0xdd3eb024{constant pool}
 - access:            0xc1000009  public static 
 - name:              &apos;main&apos;
 - signature:         &apos;([Ljava/lang/String;)V&apos;
 - max stack:         3
 - max locals:        3
 - size of params:    1
 - method size:       18
 - vtable index:      -1
 - code size:         222
 - code start:        0x92d68ae8
 - code end (excl):   0x92d68bc6
 - method data:       0x92d68e70
 - checked ex length: 0
 - linenumber start:  0x92d68bc6
 - localvar length:   0
#
#  void ( java/lang/String:exact *[int+]:exact* )
#
#r000 ECX   : parm 0: java/lang/String:exact *[int+]:exact*
# -- Old ESP -- Framesize: 16 --
#r059 ESP+12: return address
#r058 ESP+ 8: pad2, in_preserve
#r061 ESP+ 4: spill
#r060 ESP+ 0: spill
#
abababab   N1: #	B1 &lt;- B7 B10  Freq: 0.000666667
abababab
000   B1: #	B5 B2 &lt;- BLOCK HEAD IS JUNK   Freq: 0.000666667
000   	# stack bang
	SUB    ESP,12	# Create frame
00d   	MOV    [ESP + #0],ECX
010   	MOV    EAX, Thread::current()
02c   	MOV    ECX,EAX
02e   	MOV    EAX,[EAX + #76]
031   	LEA    EBX,[EAX + #32]
034   	CMPu   EBX,[ECX + #80]
037   	Jge,us B5  P=0.000100 C=-1.000000
037
039   B2: #	B3 &lt;- B1  Freq: 0.0006666
039   	MOV    [ECX + #76],EBX
03c   	MOV    [EAX],0x00000001
042   	MOV    [EAX + #4],precise klass zero: 0x08176aa8:Constant:exact *
049   	MOV    EBX,#0
04e   	MOV    [EAX + #8],EBX
051   	MOV    [EAX + #12],EBX
054   	MOV    [EAX + #16],EBX
057   	MOV    [EAX + #20],EBX
05a   	MOV    [EAX + #24],EBX
05d   	MOV    [EAX + #28],EBX
05d
060   B3: #	B8 B4 &lt;- B2 B6  Freq: 0.000666667
060   	MOV    EBX,EAX
062   	#checkcastPP of EBX
062   	MOV    [ESP + #4],EBX
066   	MOV    ECX,EBX
068   	NOP    # Pad for loops and calls
069   	NOP    # Pad for loops and calls
06a   	NOP    # Pad for loops and calls
06b   	CALL,static  zero::&lt;init&gt;
        # zero::main @ bci:4  L0=ESP + #0 L1=_ L2=_ STK0=ESP + #4
        # EBP=Callers_EBP EDI=Callers_EDI ESI=Callers_ESI [0]=Oop [4]=Oop 
070
070   B4: #	B7 &lt;- B3  Freq: 0
070   	JMP,s  B7
070
072   B5: #	B9 B6 &lt;- B1  Freq: 6.66777e-08
072   	MOV    ECX,precise klass zero: 0x08176aa8:Constant:exact *
077   	CALL,static  wrapper for: _new_Java
        # zero::main @ bci:0  L0=ESP + #0 L1=_ L2=_
        # EBP=Callers_EBP EDI=Callers_EDI ESI=Callers_ESI [0]=Oop 
07c
07c   B6: #	B3 &lt;- B5  Freq: 0
07c   	JMP,s  B3
07c
07e   B7: #	N1 &lt;- B4  Freq: 1e-06
07e   	MOV    ECX,#32
083   	CALL,static  wrapper for: uncommon_trap
        # zero::main @ bci:8  L0=ESP + #0 L1=ESP + #4 L2=_
        # EBP=Callers_EBP EDI=Callers_EDI ESI=Callers_ESI [0]=Oop [4]=Oop 
088   	INT3   ; ShouldNotReachHere
088
08d   B8: #	B10 &lt;- B3  Freq: 6.66667e-09
08d   	# exception oop is in EAX; no code emitted
08d   	MOV    ECX,EAX
08f   	JMP,s  B10
08f
091   B9: #	B10 &lt;- B5  Freq: 6.66777e-13
091   	# exception oop is in EAX; no code emitted
091   	MOV    ECX,EAX
091
093   B10: #	N1 &lt;- B8 B9  Freq: 6.66733e-09
093   	ADD    ESP,12	# Destroy frame
	
096   	JMP    rethrow_stub
096

</opto_assembly>
<opto_assembly compile_id='2'>
{method} 
 - klass: {other class}
 - method holder:     &apos;zero&apos;
 - constants:         0xdd3eb024{constant pool}
 - access:            0x81000001  public 
 - name:              &apos;&lt;init&gt;&apos;
 - signature:         &apos;()V&apos;
 - max stack:         1
 - max locals:        1
 - size of params:    1
 - method size:       18
 - vtable index:      -1
 - code size:         5
 - code start:        0x92d68a68
 - code end (excl):   0x92d68a6d
 - method data:       0x92d68f88
 - checked ex length: 0
 - linenumber start:  0x92d68a6d
 - localvar length:   0
#
#  void ( zero:NotNull * )
#
#r000 ECX   : parm 0: zero:NotNull *
# -- Old ESP -- Framesize: 8 --
#r059 ESP+ 4: return address
#r058 ESP+ 0: pad2, in_preserve
#
000   N30: #	B1 &lt;- BLOCK HEAD IS JUNK   Freq: 0.000666667
000   	CMP    EAX,[ECX+4]	# Inline cache check
	JNE    OptoRuntime::handle_ic_miss_stub
	NOP
	NOP
	NOP

000
00c   B1: #	B3 B2 &lt;- BLOCK HEAD IS JUNK   Freq: 0.000666667
00c   	# stack bang
	SUB    ESP,4	# Create frame
019   	NOP    # Pad for loops and calls
01a   	NOP    # Pad for loops and calls
01b   	CALL,static  java.lang.Object::&lt;init&gt;
        # zero::&lt;init&gt; @ bci:1  L0=_
        # EBP=Callers_EBP EDI=Callers_EDI ESI=Callers_ESI 
020
020   B2: #	N30 &lt;- B1  Freq: 0.000666653
020   	ADD    ESP,4	# Destroy frame
	TEST  PollPage,EAX	! Poll Safepoint
	
029   	RET
029
02a   B3: #	N30 &lt;- B1  Freq: 6.66667e-09
02a   	# exception oop is in EAX; no code emitted
02a   	MOV    ECX,EAX
02c   	ADD    ESP,4	# Destroy frame
	
02f   	JMP    rethrow_stub
02f

</opto_assembly>
<opto_assembly compile_id='1' compile_kind='osr'>
{method} 
 - klass: {other class}
 - method holder:     &apos;zero&apos;
 - constants:         0xdd3eb024{constant pool}
 - access:            0xc1000009  public static 
 - name:              &apos;main&apos;
 - signature:         &apos;([Ljava/lang/String;)V&apos;
 - max stack:         3
 - max locals:        3
 - size of params:    1
 - method size:       18
 - vtable index:      -1
 - code size:         222
 - code start:        0x92d68ae8
 - code end (excl):   0x92d68bc6
 - method data:       0x92d68e70
 - checked ex length: 0
 - linenumber start:  0x92d68bc6
 - localvar length:   0
#
#  void ( rawptr:BotPTR, rawptr:BotPTR )
#
#r004 EAX   : parm 0: rawptr:BotPTR
#r001 EBX   : parm 1: rawptr:BotPTR
# -- Old ESP -- Framesize: 32 --
#r059 ESP+28: return address
#r058 ESP+24: pad2, in_preserve
#r065 ESP+20: spill
#r064 ESP+16: spill
#r063 ESP+12: spill
#r062 ESP+ 8: spill
#r061 ESP+ 4: spill
#r060 ESP+ 0: spill
#
000   N253: #	B1 &lt;- BLOCK HEAD IS JUNK   Freq: 0.000666667
000   	INT3
      	NOP    # Pad for loops and calls
      	NOP    # Pad for loops and calls
      	NOP    # Pad for loops and calls

008   B1: #	B22 B2 &lt;- BLOCK HEAD IS JUNK   Freq: 0.000666667
008   	# stack bang
	MOV    EBX,EDI		# Move locals ptr to interpreter_arg_ptr_reg
	SUB    ESP,28	# Create frame
017   	MOV    [ESP + #8],EBP
	MOV    [ESP + #12],EDI
01f   	MOV    EBP,[EBX + #-4]
022   	MOV    EDI,[EBX]
024   	TEST   EDI,EDI
026   	Jeq    B22  P=0.001000 C=-1.000000
026
02c   B2: #	B23 B3 &lt;- B1  Freq: 0.000666
02c   	MOV    [ESP + #16],ESI
030   	MOV    EAX,[EDI + #4]
033   	CMPu   EAX,precise klass [Ljava/lang/String;: 0x08166b10:Constant:exact *
039   	Jne,u  B23  P=0.100000 C=-1.000000
039
03f   B3: #	B4 &lt;- B2  Freq: 0.0005994
03f   	MOV    ESI,EDI
041   	#checkcastPP of ESI
041
041   B4: #	B24 B5 &lt;- B22 B3  Freq: 0.000600067
041   	TEST   EBP,EBP
043   	Jeq    B24  P=0.001000 C=-1.000000
043
049   B5: #	B23 B6 &lt;- B4  Freq: 0.000599467
049   	MOV    EAX,[EBP + #4]
04c   	CMPu   EAX,precise klass zero: 0x08166a48:Constant:exact *
052   	Jne,u  B23  P=0.100000 C=-1.000000
052
058   B6: #	B7 &lt;- B5  Freq: 0.00053952
058   	#checkcastPP of EBP
058
058   B7: #	B28 B8 &lt;- B24 B6  Freq: 0.00054012
058   	TEST   EBP,EBP
05a   	Jeq    B28  P=0.000001 C=-1.000000
05a
060   B8: #	B27 B9 &lt;- B7  Freq: 0.000540119
060   	MOV    [EBP + #28],#1
067   	MOV    EBX,#300
06c   	MOV    EBX,[EBX + precise klass java/lang/System: 0x08166ba0:Constant:exact *]
072   	MOV    [ESP + #4],EBX
076   	MOV    ECX.lo,#1099511627760.lo
	MOV    ECX.hi,#1099511627760.hi
080   	MOV    [EBP + #16],ECX.lo
	MOV    [EBP + #16]+4,ECX.hi
086   	MOV    [ESP + #0],EBP
089   	MOV    EBX,#0
08e   	MOV    [EBP + #24],EBX
091   	MOV    EBP.lo,#1099511627775.lo
	MOV    EBP.hi,#1099511627775.hi
09b   	MOV    ECX,[ESP + #0]
09e   	MOV    [ECX + #8],EBP.lo
	MOV    [ECX + #8]+4,EBP.hi
0a4   	MOV    EBX,[ESP + #4]
0a8   	TEST   EBX,EBX
0aa   	Jeq    B27  P=0.000001 C=-1.000000
0aa
0b0   B9: #	B26 B10 &lt;- B8  Freq: 0.000540119
0b0   	MOV    ECX,EBX
0b2   	MOV    EDX,[ESP + #0]
0b5   	NOP    # Pad for loops and calls
0b6   	NOP    # Pad for loops and calls
0b7   	CALL,static  java.io.PrintStream::println
        # zero::main @ bci:57  L0=ESI L1=ESP + #0 L2=_
        # ESI=Oop [0]=Oop [8]=Callers_EBP [12]=Callers_EDI [16]=Callers_ESI 
0bc
0bc   B10: #	B25 B11 &lt;- B9  Freq: 0.000540108
0bc   	MOV    ECX,[ESI + #8]
0bf   	NullCheck ESI
0bf
0bf   B11: #	B20 B12 &lt;- B10  Freq: 0.000540108
0bf   	TEST   ECX,ECX
0c1   	Jeq    B20  P=0.100000 C=-1.000000
0c1
0c7   B12: #	B19 B13 &lt;- B11  Freq: 0.000486097
0c7   	CMP    ECX,#2
0ca   	Jeq    B19  P=0.100000 C=-1.000000
0ca
0d0   B13: #	B18 B14 &lt;- B12  Freq: 0.000437487
0d0   	CMP    ECX,#3
0d3   	Jeq,s  B18  P=0.100000 C=-1.000000
0d3
0d5   B14: #	B17 B15 &lt;- B13  Freq: 0.000393738
0d5   	CMP    ECX,#4
0d8   	Jeq,s  B17  P=0.100000 C=-1.000000
0d8
0da   B15: #	B21 B16 &lt;- B14  Freq: 0.000354365
0da   	CMP    ECX,#5
0dd   	Jne    B21  P=0.900000 C=-1.000000
0dd
0e3   B16: #	B21 &lt;- B15  Freq: 3.54365e-05
0e3   	MOV    EAX,[ESP + #0]
0e6   	MOV    ECX.lo,#1099511627760.lo
	MOV    ECX.hi,#1099511627760.hi
0f0   	MOV    [EAX + #16],ECX.lo
	MOV    [EAX + #16]+4,ECX.hi
0f6   	MOV    ECX,EAX
0f8   	MOV    EBX,#0
0fd   	MOV    [ECX + #24],EBX
100   	MOV    [ECX + #8],EBP.lo
	MOV    [ECX + #8]+4,EBP.hi
106   	MOV    [ECX + #28],#1
10d   	JMP    B21
10d
112   B17: #	B21 &lt;- B14  Freq: 3.93738e-05
112   	MOV    EDX,[ESP + #0]
115   	MOV    ECX.lo,#1099511627760.lo
	MOV    ECX.hi,#1099511627760.hi
11f   	MOV    [EDX + #16],ECX.lo
	MOV    [EDX + #16]+4,ECX.hi
125   	MOV    EBX,EDX
127   	MOV    [EBX + #8],EBP.lo
	MOV    [EBX + #8]+4,EBP.hi
12d   	MOV    ECX,EBX
12f   	MOV    [ECX + #24],#1
136   	MOV    [ECX + #28],#1
13d   	JMP    B21
13d
142   B18: #	B21 &lt;- B13  Freq: 4.37487e-05
142   	MOV    ECX,[ESP + #0]
145   	MOV    EBP.lo,#1099511627760.lo
	MOV    EBP.hi,#1099511627760.hi
14f   	MOV    [ECX + #16],EBP.lo
	MOV    [ECX + #16]+4,EBP.hi
155   	MOV    EBP,#0
15a   	MOV    [ECX + #24],EBP
15d   	MOV    EBX,#0
162   	MOV    [ECX + #28],EBX
165   	MOV    EBP.lo,#1099511627760.lo
	MOV    EBP.hi,#1099511627760.hi
16f   	MOV    [ECX + #8],EBP.lo
	MOV    [ECX + #8]+4,EBP.hi
175   	JMP,s  B21
175
177   B19: #	B21 &lt;- B12  Freq: 4.86097e-05
177   	MOV    ECX,[ESP + #0]
17a   	MOV    [ECX + #16],EBP.lo
	MOV    [ECX + #16]+4,EBP.hi
180   	MOV    [ECX + #8],EBP.lo
	MOV    [ECX + #8]+4,EBP.hi
186   	MOV    [ECX + #24],#1
18d   	MOV    [ECX + #28],#1
194   	JMP,s  B21
194
196   B20: #	B21 &lt;- B11  Freq: 5.40108e-05
196   	MOV    EDX,[ESP + #0]
199   	MOV    ECX.lo,#1099511627760.lo
	MOV    ECX.hi,#1099511627760.hi
1a3   	MOV    [EDX + #16],ECX.lo
	MOV    [EDX + #16]+4,ECX.hi
1a9   	MOV    ECX,EDX
1ab   	MOV    EBX,#0
1b0   	MOV    [ECX + #24],EBX
1b3   	MOV    EBX,#0
1b8   	MOV    [ECX + #28],EBX
1bb   	MOV    [ECX + #8],EBP.lo
	MOV    [ECX + #8]+4,EBP.hi
1bb
1c1   B21: #	N253 &lt;- B16 B20 B17 B18 B19 B15  Freq: 0.000540108
1c1   	MOV    EBP,[ESP + #8]
	MOV    EDI,[ESP + #12]
1c9   	MOV    ESI,[ESP + #16]
1cd   	ADD    ESP,28	# Destroy frame
	TEST  PollPage,EAX	! Poll Safepoint
	
1d6   	RET
1d6
1d7   B22: #	B4 &lt;- B1  Freq: 6.66658e-07
1d7   	MOV    [ESP + #16],ESI
1db   	MOV    ESI,NULL
1e0   	JMP    B4
1e0
1e5   B23: #	N253 &lt;- B2 B5  Freq: 1e-06
1e5   	MOV    ESI,[EBX + #-8]
1e8   	MOV    ECX,#-75
1ed   	NOP    # Pad for loops and calls
1ee   	NOP    # Pad for loops and calls
1ef   	CALL,static  wrapper for: uncommon_trap
        # zero::main @ bci:17  L0=EDI L1=EBP L2=ESI
        # EBP=Oop EDI=Oop [8]=Callers_EBP [12]=Callers_EDI [16]=Callers_ESI 
1f4   	INT3   ; ShouldNotReachHere
1f4
1f9   B24: #	B7 &lt;- B4  Freq: 6.00059e-07
1f9   	MOV    EBP,NULL
1fe   	JMP    B7
1fe
203   B25: #	N253 &lt;- B10  Freq: 5.4728e-10
203   	MOV    ECX,#-10
208   	NOP    # Pad for loops and calls
209   	NOP    # Pad for loops and calls
20a   	NOP    # Pad for loops and calls
20b   	CALL,static  wrapper for: uncommon_trap
        # zero::main @ bci:61  L0=_ L1=_ L2=_ STK0=_
        # [8]=Callers_EBP [12]=Callers_EDI [16]=Callers_ESI 
210   	INT3   ; ShouldNotReachHere
210
215   B26: #	N253 &lt;- B9  Freq: 5.40119e-09
215   	# exception oop is in EAX; no code emitted
215   	MOV    ECX,EAX
217   	MOV    EBP,[ESP + #8]
	MOV    EDI,[ESP + #12]
21f   	MOV    ESI,[ESP + #16]
223   	ADD    ESP,28	# Destroy frame
	
226   	JMP    rethrow_stub
226
22b   B27: #	N253 &lt;- B8  Freq: 5.47292e-10
22b   	MOV    ECX,#-10
230   	NOP    # Pad for loops and calls
231   	NOP    # Pad for loops and calls
232   	NOP    # Pad for loops and calls
233   	CALL,static  wrapper for: uncommon_trap
        # zero::main @ bci:57  L0=_ L1=_ L2=_ STK0=ESP + #4 STK1=ESP + #0
        # [0]=Oop [4]=Oop [8]=Callers_EBP [12]=Callers_EDI [16]=Callers_ESI 
238   	INT3   ; ShouldNotReachHere
238
23d   B28: #	N253 &lt;- B7

                                    

Comments
EVALUATION

Will evaluate for 1.5.1.
###@###.### 2004-04-20
                                     
2004-04-20
CONVERTED DATA

BugTraq+ Release Management Values

COMMIT TO FIX:
dragon


                                     
2004-06-14
SUGGESTED FIX

http://analemma.sfbay.sun.com/net/prt-archiver.sfbay/data/archived_workspaces/main/c2_baseline/2006/20061113125024.never.5032515/workspace/webrevs/webrev-2006.11.13/index.html
                                     
2006-11-14



Hardware and Software, Engineered to Work Together