Discussion:
CMP flags going wrong in my emu?
(too old to reply)
luserdroog
2020-09-14 04:13:34 UTC
Permalink
I'm trying to fill out my toy forth interpreter and I think I have
a bug in my emulator for the CMP instruction and therefore probably
also SUB and SBB.

The problem is showing up in my comparator functions
CODE(<, less, POP(CX),POP(BX), MOVAXI(0xff,0xff), CMP(,R,BX,CX), JL,2,INC_(R,AX), PUSH(AX))
CODE(>, more, POP(CX),POP(BX), MOVAXI(0xff,0xff), CMP(,R,BX,CX), JG,2,INC_(R,AX), PUSH(AX))

When I test it with

WORD(test2a,test2a, enter, one, ten, less, dot,
one, ten, more, dot, ok)

no matter how I permute the arguments -- changing BX and CX in the CMP
instruction, or in the POP order, or in the test script -- the results
never change.

0 -1 OK

Here's the stack trace on the first one.

ax:ffff cx:000a dx:0000 bx:0001 sp:f000 bp:1ffc si:0e1a di:0000 ip:046d fl:0004 NC NO NS NZ
3b(073) cmpwt: d9(331) x:1 y:10 ->fffffff7
ax:ffff cx:000a dx:0000 bx:0001 sp:f000 bp:1ffc si:0e1a di:0000 ip:046f fl:0891 CA OV SN NZ
7c(174) jl: 02(002) <0>
ax:ffff cx:000a dx:0000 bx:0001 sp:f000 bp:1ffc si:0e1a di:0000 ip:0471 fl:0891 CA OV SN NZ
ff(377) grp2w: c0(300) INC ->0000

The CMP produces fffffff7 which seems like the correct subtraction
extended to 32 bits. But the JL isn't taken.

Are my flags wrong after the cmpwt instruction?
luserdroog
2020-09-14 04:49:26 UTC
Permalink
Post by luserdroog
Here's the stack trace on the first one.
ax:ffff cx:000a dx:0000 bx:0001 sp:f000 bp:1ffc si:0e1a di:0000 ip:046d fl:0004 NC NO NS NZ
3b(073) cmpwt: d9(331) x:1 y:10 ->fffffff7
ax:ffff cx:000a dx:0000 bx:0001 sp:f000 bp:1ffc si:0e1a di:0000 ip:046f fl:0891 CA OV SN NZ
7c(174) jl: 02(002) <0>
ax:ffff cx:000a dx:0000 bx:0001 sp:f000 bp:1ffc si:0e1a di:0000 ip:0471 fl:0891 CA OV SN NZ
ff(377) grp2w: c0(300) INC ->0000
The CMP produces fffffff7 which seems like the correct subtraction
extended to 32 bits. But the JL isn't taken.
Are my flags wrong after the cmpwt instruction?
I'm pretty sure this *is* wrong. I think I should have Carry but No
Overflow. The upper half is clearly the sign extension of the low 15
bits so it should not be considered an overflow, I think.
Alexei A. Frounze
2020-09-14 05:54:15 UTC
Permalink
Post by luserdroog
I'm trying to fill out my toy forth interpreter and I think I have
a bug in my emulator for the CMP instruction and therefore probably
also SUB and SBB.
The problem is showing up in my comparator functions
CODE(<, less, POP(CX),POP(BX), MOVAXI(0xff,0xff), CMP(,R,BX,CX), JL,2,INC_(R,AX), PUSH(AX))
CODE(>, more, POP(CX),POP(BX), MOVAXI(0xff,0xff), CMP(,R,BX,CX), JG,2,INC_(R,AX), PUSH(AX))
When I test it with
WORD(test2a,test2a, enter, one, ten, less, dot,
one, ten, more, dot, ok)
no matter how I permute the arguments -- changing BX and CX in the CMP
instruction, or in the POP order, or in the test script -- the results
never change.
0 -1 OK
Here's the stack trace on the first one.
ax:ffff cx:000a dx:0000 bx:0001 sp:f000 bp:1ffc si:0e1a di:0000 ip:046d fl:0004 NC NO NS NZ
3b(073) cmpwt: d9(331) x:1 y:10 ->fffffff7
ax:ffff cx:000a dx:0000 bx:0001 sp:f000 bp:1ffc si:0e1a di:0000 ip:046f fl:0891 CA OV SN NZ
7c(174) jl: 02(002) <0>
ax:ffff cx:000a dx:0000 bx:0001 sp:f000 bp:1ffc si:0e1a di:0000 ip:0471 fl:0891 CA OV SN NZ
ff(377) grp2w: c0(300) INC ->0000
The CMP produces fffffff7 which seems like the correct subtraction
extended to 32 bits. But the JL isn't taken.
Are my flags wrong after the cmpwt instruction?
If you have to ask, it's quite possible.
Care to show your code for add/adc, sub/sbb/cmp?

I don't know why you're sign-extending 16-bit values
to 32 bits when performing a 16-bit operation.
It may contribute to the problem by itself.

Before you ask a question like this you should explain
your syntax a little bit. For example, in

CMP(,R,BX,CX)

I don't know if the operand order is the same as in the
intel/AMD x86 manual or the opposite like in the AT&T
assembly syntax.

Supposing the order is like in the intel syntax and you're doing:

CMP BX, CX

when BX=1 and CX=0xA, you should get:

difference = 0xFFF7 (unsigned) or -9 (signed), not stored anywhere

FLAGS.SF=1 (most significant (AKA sign) bit of the above difference)

FLAGS.ZF=0 (the above difference isn't 0)

FLAGS.CF=1 (in unsigned comparison 1 < 0xA holds true)

FLAGS.OF=0 (mathematical difference: 1 - 0xA = -9, correctly represented)

JL will jump when SF!=OF. This should be the case here.

It's very likely that your signed overflow computation is busted.
This may help: https://stackoverflow.com/a/8037485/968261

Alex
luserdroog
2020-09-14 06:18:01 UTC
Permalink
Post by Alexei A. Frounze
Post by luserdroog
I'm trying to fill out my toy forth interpreter and I think I have
a bug in my emulator for the CMP instruction and therefore probably
also SUB and SBB.
The problem is showing up in my comparator functions
CODE(<, less, POP(CX),POP(BX), MOVAXI(0xff,0xff), CMP(,R,BX,CX), JL,2,INC_(R,AX), PUSH(AX))
CODE(>, more, POP(CX),POP(BX), MOVAXI(0xff,0xff), CMP(,R,BX,CX), JG,2,INC_(R,AX), PUSH(AX))
When I test it with
WORD(test2a,test2a, enter, one, ten, less, dot,
one, ten, more, dot, ok)
no matter how I permute the arguments -- changing BX and CX in the CMP
instruction, or in the POP order, or in the test script -- the results
never change.
0 -1 OK
Here's the stack trace on the first one.
ax:ffff cx:000a dx:0000 bx:0001 sp:f000 bp:1ffc si:0e1a di:0000 ip:046d fl:0004 NC NO NS NZ
3b(073) cmpwt: d9(331) x:1 y:10 ->fffffff7
ax:ffff cx:000a dx:0000 bx:0001 sp:f000 bp:1ffc si:0e1a di:0000 ip:046f fl:0891 CA OV SN NZ
7c(174) jl: 02(002) <0>
ax:ffff cx:000a dx:0000 bx:0001 sp:f000 bp:1ffc si:0e1a di:0000 ip:0471 fl:0891 CA OV SN NZ
ff(377) grp2w: c0(300) INC ->0000
The CMP produces fffffff7 which seems like the correct subtraction
extended to 32 bits. But the JL isn't taken.
Are my flags wrong after the cmpwt instruction?
If you have to ask, it's quite possible.
Care to show your code for add/adc, sub/sbb/cmp?
I don't know why you're sign-extending 16-bit values
to 32 bits when performing a 16-bit operation.
It may contribute to the problem by itself.
Before you ask a question like this you should explain
your syntax a little bit. For example, in
CMP(,R,BX,CX)
I don't know if the operand order is the same as in the
intel/AMD x86 manual or the opposite like in the AT&T
assembly syntax.
It's a custom macro syntax which targets the "from" forms R selects
MOD=3 (register to register) and BX is in the REG field and CX is in
the REG/MEM field. So, as correctly surmised below, BX is the
Destination and CX is the Source. So the comparison should be the
same as the subtraction BX-CX.
Post by Alexei A. Frounze
CMP BX, CX
difference = 0xFFF7 (unsigned) or -9 (signed), not stored anywhere
FLAGS.SF=1 (most significant (AKA sign) bit of the above difference)
FLAGS.ZF=0 (the above difference isn't 0)
FLAGS.CF=1 (in unsigned comparison 1 < 0xA holds true)
FLAGS.OF=0 (mathematical difference: 1 - 0xA = -9, correctly represented)
JL will jump when SF!=OF. This should be the case here.
It's very likely that your signed overflow computation is busted.
This may help: https://stackoverflow.com/a/8037485/968261
Alex
Thanks. Yep, it's that stupid overflow again lol.
luserdroog
2020-09-14 17:15:06 UTC
Permalink
Post by luserdroog
Post by Alexei A. Frounze
Before you ask a question like this you should explain
your syntax a little bit. For example, in
CMP(,R,BX,CX)
I don't know if the operand order is the same as in the
intel/AMD x86 manual or the opposite like in the AT&T
assembly syntax.
It's a custom macro syntax which targets the "from" forms R selects
MOD=3 (register to register) and BX is in the REG field and CX is in
the REG/MEM field. So, as correctly surmised below, BX is the
Destination and CX is the Source. So the comparison should be the
same as the subtraction BX-CX.
I said this wrong. These are "to" forms, not "from". The first argument
to my CMP macro can be F to create a "from" opcode or blank (as above)
to create a "to" opcode. Elsethread I mentioned the expanded operator
function listed in my codegolf answer, that should be "addbf" (the "from"
form) not "addbt". Sigh. addbf (add byte from) being opcode 0x00.
wolfgang kern
2020-09-15 08:27:31 UTC
Permalink
On 14.09.2020 19:15, luserdroog wrote:
...
Post by luserdroog
Post by luserdroog
CMP(,R,BX,CX)
3b d9 CMP bx,cx ;does a faked SUB (bx-cx)
flags affected: CY,PAR,AUX,Z,S,O
usable conditional instructions after this:
Jcc(short or long)/SETcc/CMOVcc/(FCMOVcc is a bit different)

x86 Conditions:
O code 70/40/80/90 if OF=0
NO code 71 41/81/91 if OF=1
C aka B aka NAE aka < code 72/42/82/92 if CF=1 if below/if carry
NC aka NB aka AE aka >= code 73/43/83/93 if CF=0 if not below/if
above or equal

Z aka E aka = code 74/44/84/94 if ZF=1 if zero/if equal
NZ aka NE aka <> code 75 and so on if ZF=0 if not zero/if
not equal

NA aka BE aka <= code 76 if CF=1 or ZF=1 if not
above/if below or equal

A aka NBE aka > aka NCNZ code 77 if CF=0 and ZF=0 if not
below or equal/if above

S aka SM aka - code 78 if SF=1 (usable after
unsigned cmp/sub/add...)

NS aka SP aka + code 79 if SF=0
P aka PE code 7A if PE=1 parity even
(works on low byte only)

NP aka PO code 7B if PE=0 prity odd ("-")
L aka NGE aka <0 code 7C if SF<>OF if less/if not
greater or equal **

NL aka GE aka >=0 code 7D if SF=OF if not less/ if
greater or equal **

NG aka LE aka <=0 code 7E if SF<>OF or ZF=1 if less
or equal/if not greater **

G aka NLE aka >0 code 7F/4F/9F/9F if ZF=0 and SF=OF if not
less or equal/if greater **

** this four are made for signed compare...
ie:
70 xx JO xx
0f 80 xx xx JO xxxx
0f 40/r CMOVO r
0f 90/0/rm SETO r/rm
...
Post by luserdroog
I said this wrong. These are "to" forms, not "from". The first
argument to my CMP macro can be F to create a "from" opcode or blank
(as above) to create a "to" opcode. Elsethread I mentioned the
expanded operator function listed in my codegolf answer, that should
be "addbf" (the "from" form) not "addbt". Sigh. addbf (add byte from)
being opcode 0x00.
Why easy when you can make it complicated ?
what's wrong with the assembler syntax used by NASM,FASM,..(long list)
and also AMD and Intel in their docs ?

sorry if the lines wrap
__
wolfgang
luserdroog
2020-09-15 16:12:47 UTC
Permalink
Post by wolfgang kern
Post by luserdroog
I said this wrong. These are "to" forms, not "from". The first
argument to my CMP macro can be F to create a "from" opcode or blank
(as above) to create a "to" opcode. Elsethread I mentioned the
expanded operator function listed in my codegolf answer, that should
be "addbf" (the "from" form) not "addbt". Sigh. addbf (add byte from)
being opcode 0x00.
Why easy when you can make it complicated ?
what's wrong with the assembler syntax used by NASM,FASM,..(long list)
and also AMD and Intel in their docs ?
sorry if the lines wrap
The whole idea of this project was to build something running on top
of the 8086 emulator. This was to exercise the emulator and shake
out bugs, but also to motivate implementing more opcodes.

For the assembler part, I wanted to put the assembled bytes into a C
data structure. I also wanted complete control over the exact encoding
of the opcodes, mostly so I could make sure it was using codes that
are actually implemented in the emulator.

For these reasons, the best choice I could think of was to make the
assembler with C preprocessor macros that yield a comma separated
list of byte values. One advantage of this approach is that I can
design the *machine code* to be (somewhat) readable. I put opcodes
in hex, M/R/R-M bytes in octal, and offsets in decimal.

"We choose to do these things not because they are easy, but because
they are hard."
luserdroog
2020-09-14 06:50:16 UTC
Permalink
Post by Alexei A. Frounze
Post by luserdroog
Here's the stack trace on the first one.
ax:ffff cx:000a dx:0000 bx:0001 sp:f000 bp:1ffc si:0e1a di:0000 ip:046d fl:0004 NC NO NS NZ
3b(073) cmpwt: d9(331) x:1 y:10 ->fffffff7
ax:ffff cx:000a dx:0000 bx:0001 sp:f000 bp:1ffc si:0e1a di:0000 ip:046f fl:0891 CA OV SN NZ
7c(174) jl: 02(002) <0>
ax:ffff cx:000a dx:0000 bx:0001 sp:f000 bp:1ffc si:0e1a di:0000 ip:0471 fl:0891 CA OV SN NZ
ff(377) grp2w: c0(300) INC ->0000
The CMP produces fffffff7 which seems like the correct subtraction
extended to 32 bits. But the JL isn't taken.
Are my flags wrong after the cmpwt instruction?
If you have to ask, it's quite possible.
Care to show your code for add/adc, sub/sbb/cmp?
The code is at:

https://github.com/luser-dr00g/8086/blob/fd410940a9fd226dea5d70a8e3264fabee4595f3/a8086.c

and an explanation of the code is at:

https://codegolf.stackexchange.com/a/52902/2381

(this includes a fully preprocessed and indent-ed addbt
(add byte "to") operator function.

The CMP operator sets the destination pointer to a sink,
loads variables x and y from dest and source, and calls
SUB which does ... hang on, computer rebooted. now I gotta
delete the X.lock to fire up emacs ... ah, that's better...
SUB does

#define SUB z=d?x-y:y-x; LOGFLAGS MATHFLAGS RESULT

where the flags are set with

// flags set by logical operators
#define LOGFLAGS *fl=0; \
*fl |= ( (z&(w?0x8000:0x80)) ?SF:0) \
| ( (z&(w?0xffff:0xff))==0 ?ZF:0) ;

// additional flags set by math operators
#define MATHFLAGS *fl |= ( (z&(w?0xffff0000:0xff00)) ?CF:0) \
| ( ((z^x)&(z^y)&(w?0x8000:0x80)) ?OF:0) \
| ( ((x^y^z)&0x10) ?AF:0); \
SETPF

I don't know where I came up with that expression for OF.
It's gibberish to me now.
Alexei A. Frounze
2020-09-14 08:41:35 UTC
Permalink
Post by luserdroog
// additional flags set by math operators
#define MATHFLAGS *fl |= ( (z&(w?0xffff0000:0xff00)) ?CF:0) \
| ( ((z^x)&(z^y)&(w?0x8000:0x80)) ?OF:0) \
| ( ((x^y^z)&0x10) ?AF:0); \
SETPF
I don't know where I came up with that expression for OF.
It's gibberish to me now.
It's making sure that the result's sign is the same
as the signs of x and y. If that isn't true, OF gets set.
That works for addition, z = x + y,
but not subtraction, z = x - y.
Unless you exchange things a bit...

add: z = x + y
(z^x)&(z^y)&...

sub: z = x - y
OR, speaking in terms of addition...
sub: x = z + y
(x^z)&(x^y)&...

IOW, for the purposes of OF calculation you need to
exchange x and z in that formula of yours.

Alex
luserdroog
2020-09-14 09:38:07 UTC
Permalink
Post by Alexei A. Frounze
Post by luserdroog
// additional flags set by math operators
#define MATHFLAGS *fl |= ( (z&(w?0xffff0000:0xff00)) ?CF:0) \
| ( ((z^x)&(z^y)&(w?0x8000:0x80)) ?OF:0) \
| ( ((x^y^z)&0x10) ?AF:0); \
SETPF
I don't know where I came up with that expression for OF.
It's gibberish to me now.
It's making sure that the result's sign is the same
as the signs of x and y. If that isn't true, OF gets set.
That works for addition, z = x + y,
but not subtraction, z = x - y.
Unless you exchange things a bit...
add: z = x + y
(z^x)&(z^y)&...
sub: z = x - y
OR, speaking in terms of addition...
sub: x = z + y
(x^z)&(x^y)&...
IOW, for the purposes of OF calculation you need to
exchange x and z in that formula of yours.
Alex
Thanks a bunch! It works great now. My forth can now
print numbers in any base from 2 to 36.
Alexei A. Frounze
2020-09-14 10:34:17 UTC
Permalink
Post by luserdroog
Post by Alexei A. Frounze
Post by luserdroog
// additional flags set by math operators
#define MATHFLAGS *fl |= ( (z&(w?0xffff0000:0xff00)) ?CF:0) \
| ( ((z^x)&(z^y)&(w?0x8000:0x80)) ?OF:0) \
| ( ((x^y^z)&0x10) ?AF:0); \
SETPF
I don't know where I came up with that expression for OF.
It's gibberish to me now.
It's making sure that the result's sign is the same
as the signs of x and y. If that isn't true, OF gets set.
That works for addition, z = x + y,
but not subtraction, z = x - y.
Unless you exchange things a bit...
add: z = x + y
(z^x)&(z^y)&...
sub: z = x - y
OR, speaking in terms of addition...
sub: x = z + y
(x^z)&(x^y)&...
IOW, for the purposes of OF calculation you need to
exchange x and z in that formula of yours.
Alex
Thanks a bunch! It works great now. My forth can now
print numbers in any base from 2 to 36.
Sorry, I hit "send" too soon without a clarification.

You need to exchange x with z in MATHFLAGS for sub only.
I think now you have add broken.

Alex
luserdroog
2020-09-14 16:53:55 UTC
Permalink
Post by Alexei A. Frounze
Post by luserdroog
Post by Alexei A. Frounze
Post by luserdroog
// additional flags set by math operators
#define MATHFLAGS *fl |= ( (z&(w?0xffff0000:0xff00)) ?CF:0) \
| ( ((z^x)&(z^y)&(w?0x8000:0x80)) ?OF:0) \
| ( ((x^y^z)&0x10) ?AF:0); \
SETPF
I don't know where I came up with that expression for OF.
It's gibberish to me now.
It's making sure that the result's sign is the same
as the signs of x and y. If that isn't true, OF gets set.
That works for addition, z = x + y,
but not subtraction, z = x - y.
Unless you exchange things a bit...
add: z = x + y
(z^x)&(z^y)&...
sub: z = x - y
OR, speaking in terms of addition...
sub: x = z + y
(x^z)&(x^y)&...
IOW, for the purposes of OF calculation you need to
exchange x and z in that formula of yours.
Alex
Thanks a bunch! It works great now. My forth can now
print numbers in any base from 2 to 36.
Sorry, I hit "send" too soon without a clarification.
You need to exchange x with z in MATHFLAGS for sub only.
I think now you have add broken.
Ok. Thanks again. I'm parameterizing it so there's a separate formula
for ADDFLAGS vs SUBFLAGS.
luserdroog
2020-09-14 21:31:54 UTC
Permalink
Post by Alexei A. Frounze
Post by luserdroog
// additional flags set by math operators
#define MATHFLAGS *fl |= ( (z&(w?0xffff0000:0xff00)) ?CF:0) \
| ( ((z^x)&(z^y)&(w?0x8000:0x80)) ?OF:0) \
| ( ((x^y^z)&0x10) ?AF:0); \
SETPF
I don't know where I came up with that expression for OF.
It's gibberish to me now.
It's making sure that the result's sign is the same
as the signs of x and y. If that isn't true, OF gets set.
That works for addition, z = x + y,
but not subtraction, z = x - y.
Unless you exchange things a bit...
add: z = x + y
(z^x)&(z^y)&...
sub: z = x - y
OR, speaking in terms of addition...
sub: x = z + y
(x^z)&(x^y)&...
IOW, for the purposes of OF calculation you need to
exchange x and z in that formula of yours.
Alex
Do I also need to swap x and y if the direction is
reversed and z = y - x?
then y = z + x.
sthg like:
((d?x:y)^z)&(x^y)&...
Alexei A. Frounze
2020-09-14 22:51:41 UTC
Permalink
Post by luserdroog
Post by Alexei A. Frounze
Post by luserdroog
// additional flags set by math operators
#define MATHFLAGS *fl |= ( (z&(w?0xffff0000:0xff00)) ?CF:0) \
| ( ((z^x)&(z^y)&(w?0x8000:0x80)) ?OF:0) \
| ( ((x^y^z)&0x10) ?AF:0); \
SETPF
I don't know where I came up with that expression for OF.
It's gibberish to me now.
It's making sure that the result's sign is the same
as the signs of x and y. If that isn't true, OF gets set.
That works for addition, z = x + y,
but not subtraction, z = x - y.
Unless you exchange things a bit...
add: z = x + y
(z^x)&(z^y)&...
sub: z = x - y
OR, speaking in terms of addition...
sub: x = z + y
(x^z)&(x^y)&...
IOW, for the purposes of OF calculation you need to
exchange x and z in that formula of yours.
Alex
Do I also need to swap x and y if the direction is
reversed and z = y - x?
then y = z + x.
((d?x:y)^z)&(x^y)&...
I didn't follow the meaning of "direction" in your code,
but yes, if you're swapping the diminuend and the subtrahend.

That gives you 3 cases:

z=x+y:
(z^x)&(z^y)&...

z=x-y (mathematically equivalent to x=z+y):
(x^z)&(x^y)&...

z=y-x (mathematically equivalent to y=x+z):
(y^x)&(y^z)&...

Putting it all together:

(((is_add||sub_dir)?z:y)^x) &
(((is_add||!sub_dir)?z:x)^y) & ...

Alex
olcott
2020-09-16 20:06:56 UTC
Permalink
Post by luserdroog
I'm trying to fill out my toy forth interpreter and I think I have
a bug in my emulator for the CMP instruction and therefore probably
also SUB and SBB.
The problem is showing up in my comparator functions
CODE(<, less, POP(CX),POP(BX), MOVAXI(0xff,0xff), CMP(,R,BX,CX), JL,2,INC_(R,AX), PUSH(AX))
CODE(>, more, POP(CX),POP(BX), MOVAXI(0xff,0xff), CMP(,R,BX,CX), JG,2,INC_(R,AX), PUSH(AX))
When I test it with
WORD(test2a,test2a, enter, one, ten, less, dot,
one, ten, more, dot, ok)
no matter how I permute the arguments -- changing BX and CX in the CMP
instruction, or in the POP order, or in the test script -- the results
never change.
0 -1 OK
Here's the stack trace on the first one.
ax:ffff cx:000a dx:0000 bx:0001 sp:f000 bp:1ffc si:0e1a di:0000 ip:046d fl:0004 NC NO NS NZ
3b(073) cmpwt: d9(331) x:1 y:10 ->fffffff7
ax:ffff cx:000a dx:0000 bx:0001 sp:f000 bp:1ffc si:0e1a di:0000 ip:046f fl:0891 CA OV SN NZ
7c(174) jl: 02(002) <0>
ax:ffff cx:000a dx:0000 bx:0001 sp:f000 bp:1ffc si:0e1a di:0000 ip:0471 fl:0891 CA OV SN NZ
ff(377) grp2w: c0(300) INC ->0000
The CMP produces fffffff7 which seems like the correct subtraction
extended to 32 bits. But the JL isn't taken.
Are my flags wrong after the cmpwt instruction?
This is a real "emu"
https://i.insider.com/5d31dff836e03c28ef338ee2?width=1300&format=jpeg&auto=webp
--
Copyright 2020 Pete Olcott
Loading...