Idiomatic way to define byte constants

I've probably been doing too much C/Python lately so I'm struggling knowing the right/idiomatic way to do this simple thing in Swift. I'm implementing a byte stuffing algorithm in Swift. So stealing from C/Python, I found myself wanting to do

enum PacketCode:UInt8 {
	case start = 0x83
	case stop = 0x87
	case escape = 0x88
}

But what I immediately find is that I have to constantly use .rawValue to interface with the bytes I'm working with.
I could implement func = (a:UInt8, b:PacketCode) -> Bool and a variety of other protocols all so that I could use my named byte codes easily with normal UInt8s. But that just feels heavy handed. My forays with Swift in the past taught me that Swift enums are a LOT more than traditional enums, and maybe in this case, I really just should have 3 constants

let StartOfPacket:UInt8 = 0x83
let EndOfPacket:UInt8 = 0x87
let PacketEscape:UInt8 = 0x88

I just liked that they were all kind of namespaced together. Maybe I just do something like

struct PacketCode:UInt8 {
	let start = 0x83
	let stop = 0x87
	let escape = 0x88
}

You could make these static let constants inside a struct or enum as well.

2 Likes

i remember not a long while back I was running into issues where the compiler was not inlining the constants into literals (the way the C preprocessor would) and this was causing strange performance discrepancies depending on which .swift file the constants were declared in (even with WMO). Is this something better suited for constexpr if we ever get that?

1 Like

It'd be better to fix the compiler so that they reliably inline away.

1 Like

any tips on how to verify this and if necessary isolate and report optimizer bugs? The swift compiler with -emit-assembly currently emits reams upon reams of (seemingly repetitive) x86, even for tiny 5-line test programs, so it’s hard to see if the correct substitutions actually get made

The easiest way would be to look at the assembly or LLVM IR for the function you're using the constant from.

struct X {
  static let x = 1738
}

func give_me_an_x() -> Int {
  return x
}

If the optimizer's doing its job, give_me_an_x ought to boil down to mov rax, 1738; ret.

i compiled it to x86 and it gave me

	.text
	.file	"-"
	.protected	main
	.globl	main
	.p2align	4, 0x90
	.type	main,@function
main:
	.cfi_startproc
	pushq	%rbp
.Lcfi0:
	.cfi_def_cfa_offset 16
.Lcfi1:
	.cfi_offset %rbp, -16
	movq	%rsp, %rbp
.Lcfi2:
	.cfi_def_cfa_register %rbp
	xorl	%eax, %eax
	movl	%edi, -4(%rbp)
	movq	%rsi, -16(%rbp)
	popq	%rbp
	retq
.Lfunc_end0:
	.size	main, .Lfunc_end0-main
	.cfi_endproc

	.p2align	4, 0x90
	.type	globalinit_33_0E54A108C56A62EF46FF0114705DB62D_func0,@function
globalinit_33_0E54A108C56A62EF46FF0114705DB62D_func0:
	.cfi_startproc
	pushq	%rbp
.Lcfi3:
	.cfi_def_cfa_offset 16
.Lcfi4:
	.cfi_offset %rbp, -16
	movq	%rsp, %rbp
.Lcfi5:
	.cfi_def_cfa_register %rbp
	movq	$1738, _T08constant1XV1xSivpZ(%rip)
	popq	%rbp
	retq
.Lfunc_end1:
	.size	globalinit_33_0E54A108C56A62EF46FF0114705DB62D_func0, .Lfunc_end1-globalinit_33_0E54A108C56A62EF46FF0114705DB62D_func0
	.cfi_endproc

	.hidden	_T08constant1XV1xSivau
	.globl	_T08constant1XV1xSivau
	.p2align	4, 0x90
	.type	_T08constant1XV1xSivau,@function
_T08constant1XV1xSivau:
	.cfi_startproc
	pushq	%rbp
.Lcfi6:
	.cfi_def_cfa_offset 16
.Lcfi7:
	.cfi_offset %rbp, -16
	movq	%rsp, %rbp
.Lcfi8:
	.cfi_def_cfa_register %rbp
	leaq	globalinit_33_0E54A108C56A62EF46FF0114705DB62D_token0(%rip), %rdi
	leaq	globalinit_33_0E54A108C56A62EF46FF0114705DB62D_func0(%rip), %rsi
	callq	swift_once@PLT
	leaq	_T08constant1XV1xSivpZ(%rip), %rax
	popq	%rbp
	retq
.Lfunc_end2:
	.size	_T08constant1XV1xSivau, .Lfunc_end2-_T08constant1XV1xSivau
	.cfi_endproc

	.hidden	_T08constant1XV1xSivgZ
	.globl	_T08constant1XV1xSivgZ
	.p2align	4, 0x90
	.type	_T08constant1XV1xSivgZ,@function
_T08constant1XV1xSivgZ:
	.cfi_startproc
	pushq	%rbp
.Lcfi9:
	.cfi_def_cfa_offset 16
.Lcfi10:
	.cfi_offset %rbp, -16
	movq	%rsp, %rbp
.Lcfi11:
	.cfi_def_cfa_register %rbp
	callq	_T08constant1XV1xSivau
	movq	(%rax), %rax
	popq	%rbp
	retq
.Lfunc_end3:
	.size	_T08constant1XV1xSivgZ, .Lfunc_end3-_T08constant1XV1xSivgZ
	.cfi_endproc

	.hidden	_T08constant1XVACycfC
	.globl	_T08constant1XVACycfC
	.p2align	4, 0x90
	.type	_T08constant1XVACycfC,@function
_T08constant1XVACycfC:
	.cfi_startproc
	pushq	%rbp
.Lcfi12:
	.cfi_def_cfa_offset 16
.Lcfi13:
	.cfi_offset %rbp, -16
	movq	%rsp, %rbp
.Lcfi14:
	.cfi_def_cfa_register %rbp
	popq	%rbp
	retq
.Lfunc_end4:
	.size	_T08constant1XVACycfC, .Lfunc_end4-_T08constant1XVACycfC
	.cfi_endproc

	.hidden	_T08constant12give_me_an_xSiyF
	.globl	_T08constant12give_me_an_xSiyF
	.p2align	4, 0x90
	.type	_T08constant12give_me_an_xSiyF,@function
_T08constant12give_me_an_xSiyF:
	.cfi_startproc
	pushq	%rbp
.Lcfi15:
	.cfi_def_cfa_offset 16
.Lcfi16:
	.cfi_offset %rbp, -16
	movq	%rsp, %rbp
.Lcfi17:
	.cfi_def_cfa_register %rbp
	callq	_T08constant1XV1xSivau
	movq	(%rax), %rax
	popq	%rbp
	retq
.Lfunc_end5:
	.size	_T08constant12give_me_an_xSiyF, .Lfunc_end5-_T08constant12give_me_an_xSiyF
	.cfi_endproc

	.hidden	__swift_memcpy0_1
	.weak	__swift_memcpy0_1
	.p2align	4, 0x90
	.type	__swift_memcpy0_1,@function
__swift_memcpy0_1:
	movq	%rdi, %rax
	movq	%rsi, -8(%rsp)
	movq	%rdx, -16(%rsp)
	retq
.Lfunc_end6:
	.size	__swift_memcpy0_1, .Lfunc_end6-__swift_memcpy0_1

	.hidden	__swift_noop_void_return
	.weak	__swift_noop_void_return
	.p2align	4, 0x90
	.type	__swift_noop_void_return,@function
__swift_noop_void_return:
	movq	%rdi, -8(%rsp)
	movq	%rsi, -16(%rsp)
	retq
.Lfunc_end7:
	.size	__swift_noop_void_return, .Lfunc_end7-__swift_noop_void_return

	.hidden	_T08constant1XVwet
	.weak	_T08constant1XVwet
	.p2align	4, 0x90
	.type	_T08constant1XVwet,@function
_T08constant1XVwet:
	pushq	%rbp
	movq	%rsp, %rbp
	subq	$80, %rsp
	xorl	%eax, %eax
	cmpl	%esi, %eax
	movl	%esi, -4(%rbp)
	movq	%rdi, -16(%rbp)
	movq	%rdx, -24(%rbp)
	je	.LBB8_7
	movl	-4(%rbp), %eax
	cmpl	$0, %eax
	jbe	.LBB8_6
	movl	$1, %eax
	movq	%rsp, %rcx
	addq	$-16, %rcx
	movq	%rcx, %rsp
	movl	$0, (%rcx)
	movl	-4(%rbp), %edx
	subl	$0, %edx
	addl	$0, %edx
	shrl	$0, %edx
	addl	$1, %edx
	cmpl	$256, %edx
	movl	%edx, -28(%rbp)
	movq	%rcx, -40(%rbp)
	movl	%eax, -44(%rbp)
	jb	.LBB8_4
	movl	$4, %eax
	movl	$2, %ecx
	movl	-28(%rbp), %edx
	cmpl	$65536, %edx
	cmovbl	%ecx, %eax
	movl	%eax, -44(%rbp)
.LBB8_4:
	movl	-44(%rbp), %eax
	movq	-16(%rbp), %rcx
	movq	-40(%rbp), %rdx
	movl	%eax, %esi
	movq	%rdx, %rdi
	movq	%rsi, -56(%rbp)
	movq	%rcx, %rsi
	movq	-56(%rbp), %rdx
	callq	memcpy@PLT
	movq	-40(%rbp), %rcx
	movl	(%rcx), %r8d
	cmpl	$0, %r8d
	movq	%rax, -64(%rbp)
	movl	%r8d, -68(%rbp)
	je	.LBB8_6
	xorl	%eax, %eax
	movb	%al, %cl
	xorl	%eax, %eax
	movq	%rsp, %rdx
	addq	$-16, %rdx
	movq	%rdx, %rsp
	movl	$0, (%rdx)
	movl	-68(%rbp), %esi
	subl	$1, %esi
	shll	$0, %esi
	testb	$1, %cl
	cmovnel	%eax, %esi
	orl	(%rdx), %esi
	addl	$0, %esi
	movl	%esi, -72(%rbp)
	jmp	.LBB8_8
.LBB8_6:
	movl	$4294967295, %eax
	movl	%eax, -72(%rbp)
	jmp	.LBB8_8
.LBB8_7:
	movl	$4294967295, %eax
	movl	%eax, -72(%rbp)
	jmp	.LBB8_8
.LBB8_8:
	movl	-72(%rbp), %eax
	movq	%rbp, %rsp
	popq	%rbp
	retq
.Lfunc_end8:
	.size	_T08constant1XVwet, .Lfunc_end8-_T08constant1XVwet

	.hidden	_T08constant1XVwst
	.weak	_T08constant1XVwst
	.p2align	4, 0x90
	.type	_T08constant1XVwst,@function
_T08constant1XVwst:
	pushq	%rbp
	movq	%rsp, %rbp
	subq	$64, %rsp
	xorl	%eax, %eax
	cmpl	$0, %edx
	movl	%edx, -4(%rbp)
	movl	%esi, -8(%rbp)
	movq	%rdi, -16(%rbp)
	movq	%rcx, -24(%rbp)
	movl	%eax, -28(%rbp)
	jbe	.LBB9_4
	movl	$1, %eax
	movl	-4(%rbp), %ecx
	subl	$0, %ecx
	addl	$0, %ecx
	shrl	$0, %ecx
	addl	$1, %ecx
	cmpl	$256, %ecx
	movl	%ecx, -32(%rbp)
	movl	%eax, -36(%rbp)
	jb	.LBB9_3
	movl	$4, %eax
	movl	$2, %ecx
	movl	-32(%rbp), %edx
	cmpl	$65536, %edx
	cmovbl	%ecx, %eax
	movl	%eax, -36(%rbp)
.LBB9_3:
	movl	-36(%rbp), %eax
	movl	%eax, -28(%rbp)
.LBB9_4:
	movl	-28(%rbp), %eax
	movl	-8(%rbp), %ecx
	cmpl	$0, %ecx
	movl	%eax, -40(%rbp)
	jge	.LBB9_13
	movl	-40(%rbp), %eax
	cmpl	$0, %eax
	je	.LBB9_11
	movl	-40(%rbp), %eax
	cmpl	$1, %eax
	je	.LBB9_8
	movl	-40(%rbp), %eax
	cmpl	$2, %eax
	je	.LBB9_9
	jmp	.LBB9_10
.LBB9_8:
	xorl	%esi, %esi
	movl	$1, %eax
	movl	%eax, %edx
	movq	-16(%rbp), %rdi
	callq	memset@PLT
	jmp	.LBB9_11
.LBB9_9:
	xorl	%esi, %esi
	movl	$2, %eax
	movl	%eax, %edx
	movq	-16(%rbp), %rdi
	callq	memset@PLT
	jmp	.LBB9_11
.LBB9_10:
	xorl	%esi, %esi
	movl	$4, %eax
	movl	%eax, %edx
	movq	-16(%rbp), %rdi
	callq	memset@PLT
.LBB9_11:
	movl	-8(%rbp), %eax
	cmpl	$-1, %eax
	je	.LBB9_22
	jmp	.LBB9_22
.LBB9_13:
	movl	$1, %eax
	xorl	%ecx, %ecx
	movb	%cl, %dl
	movl	-8(%rbp), %ecx
	subl	$0, %ecx
	testb	$1, %dl
	movl	%ecx, %esi
	movl	%ecx, -44(%rbp)
	movl	%eax, -48(%rbp)
	movl	%esi, -52(%rbp)
	jne	.LBB9_15
	movl	-44(%rbp), %eax
	shrl	$0, %eax
	addl	$1, %eax
	movl	-44(%rbp), %ecx
	andl	$0, %ecx
	movl	%eax, -48(%rbp)
	movl	%ecx, -52(%rbp)
.LBB9_15:
	movl	-52(%rbp), %eax
	movl	-48(%rbp), %ecx
	movq	%rsp, %rdx
	movq	%rdx, %rsi
	addq	$-16, %rsi
	movq	%rsi, %rsp
	movl	%eax, -16(%rdx)
	movq	%rsp, %rdx
	addq	$-16, %rdx
	movq	%rdx, %rsp
	movl	%ecx, (%rdx)
	movl	-40(%rbp), %eax
	cmpl	$0, %eax
	movq	%rdx, -64(%rbp)
	je	.LBB9_21
	movl	-40(%rbp), %eax
	cmpl	$1, %eax
	je	.LBB9_18
	movl	-40(%rbp), %eax
	cmpl	$2, %eax
	je	.LBB9_19
	jmp	.LBB9_20
.LBB9_18:
	movq	-64(%rbp), %rax
	movb	(%rax), %cl
	movq	-16(%rbp), %rdx
	movb	%cl, (%rdx)
	jmp	.LBB9_21
.LBB9_19:
	movq	-64(%rbp), %rax
	movw	(%rax), %cx
	movq	-16(%rbp), %rdx
	movw	%cx, (%rdx)
	jmp	.LBB9_21
.LBB9_20:
	movq	-64(%rbp), %rax
	movl	(%rax), %ecx
	movq	-16(%rbp), %rdx
	movl	%ecx, (%rdx)
.LBB9_21:
	jmp	.LBB9_22
.LBB9_22:
	movq	%rbp, %rsp
	popq	%rbp
	retq
.Lfunc_end9:
	.size	_T08constant1XVwst, .Lfunc_end9-_T08constant1XVwst

	.p2align	4, 0x90
	.type	.Lget_field_types_X,@function
.Lget_field_types_X:
	.cfi_startproc
	pushq	%rbp
.Lcfi18:
	.cfi_def_cfa_offset 16
.Lcfi19:
	.cfi_offset %rbp, -16
	movq	%rsp, %rbp
.Lcfi20:
	.cfi_def_cfa_register %rbp
	subq	$48, %rsp
	movq	.Lfield_type_vector_X(%rip), %rax
	cmpq	$0, %rax
	movq	%rdi, -8(%rbp)
	movq	%rax, -16(%rbp)
	jne	.LBB10_3
	xorl	%eax, %eax
	movl	%eax, %ecx
	movl	$7, %eax
	movl	%eax, %esi
	movq	%rcx, %rdi
	movq	%rcx, -24(%rbp)
	callq	swift_rt_swift_slowAlloc
	movq	%rax, %rcx
	movq	%rax, %rsi
	movq	-24(%rbp), %rdi
	movq	%rax, -32(%rbp)
	movq	%rdi, %rax
	movq	-32(%rbp), %rdx
	lock		cmpxchgq	%rdx, .Lfield_type_vector_X(%rip)
	sete	%r8b
	testb	$1, %r8b
	movq	%rcx, -40(%rbp)
	movq	%rax, -48(%rbp)
	movq	%rsi, -16(%rbp)
	jne	.LBB10_3
	xorl	%eax, %eax
	movl	%eax, %esi
	movl	$7, %eax
	movl	%eax, %edx
	movq	-40(%rbp), %rdi
	callq	swift_rt_swift_slowDealloc
	movq	-48(%rbp), %rdx
	movq	%rdx, -16(%rbp)
.LBB10_3:
	movq	-16(%rbp), %rax
	addq	$48, %rsp
	popq	%rbp
	retq
.Lfunc_end10:
	.size	.Lget_field_types_X, .Lfunc_end10-.Lget_field_types_X
	.cfi_endproc

	.hidden	_T08constant1XVMa
	.globl	_T08constant1XVMa
	.p2align	4, 0x90
	.type	_T08constant1XVMa,@function
_T08constant1XVMa:
	pushq	%rbp
	movq	%rsp, %rbp
	leaq	_T08constant1XVMf(%rip), %rax
	addq	$8, %rax
	popq	%rbp
	retq
.Lfunc_end11:
	.size	_T08constant1XVMa, .Lfunc_end11-_T08constant1XVMa

	.hidden	swift_rt_swift_slowAlloc
	.weak	swift_rt_swift_slowAlloc
	.p2align	4, 0x90
	.type	swift_rt_swift_slowAlloc,@function
swift_rt_swift_slowAlloc:
	movq	_swift_slowAlloc@GOTPCREL(%rip), %rax
	movq	(%rax), %rax
	jmpq	*%rax
.Lfunc_end12:
	.size	swift_rt_swift_slowAlloc, .Lfunc_end12-swift_rt_swift_slowAlloc

	.hidden	swift_rt_swift_slowDealloc
	.weak	swift_rt_swift_slowDealloc
	.p2align	4, 0x90
	.type	swift_rt_swift_slowDealloc,@function
swift_rt_swift_slowDealloc:
	movq	_swift_slowDealloc@GOTPCREL(%rip), %rax
	movq	(%rax), %rax
	jmpq	*%rax
.Lfunc_end13:
	.size	swift_rt_swift_slowDealloc, .Lfunc_end13-swift_rt_swift_slowDealloc

	.type	globalinit_33_0E54A108C56A62EF46FF0114705DB62D_token0,@object
	.local	globalinit_33_0E54A108C56A62EF46FF0114705DB62D_token0
	.comm	globalinit_33_0E54A108C56A62EF46FF0114705DB62D_token0,8,8
	.hidden	_T08constant1XV1xSivpZ
	.type	_T08constant1XV1xSivpZ,@object
	.bss
	.globl	_T08constant1XV1xSivpZ
	.p2align	3
_T08constant1XV1xSivpZ:
	.zero	8
	.size	_T08constant1XV1xSivpZ, 8

	.type	_T08constant1XVWV,@object
	.section	.data.rel.ro,"aw",@progbits
	.p2align	3
_T08constant1XVWV:
	.quad	__swift_memcpy0_1
	.quad	__swift_noop_void_return
	.quad	__swift_memcpy0_1
	.quad	__swift_memcpy0_1
	.quad	__swift_memcpy0_1
	.quad	__swift_memcpy0_1
	.quad	__swift_memcpy0_1
	.quad	_T08constant1XVwet
	.quad	_T08constant1XVwst
	.quad	0
	.quad	0
	.quad	1
	.size	_T08constant1XVWV, 96

	.type	.L__unnamed_1,@object
	.section	.rodata,"a",@progbits
.L__unnamed_1:
	.asciz	"8constant1XV"
	.size	.L__unnamed_1, 13

	.type	.L__unnamed_2,@object
.L__unnamed_2:
	.zero	1
	.size	.L__unnamed_2, 1

	.hidden	_T08constant1XVMn
	.type	_T08constant1XVMn,@object
	.globl	_T08constant1XVMn
	.p2align	3
_T08constant1XVMn:
	.long	.L__unnamed_1-_T08constant1XVMn
	.long	0
	.long	2
	.long	(.L__unnamed_2-_T08constant1XVMn)-12
	.long	(.Lget_field_types_X-_T08constant1XVMn)-16
	.long	1
	.long	(_T08constant1XVMa-_T08constant1XVMn)-24
	.long	2
	.long	0
	.long	0
	.short	1
	.short	0
	.long	0
	.size	_T08constant1XVMn, 48

	.type	_T08constant1XVMf,@object
	.section	.data.rel.ro,"aw",@progbits
	.p2align	3
_T08constant1XVMf:
	.quad	_T08constant1XVWV
	.quad	1
	.quad	_T08constant1XVMn
	.size	_T08constant1XVMf, 24

	.type	.L__unnamed_3,@object
	.section	swift3_typeref,"a",@progbits
.L__unnamed_3:
	.asciz	"8constant1XV"
	.size	.L__unnamed_3, 13

	.type	_T08constant1XVMF,@object
	.section	swift3_fieldmd,"aw",@progbits
	.p2align	2
_T08constant1XVMF:
	.long	.L__unnamed_3-_T08constant1XVMF
	.long	0
	.short	0
	.short	12
	.long	0
	.size	_T08constant1XVMF, 16

	.type	.Lfield_type_vector_X,@object
	.local	.Lfield_type_vector_X
	.comm	.Lfield_type_vector_X,8,8
	.type	l_type_metadata_table,@object
	.section	swift2_type_metadata,"aw",@progbits
	.p2align	3
l_type_metadata_table:
	.long	(_T08constant1XVMf-l_type_metadata_table)+8
	.long	1
	.size	l_type_metadata_table, 8

	.hidden	__swift_reflection_version
	.type	__swift_reflection_version,@object
	.section	.rodata,"a",@progbits
	.weak	__swift_reflection_version
	.p2align	1
__swift_reflection_version:
	.short	3
	.size	__swift_reflection_version, 2

	.type	.L_swift1_autolink_entries,@object
	.section	.swift1_autolink_entries,"a",@progbits
	.p2align	3
.L_swift1_autolink_entries:
	.asciz	"-lswiftCore\000-lswiftSwiftOnoneSupport"
	.size	.L_swift1_autolink_entries, 37


	.globl	_T08constant1XVN
	.hidden	_T08constant1XVN
_T08constant1XVN = _T08constant1XVMf+8
	.section	".note.GNU-stack","",@progbits

i don't know how to begin making sense of this lol

Was this with the optimizer enabled? It looks like it's going through an accessor function instead of inlining the constant value.

I see

_T08constant1XV1xSivgZ:
	pushq	%rbp
	movq	%rsp, %rbp
	movl	$1738, %eax
	popq	%rbp
	retq
.Lfunc_end2:
	.size	_T08constant1XV1xSivgZ, .Lfunc_end2-_T08constant1XV1xSivgZ

	.hidden	_T08constant1XVACycfC
	.globl	_T08constant1XVACycfC
	.p2align	4, 0x90
	.type	_T08constant1XVACycfC,@function
_T08constant1XVACycfC:
	pushq	%rbp
	movq	%rsp, %rbp
	popq	%rbp
	retq
.Lfunc_end3:
	.size	_T08constant1XVACycfC, .Lfunc_end3-_T08constant1XVACycfC

	.hidden	_T08constant12give_me_an_xSiyF
	.globl	_T08constant12give_me_an_xSiyF
	.p2align	4, 0x90
	.type	_T08constant12give_me_an_xSiyF,@function
_T08constant12give_me_an_xSiyF:
	pushq	%rbp
	movq	%rsp, %rbp
	movl	$1738, %eax
	popq	%rbp
	retq

among a ton of other stuff. i assume _T08constant1XV1xSivgZ is an accessor function to get the X.x member, and _T08constant12give_me_an_xSiyF is the actual function, but what is all the other code in the generated assembly? what is _T08constant1XVACycfC for, it just looks like a completely empty stack frame?

also i didn’t know swift returned in %eax like C does

That's right. I would expect the optimizer to be able to turn the worst-case access through a function into a normal stored global variable in this case. The rest of the functions in the disassembly look like the value witness functions for X.

this is probs a dumb question but what are value witness functions? And why does Swift still use a frame pointer?

The value witness functions are part of the type metadata that's used if you pass a value of type X into an unspecialized generic function, or put it in a value of protocol type, to dynamically handle copying and moving values of the type. LLVM decides whether to emit the frame pointer or not. Just like in C, it's not strictly necessary, but is useful for debuggability to get good stack traces.

2 Likes

why does x need a value witness table if it’s small enough to fit in the 24 byte protocol inline storage? (is that still a thing?)

Every type needs a value witness table to deal with its specific copying logic. If you copy a protocol value that contains a class reference, that copy needs to retain the reference, whereas if it’s a trivial type like Int, it just needs to copy the bits. That’s necessary regardless of whether values of the contained type fit inside the inline buffer.

2 Likes