diff --git a/src/main/java/com/yasuenag/ffmasm/amd64/SSEAsmBuilder.java b/src/main/java/com/yasuenag/ffmasm/amd64/SSEAsmBuilder.java index e276e40..f963939 100644 --- a/src/main/java/com/yasuenag/ffmasm/amd64/SSEAsmBuilder.java +++ b/src/main/java/com/yasuenag/ffmasm/amd64/SSEAsmBuilder.java @@ -121,4 +121,45 @@ public SSEAsmBuilder movdquMR(Register r, Register m, OptionalInt disp){ return movdq(r, m, disp, (byte)0xf3, (byte)0x7f); } + private SSEAsmBuilder movDorQ(Register r, Register m, OptionalInt disp, byte secondOpcode){ + byteBuf.put((byte)0x66); // prefix + emitREXOp(r, m); + byteBuf.put((byte)0x0f); // escape opcode + byteBuf.put(secondOpcode); + var mode = emitModRM(r, m, disp); + emitDisp(mode, disp, m); + + return this; + } + + /** + * Move doubleword from r/m32 to xmm. + * Opcode: 66 0F 6E /r + * Instruction: MOVD xmm, r/m32 + * Op/En: A + * + * @param r "r" register + * @param m "r/m" register + * @param disp Displacement. Set "empty" if this operation is reg-reg. + * @return This instance + */ + public SSEAsmBuilder movdRM(Register r, Register m, OptionalInt disp){ + return movDorQ(r, m, disp, (byte)0x6e); + } + + /** + * Move doubleword from xmm register to r/m32. + * Opcode: 66 0F 7E /r + * Instruction: MOVD r/m32, xmm + * Op/En: B + * + * @param r "r" register + * @param m "r/m" register + * @param disp Displacement. Set "empty" if this operation is reg-reg. + * @return This instance + */ + public SSEAsmBuilder movdMR(Register r, Register m, OptionalInt disp){ + return movDorQ(r, m, disp, (byte)0x7e); + } + } diff --git a/src/test/java/com/yasuenag/ffmasm/test/amd64/SSEAsmTest.java b/src/test/java/com/yasuenag/ffmasm/test/amd64/SSEAsmTest.java index 7e1a72b..82740c6 100644 --- a/src/test/java/com/yasuenag/ffmasm/test/amd64/SSEAsmTest.java +++ b/src/test/java/com/yasuenag/ffmasm/test/amd64/SSEAsmTest.java @@ -111,4 +111,75 @@ public void testMOVDQU(){ } } + /** + * Tests MOVD A + */ + @Test + @EnabledOnOs({OS.LINUX, OS.WINDOWS}) + public void testMOVD_A(){ + try(var seg = new CodeSegment()){ + var desc = FunctionDescriptor.of( + ValueLayout.JAVA_FLOAT, // return value + ValueLayout.ADDRESS // 1st argument + ); + var method = AMD64AsmBuilder.create(SSEAsmBuilder.class, seg, desc) + /* push %rbp */ .push(Register.RBP) + /* mov %rsp, %rbp */ .movMR(Register.RSP, Register.RBP, OptionalInt.empty()) + .cast(SSEAsmBuilder.class) + /* movd (arg1), %xmm0 */ .movdRM(Register.XMM0, argReg.arg1(), OptionalInt.of(0)) + /* leave */ .leave() + /* ret */ .ret() + .build(); + + float expected = 1.1f; + var arena = Arena.ofAuto(); + MemorySegment src = arena.allocate(ValueLayout.JAVA_FLOAT); + src.set(ValueLayout.JAVA_FLOAT, 0, expected); + + float actual = (float)method.invoke(src); + + Assertions.assertEquals(expected, actual); + } + catch(Throwable t){ + Assertions.fail(t); + } + } + + /** + * Tests MOVD B + */ + @Test + @EnabledOnOs({OS.LINUX, OS.WINDOWS}) + public void testMOVD_B(){ + try(var seg = new CodeSegment()){ + var arena = Arena.ofAuto(); + MemorySegment dest = arena.allocate(ValueLayout.JAVA_FLOAT); + + var desc = FunctionDescriptor.ofVoid( + ValueLayout.JAVA_FLOAT // 1st argument + ); + var method = AMD64AsmBuilder.create(SSEAsmBuilder.class, seg, desc) + /* push %rbp */ .push(Register.RBP) + /* mov %rsp, %rbp */ .movMR(Register.RSP, Register.RBP, OptionalInt.empty()) + // Mixed argument order (int, fp) is different between Windows and Linux. + // Thus address is loaded from immediate value. + /* mov addr, %rax */ .movImm(Register.RAX, dest.address()) + .cast(SSEAsmBuilder.class) + /* movd %xmm0, (%rax) */ .movdMR(Register.XMM0, Register.RAX, OptionalInt.of(0)) + /* leave */ .leave() + /* ret */ .ret() + .build(); + + float expected = 1.1f; + + method.invoke(expected); + float actual = dest.get(ValueLayout.JAVA_FLOAT, 0); + + Assertions.assertEquals(expected, actual); + } + catch(Throwable t){ + Assertions.fail(t); + } + } + }