Author: Jon Roelofs
Date: 2026-06-18T12:57:21-07:00
New Revision: df9b1f89a4f4f0c7b5afc03d273a825caef268fb

URL: 
https://github.com/llvm/llvm-project/commit/df9b1f89a4f4f0c7b5afc03d273a825caef268fb
DIFF: 
https://github.com/llvm/llvm-project/commit/df9b1f89a4f4f0c7b5afc03d273a825caef268fb.diff

LOG: [clang][Mach-O] Add an option to force UNWIND_*_MODE_DWARF compact unwind 
info (#204005)

The new option value extends: `-femit-dwarf-unwind=dwarf-only`. This is
primarily intended as a testing mechanism to ensure coverage on the
DWARF-only parts of the unwinder, where previously the compact unwinder
would have taken care of most functions.

Added: 
    llvm/test/MC/AArch64/compact-unwind-force-dwarf.s
    llvm/test/MC/X86/compact-unwind-force-dwarf.s

Modified: 
    clang/docs/UsersManual.rst
    clang/include/clang/Options/Options.td
    clang/test/Driver/femit-dwarf-unwind.c
    clang/test/Driver/femit-dwarf-unwind.s
    clang/tools/driver/cc1as_main.cpp
    llvm/include/llvm/MC/MCTargetOptions.h
    llvm/lib/MC/MCObjectFileInfo.cpp
    llvm/lib/MC/MCTargetOptionsCommandFlags.cpp
    llvm/lib/Target/AArch64/MCTargetDesc/AArch64AsmBackend.cpp
    llvm/lib/Target/ARM/MCTargetDesc/ARMAsmBackend.cpp
    llvm/lib/Target/X86/MCTargetDesc/X86AsmBackend.cpp
    llvm/test/MC/MachO/ARM/compact-unwind-armv7k.s

Removed: 
    


################################################################################
diff  --git a/clang/docs/UsersManual.rst b/clang/docs/UsersManual.rst
index 3f4b1585e5935..77f644f221a17 100644
--- a/clang/docs/UsersManual.rst
+++ b/clang/docs/UsersManual.rst
@@ -940,6 +940,8 @@ Clang options that don't fit neatly into other categories.
   * ``no-compact-unwind`` - Only emit DWARF unwind when compact unwind 
encodings
     aren't available. This is the default for arm64.
   * ``always`` - Always emit DWARF unwind regardless.
+  * ``dwarf-only`` - Always emit DWARF unwind, and force compact unwind to 
defer
+    to DWARF.
   * ``default`` - Use the platform-specific default (``always`` for all
     non-arm64-platforms).
 

diff  --git a/clang/include/clang/Options/Options.td 
b/clang/include/clang/Options/Options.td
index 22e730ac58fb1..5028684731b2d 100644
--- a/clang/include/clang/Options/Options.td
+++ b/clang/include/clang/Options/Options.td
@@ -5045,8 +5045,8 @@ defm force_dwarf_frame : BoolFOption<"force-dwarf-frame",
 def femit_dwarf_unwind_EQ : Joined<["-"], "femit-dwarf-unwind=">,
   Group<f_Group>, Visibility<[ClangOption, CC1Option, CC1AsOption]>,
   HelpText<"When to emit DWARF unwind (EH frame) info">,
-  Values<"always,no-compact-unwind,default">,
-  NormalizedValues<["Always", "NoCompactUnwind", "Default"]>,
+  Values<"always,no-compact-unwind,dwarf-only,default">,
+  NormalizedValues<["Always", "NoCompactUnwind", "DwarfOnly", "Default"]>,
   NormalizedValuesScope<"llvm::EmitDwarfUnwindType">,
   MarshallingInfoEnum<CodeGenOpts<"EmitDwarfUnwind">, "Default">;
 defm emit_compact_unwind_non_canonical : 
BoolFOption<"emit-compact-unwind-non-canonical",

diff  --git a/clang/test/Driver/femit-dwarf-unwind.c 
b/clang/test/Driver/femit-dwarf-unwind.c
index 89e733462c2c9..ee8e3964fef4e 100644
--- a/clang/test/Driver/femit-dwarf-unwind.c
+++ b/clang/test/Driver/femit-dwarf-unwind.c
@@ -1,11 +1,22 @@
 // REQUIRES: x86-registered-target
 
 // RUN: rm -rf %t; mkdir %t
-// RUN: %clang -target x86_64-apple-macos11.0 -c %s -o %t/x86_64.o 
-femit-compact-unwind-non-canonical
-// RUN: %clang -target x86_64-apple-macos11.0 
-femit-dwarf-unwind=no-compact-unwind -femit-compact-unwind-non-canonical -c %s 
-o %t/x86_64-no-dwarf.o
+
+// RUN: %clang --target=x86_64-apple-macos11.0 -c %s -o %t/x86_64.o 
-femit-compact-unwind-non-canonical
 // RUN: llvm-objdump --macho --dwarf=frames %t/x86_64.o | FileCheck %s 
--check-prefix=WITH-FDE
+
+// RUN: %clang --target=x86_64-apple-macos11.0 
-femit-dwarf-unwind=no-compact-unwind -femit-compact-unwind-non-canonical -c %s 
-o %t/x86_64-no-dwarf.o
 // RUN: llvm-objdump --macho --dwarf=frames %t/x86_64-no-dwarf.o | FileCheck 
%s --check-prefix=NO-FDE
 
+// RUN: %clang --target=x86_64-apple-macos11.0 -femit-dwarf-unwind=dwarf-only 
-c %s -o %t/x86_64-dwarf-only.o
+// RUN: llvm-objdump --macho --dwarf=frames --unwind-info 
%t/x86_64-dwarf-only.o | FileCheck %s --check-prefix=CU-DWARF
+
+// CU-DWARF:      Contents of __compact_unwind section:
+// CU-DWARF-NEXT:   Entry at offset 0x0:
+// CU-DWARF-NEXT:     start:                0x0 _foo
+// CU-DWARF-NEXT:     length:               0xb
+// CU-DWARF-NEXT:     compact encoding:     0x04000000
+
 // WITH-FDE: FDE
 // NO-FDE-NOT: FDE
 

diff  --git a/clang/test/Driver/femit-dwarf-unwind.s 
b/clang/test/Driver/femit-dwarf-unwind.s
index 41951352eeafd..32d972abf0a21 100644
--- a/clang/test/Driver/femit-dwarf-unwind.s
+++ b/clang/test/Driver/femit-dwarf-unwind.s
@@ -1,11 +1,22 @@
 // REQUIRES: x86-registered-target
 
 // RUN: rm -rf %t; mkdir %t
+
 // RUN: %clang -target x86_64-apple-macos11.0 -c %s -o %t/x86_64.o 
-femit-compact-unwind-non-canonical
-// RUN: %clang -target x86_64-apple-macos11.0 
-femit-dwarf-unwind=no-compact-unwind -c %s -o %t/x86_64-no-dwarf.o 
-femit-compact-unwind-non-canonical
 // RUN: llvm-objdump --macho --dwarf=frames %t/x86_64.o | FileCheck %s 
--check-prefix=WITH-FDE
+
+// RUN: %clang -target x86_64-apple-macos11.0 
-femit-dwarf-unwind=no-compact-unwind -c %s -o %t/x86_64-no-dwarf.o 
-femit-compact-unwind-non-canonical
 // RUN: llvm-objdump --macho --dwarf=frames %t/x86_64-no-dwarf.o | FileCheck 
%s --check-prefix=NO-FDE
 
+// RUN: %clang -target x86_64-apple-macos11.0 -femit-dwarf-unwind=dwarf-only 
-c %s -o %t/x86_64-dwarf-only.o
+// RUN: llvm-objdump --macho --dwarf=frames --unwind-info 
%t/x86_64-dwarf-only.o | FileCheck %s --check-prefixes=WITH-FDE,CU-DWARF
+
+// CU-DWARF:      Contents of __compact_unwind section:
+// CU-DWARF-NEXT:   Entry at offset 0x0:
+// CU-DWARF-NEXT:     start:                0x0 _foo
+// CU-DWARF-NEXT:     length:               0x1
+// CU-DWARF-NEXT:     compact encoding:     0x04000000
+
 // WITH-FDE: FDE
 // NO-FDE-NOT: FDE
 

diff  --git a/clang/tools/driver/cc1as_main.cpp 
b/clang/tools/driver/cc1as_main.cpp
index 33c34dd04d0e6..077cd69ce4e2c 100644
--- a/clang/tools/driver/cc1as_main.cpp
+++ b/clang/tools/driver/cc1as_main.cpp
@@ -389,6 +389,7 @@ bool 
AssemblerInvocation::CreateFromArgs(AssemblerInvocation &Opts,
         llvm::StringSwitch<EmitDwarfUnwindType>(A->getValue())
             .Case("always", EmitDwarfUnwindType::Always)
             .Case("no-compact-unwind", EmitDwarfUnwindType::NoCompactUnwind)
+            .Case("dwarf-only", EmitDwarfUnwindType::DwarfOnly)
             .Case("default", EmitDwarfUnwindType::Default);
   }
 

diff  --git a/llvm/include/llvm/MC/MCTargetOptions.h 
b/llvm/include/llvm/MC/MCTargetOptions.h
index 2f24608b5f6ba..e3d1a8cc763b7 100644
--- a/llvm/include/llvm/MC/MCTargetOptions.h
+++ b/llvm/include/llvm/MC/MCTargetOptions.h
@@ -21,6 +21,7 @@ namespace llvm {
 enum class EmitDwarfUnwindType {
   Always,          // Always emit dwarf unwind
   NoCompactUnwind, // Only emit if compact unwind isn't available
+  DwarfOnly,       // Force compact unwind to reference DWARF
   Default,         // Default behavior is based on the target
 };
 

diff  --git a/llvm/lib/MC/MCObjectFileInfo.cpp 
b/llvm/lib/MC/MCObjectFileInfo.cpp
index ab8ae43d22940..cc95fd8bf8ac9 100644
--- a/llvm/lib/MC/MCObjectFileInfo.cpp
+++ b/llvm/lib/MC/MCObjectFileInfo.cpp
@@ -77,6 +77,7 @@ void MCObjectFileInfo::initMachOMCObjectFileInfo(const Triple 
&T) {
 
   switch (Ctx->emitDwarfUnwindInfo()) {
   case EmitDwarfUnwindType::Always:
+  case EmitDwarfUnwindType::DwarfOnly:
     OmitDwarfIfHaveCompactUnwind = false;
     break;
   case EmitDwarfUnwindType::NoCompactUnwind:

diff  --git a/llvm/lib/MC/MCTargetOptionsCommandFlags.cpp 
b/llvm/lib/MC/MCTargetOptionsCommandFlags.cpp
index 359c1bceb25b1..32f6983ed95a9 100644
--- a/llvm/lib/MC/MCTargetOptionsCommandFlags.cpp
+++ b/llvm/lib/MC/MCTargetOptionsCommandFlags.cpp
@@ -102,6 +102,8 @@ 
llvm::mc::RegisterMCTargetOptionsFlags::RegisterMCTargetOptionsFlags() {
                             "no-compact-unwind",
                             "Only emit EH frame entries when compact unwind is 
"
                             "not available"),
+                 clEnumValN(EmitDwarfUnwindType::DwarfOnly, "dwarf-only",
+                            "Force compact unwind to reference DWARF"),
                  clEnumValN(EmitDwarfUnwindType::Default, "default",
                             "Use target platform default")));
   MCBINDOPT(EmitDwarfUnwind);

diff  --git a/llvm/lib/Target/AArch64/MCTargetDesc/AArch64AsmBackend.cpp 
b/llvm/lib/Target/AArch64/MCTargetDesc/AArch64AsmBackend.cpp
index eece54692e18c..98fd4fd4a451b 100644
--- a/llvm/lib/Target/AArch64/MCTargetDesc/AArch64AsmBackend.cpp
+++ b/llvm/lib/Target/AArch64/MCTargetDesc/AArch64AsmBackend.cpp
@@ -575,6 +575,9 @@ class DarwinAArch64AsmBackend : public AArch64AsmBackend {
   /// Generate the compact unwind encoding from the CFI directives.
   uint64_t generateCompactUnwindEncoding(const MCDwarfFrameInfo *FI,
                                          const MCContext *Ctxt) const override 
{
+    if (Ctxt->emitDwarfUnwindInfo() == EmitDwarfUnwindType::DwarfOnly)
+      return CU::UNWIND_ARM64_MODE_DWARF;
+
     // MTE-tagged frames must use DWARF unwinding because compact unwind
     // doesn't handle MTE tags
     if (FI->IsMTETaggedFrame)

diff  --git a/llvm/lib/Target/ARM/MCTargetDesc/ARMAsmBackend.cpp 
b/llvm/lib/Target/ARM/MCTargetDesc/ARMAsmBackend.cpp
index 7c9f130024342..7da862d26c091 100644
--- a/llvm/lib/Target/ARM/MCTargetDesc/ARMAsmBackend.cpp
+++ b/llvm/lib/Target/ARM/MCTargetDesc/ARMAsmBackend.cpp
@@ -1185,6 +1185,9 @@ uint64_t 
ARMAsmBackendDarwin::generateCompactUnwindEncoding(
   if (Subtype != MachO::CPU_SUBTYPE_ARM_V7K)
     return 0;
 
+  if (Ctxt->emitDwarfUnwindInfo() == EmitDwarfUnwindType::DwarfOnly)
+    return CU::UNWIND_ARM_MODE_DWARF;
+
   // Signal frames cannot be encoded in compact unwind.
   if (FI->IsSignalFrame)
     return CU::UNWIND_ARM_MODE_DWARF;

diff  --git a/llvm/lib/Target/X86/MCTargetDesc/X86AsmBackend.cpp 
b/llvm/lib/Target/X86/MCTargetDesc/X86AsmBackend.cpp
index 7515c75ab33e1..0b3da6582d907 100644
--- a/llvm/lib/Target/X86/MCTargetDesc/X86AsmBackend.cpp
+++ b/llvm/lib/Target/X86/MCTargetDesc/X86AsmBackend.cpp
@@ -28,6 +28,7 @@
 #include "llvm/MC/MCRegisterInfo.h"
 #include "llvm/MC/MCSection.h"
 #include "llvm/MC/MCSubtargetInfo.h"
+#include "llvm/MC/MCTargetOptions.h"
 #include "llvm/MC/MCValue.h"
 #include "llvm/MC/TargetRegistry.h"
 #include "llvm/Support/CommandLine.h"
@@ -1306,6 +1307,9 @@ class DarwinX86AsmBackend : public X86AsmBackend {
   /// for the CFI instructions.
   uint64_t generateCompactUnwindEncoding(const MCDwarfFrameInfo *FI,
                                          const MCContext *Ctxt) const override 
{
+    if (Ctxt->emitDwarfUnwindInfo() == EmitDwarfUnwindType::DwarfOnly)
+      return CU::UNWIND_MODE_DWARF;
+
     // Signal frames cannot be encoded in compact unwind.
     if (FI->IsSignalFrame)
       return CU::UNWIND_MODE_DWARF;

diff  --git a/llvm/test/MC/AArch64/compact-unwind-force-dwarf.s 
b/llvm/test/MC/AArch64/compact-unwind-force-dwarf.s
new file mode 100644
index 0000000000000..d5a52f664c253
--- /dev/null
+++ b/llvm/test/MC/AArch64/compact-unwind-force-dwarf.s
@@ -0,0 +1,42 @@
+// RUN: llvm-mc -triple arm64-apple-macos10.6 -filetype=obj %s -o %t.compact.o
+// RUN: llvm-objdump --macho --unwind-info --dwarf=frames %t.compact.o | 
FileCheck %s --check-prefixes=CHECK,COMPACT
+// RUN: llvm-mc -triple arm64-apple-macos10.6 -filetype=obj 
--emit-dwarf-unwind dwarf-only %s -o %t.dwarf.o
+// RUN: llvm-objdump --macho --unwind-info --dwarf=frames %t.dwarf.o | 
FileCheck %s --check-prefixes=CHECK,DWARF
+
+_f:
+  .cfi_startproc
+  ret
+  .cfi_endproc
+
+// On arm64, a compact unwind encoding of 0x03000000 indicates
+// "fall back on DWARF unwind".
+
+// CHECK: Contents of __compact_unwind section:
+// CHECK:   Entry at offset 0x0:
+// CHECK:     start:                0x0 ltmp0
+// CHECK:     length:               0x4
+// COMPACT:   compact encoding:     0x02000000
+// DWARF:     compact encoding:     0x03000000
+
+// CHECK: .eh_frame contents:
+
+// DWARF: 00000000 00000010 00000000 CIE
+// DWARF:   Format:                DWARF32
+// DWARF:   Version:               1
+// DWARF:   Augmentation:          "zR"
+// DWARF:   Code alignment factor: 1
+// DWARF:   Data alignment factor: -8
+// DWARF:   Return address column: 30
+// DWARF:   Augmentation data:     10
+
+// DWARF:   DW_CFA_def_cfa: reg31 +0
+
+// DWARF:   CFA=reg31
+
+// DWARF: FDE cie=00000000
+// DWARF:   Format:       DWARF32
+// DWARF:   DW_CFA_nop:
+// DWARF:   DW_CFA_nop:
+// DWARF:   DW_CFA_nop:
+
+// DWARF:   CFA=reg31

diff  --git a/llvm/test/MC/MachO/ARM/compact-unwind-armv7k.s 
b/llvm/test/MC/MachO/ARM/compact-unwind-armv7k.s
index 65890bcae750f..226aa99614b11 100644
--- a/llvm/test/MC/MachO/ARM/compact-unwind-armv7k.s
+++ b/llvm/test/MC/MachO/ARM/compact-unwind-armv7k.s
@@ -1,4 +1,5 @@
-@ RUN: llvm-mc -triple=thumbv7k-apple-watchos2.0.0 
-emit-compact-unwind-non-canonical=true -filetype=obj -o %t < %s && 
llvm-objdump --unwind-info %t | FileCheck %s
+@ RUN: llvm-mc -triple=thumbv7k-apple-watchos2.0.0 
-emit-compact-unwind-non-canonical=true -filetype=obj -o %t < %s && 
llvm-objdump --unwind-info %t | FileCheck %s --check-prefixes=CHECK,COMPACT
+@ RUN: llvm-mc -triple=thumbv7k-apple-watchos2.0.0 --emit-dwarf-unwind 
dwarf-only -filetype=obj -o %t < %s && llvm-objdump --unwind-info %t | 
FileCheck %s --check-prefixes=CHECK,DWARF
 
 @ CHECK: Contents of __compact_unwind section:
 
@@ -6,8 +7,12 @@
         .align        2
         .code        16
 
+@ On armv7k, a compact unwind entry of 0x04000000 indicates
+@ "fall back on DWARF unwind"
+
 @ CHECK-LABEL: start: {{.*}} _test_r4_r5_r6
-@ CHECK: compact encoding: 0x01000007
+@ COMPACT: compact encoding: 0x01000007
+@ DWARF:   compact encoding: 0x04000000
         .thumb_func        _test_r4_r5_r6
 _test_r4_r5_r6:
         .cfi_startproc
@@ -24,7 +29,8 @@ _test_r4_r5_r6:
 
 
 @ CHECK-LABEL: start: {{.*}} _test_r4_r5_r10_r11
-@ CHECK: compact encoding: 0x01000063
+@ COMPACT: compact encoding: 0x01000063
+@ DWARF:   compact encoding: 0x04000000
         .thumb_func        _test_r4_r5_r10_r11
 _test_r4_r5_r10_r11:
         .cfi_startproc
@@ -42,7 +48,8 @@ _test_r4_r5_r10_r11:
 
 
 @ CHECK-LABEL: start: {{.*}} _test_d8
-@ CHECK: compact encoding: 0x02000000
+@ COMPACT: compact encoding: 0x02000000
+@ DWARF:   compact encoding: 0x04000000
         .thumb_func        _test_d8
 _test_d8:
         .cfi_startproc
@@ -57,7 +64,8 @@ _test_d8:
 
 
 @ CHECK-LABEL: start: {{.*}} _test_d8_d10_d12_d14
-@ CHECK: compact encoding: 0x02000300
+@ COMPACT: compact encoding: 0x02000300
+@ DWARF:   compact encoding: 0x04000000
         .thumb_func        _test_d8_d10_d12_d14
 _test_d8_d10_d12_d14:
         .cfi_startproc
@@ -77,7 +85,8 @@ _test_d8_d10_d12_d14:
         .cfi_endproc
 
 @ CHECK-LABEL: start: {{.*}} _test_varargs
-@ CHECK: compact encoding: 0x01c00001
+@ COMPACT: compact encoding: 0x01c00001
+@ DWARF:   compact encoding: 0x04000000
         .thumb_func        _test_varargs
 _test_varargs:
         .cfi_startproc
@@ -94,7 +103,8 @@ _test_varargs:
         .cfi_endproc
 
 @ CHECK-LABEL: start: {{.*}} _test_missing_lr
-@ CHECK: compact encoding: 0x04000000
+@ COMPACT: compact encoding: 0x04000000
+@ DWARF:   compact encoding: 0x04000000
         .thumb_func _test_missing_lr
 _test_missing_lr:
         .cfi_startproc
@@ -106,7 +116,8 @@ _test_missing_lr:
         .cfi_endproc
 
 @ CHECK-LABEL: start: {{.*}} _test_swapped_offsets
-@ CHECK: compact encoding: 0x04000000
+@ COMPACT: compact encoding: 0x04000000
+@ DWARF:   compact encoding: 0x04000000
         .thumb_func _test_swapped_offsets
 _test_swapped_offsets:
         .cfi_startproc

diff  --git a/llvm/test/MC/X86/compact-unwind-force-dwarf.s 
b/llvm/test/MC/X86/compact-unwind-force-dwarf.s
new file mode 100644
index 0000000000000..37c4c4324d354
--- /dev/null
+++ b/llvm/test/MC/X86/compact-unwind-force-dwarf.s
@@ -0,0 +1,54 @@
+// RUN: llvm-mc -triple x86_64-apple-macos10.6 -filetype=obj 
--emit-dwarf-unwind dwarf-only %s -o %t.dwarf.o
+// RUN: llvm-objdump --macho --unwind-info --dwarf=frames %t.dwarf.o | 
FileCheck %s --check-prefixes=CHECK,DWARF
+// RUN: llvm-mc -triple x86_64-apple-macos10.6 -filetype=obj %s -o %t.compact.o
+// RUN: llvm-objdump --macho --unwind-info --dwarf=frames %t.compact.o | 
FileCheck %s --check-prefixes=CHECK,COMPACT
+
+_f:
+  .cfi_startproc
+  pushq %rbp
+  .cfi_def_cfa_offset 16
+  .cfi_offset %rbp, -16
+  movq %rsp, %rbp
+  .cfi_def_cfa_register %rbp
+  popq %rbp
+  ret
+  .cfi_endproc
+
+// On x86, a compact unwind encoding of 0x04000000 indicates
+// "fall back on DWARF unwind"
+
+// CHECK: Contents of __compact_unwind section:
+// CHECK:   Entry at offset 0x0:
+// CHECK:     start:                0x[[#%x,F:]] _f
+// CHECK:     length:               0x6
+// COMPACT:   compact encoding:     0x01000000
+// DWARF:     compact encoding:     0x04000000
+
+// CHECK: .eh_frame contents:
+// CHECK: 00000000 00000014 00000000 CIE
+// CHECK:   Format:                DWARF32
+// CHECK:   Version:               1
+// CHECK:   Augmentation:          "zR"
+// CHECK:   Code alignment factor: 1
+// CHECK:   Data alignment factor: -8
+// CHECK:   Return address column: 16
+// CHECK:   Augmentation data:     10
+
+// CHECK:   DW_CFA_def_cfa: reg7 +8
+// CHECK:   DW_CFA_offset: reg16 -8
+// CHECK:   DW_CFA_nop:
+// CHECK:   DW_CFA_nop:
+
+// CHECK:   CFA=reg7+8: reg16=[CFA-8]
+
+// CHECK: FDE cie=00000000 pc=00000000...00000006
+// CHECK:   Format:       DWARF32
+// CHECK:   DW_CFA_advance_loc: 1 to 0x1
+// CHECK:   DW_CFA_def_cfa_offset: +16
+// CHECK:   DW_CFA_offset: reg6 -16
+// CHECK:   DW_CFA_advance_loc: 3 to 0x4
+// CHECK:   DW_CFA_def_cfa_register: reg6
+
+// CHECK:   0x0: CFA=reg7+8: reg16=[CFA-8]
+// CHECK:   0x1: CFA=reg7+16: reg6=[CFA-16], reg16=[CFA-8]
+// CHECK:   0x4: CFA=reg6+16: reg6=[CFA-16], reg16=[CFA-8]


        
_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to