making progress

This commit is contained in:
Perry Kivolowitz 2022-06-18 09:53:54 -05:00
parent 6ce1759392
commit b3f1a63e51
5 changed files with 332 additions and 12 deletions

View file

@ -67,30 +67,153 @@ five bit field inside one byte.
Without bit fields, we would have to write this code:
```c
/* Note the absence of defensive programming such as checking
to ensure that byte is not null and that bit_number is not
too large.
*/
void ClearA(unsigned char * byte) {
*byte &= ~1;
}
```
This function takes the address of the byte containing the `a`,
`b` and `c` portions.
*Good programming practice would check `byte` against `NULL`
or `nullptr`.*
The `~` operator is a bitwise negation. All the bits in the
value are flipped from 0 to 1 or 1 to 0. `~1` in an unsigned
char will produce `0xFE`, or all ones except for bit 0. `and`ing
this value to `*byte` ensures that its bit 0 is 0 and all other
bits are left alone.
In assembly language, written *naively*, this would look like
this:
```asm
ClearA: ldrb w1, [x0] // 1
mov w2, 1 // 2
mvn w2, w2 // 3
and w1, w1, w2 // 4
strb w1, [x0] // 5
ret // 6
```
`x30` does not have to be backed up or restored as this function is a "leaf."
`Line 3` uses the instruction `mvn` to flip all the bits in `w2`.
This code completely tracks the C / C++ code.
We have no obligation to follow the C / C++ code exactly. Instead we
could write:
```asm
ClearA: ldrb w1, [x0] // 1
and w1, w1, 0xFE // 2
strb w1, [x0] // 3
ret // 4
```
Here, the `0xFE` literal takes the place of `lines 2 and 3` in the previous
version.
For setting the `a` bit, we would do this:
```c
void SetA(unsigned char * byte) {
*byte &= ~1;
*byte |= 1;
}
```
This is an anomaly for bit bashing. In almost all cases when setting
bit values, the bits must be cleared first because an *or* instruction
is responsible for setting any 1 bits to 1. In the case, it is a single
bit we're setting so we can just or it in.
In assembly language:
```asm
SetA: ldrb w1, [x0] // 1
orr w1, w1, 1 // 2
strb w1, [x0] // 3
ret // 4
```
`orr` is one of several or instructions in AARCH64. It is the one that maps
most closely to `|` in C and C++.
Moving onto the `b` field, things begin to get a little more interesting.
To clear the `b` field we might do this in C | C++.
```c
void ClearB(unsigned char * byte) {
*byte &= ~6;
}
```
void SetB(unsigned char * byte, unsigned char value) {
value &= 3; // ensures only bits 0 and 1 can be set
*byte &= ~6; // clears bits 1 and 2 in byte
*byte |= (value << 1); // stores bits 0 and 1 into bits 2 and 3
}
This could *naively* be written as:
```asm
ClearB: ldrb w1, [x0] // 1
mov w2, 6 // 2
mvn w2, w2 // 3
and w1, w1, w2 // 4
strb w1, [x0] // 5
ret // 6
```
This code is essentially the same as the *naive* version of `ClearA` given
above. Once again, we can pre-compute the results of `lines 2 and 3` to
make:
```asm
ClearB: ldrb w1, [x0] // 1
and w1, w1, 0xF9 // 2
strb w1, [x0] // 3
ret // 4
```
Turning to setting `b`, the code gets a little more complicated as for
the first time, we have to accept a parameter for the value to place into
`b`.
```c
void SetB(unsigned char * byte, unsigned char value) { // 1
value &= 3; // ensures only bits 0 and 1 can be set // 2
*byte &= ~6; // clears bits 1 and 2 in byte // 3
*byte |= (value << 1); // stores bits 0 and 1 into bits 2 and 3 // 4
} // 5
```
`Line 2` is necessary to prevent stray 1's from being or'ed into `*byte`.
`Line 3` is necessary to squash the existing target bits to zero prior
to being or'ed.
Notice `value` is being shifted left by 1 bit as the `b` field begins at
bit index 1.
In *naive* assembly language we could write this:
```asm
SetB: ldrb w3, [x0] // 1
and w1, w1, 3 // value &= 3 // 2
lsl w1, w1, 1 // 3
mov w2, 6 // 4
mvn w2, w2 // 5
and w3, w3, w2 // B is cleared // 6
orr w3, w3, w1 // 7
strb w3, [x0] // 8
ret // 9
```
The only interesting thing in this code as that we chose to perform the
left shift by one bit early in the code rather than later. There is no
side effect to changing this order.
`lsl` means "left shift logical" which fills the right side recently
vacated bits with zero.
```c
void ClearC(unsigned char * byte) {
*byte &= 7; // squashes bits 3 to 7 to 0
}

View file

@ -0,0 +1,58 @@
.global SetA
.global SetB
.global SetC
.global ClearA
.global ClearB
.global ClearC
.text
.align 2
ClearA: ldrb w1, [x0]
and w1, w1, 0xFE
strb w1, [x0]
ret
ClearB: ldrb w1, [x0]
and w1, w1, 0xF9
strb w1, [x0]
ret
ClearC: ldrb w1, [x0]
and w1, w1, 7
strb w1, [x0]
ret
SetA: ldrb w1, [x0]
orr w1, w1, 1
strb w1, [x0]
ret
SetB: ldrb w3, [x0]
and w1, w1, 3 // value &= 3
lsl w1, w1, 1
mov w2, 6
mvn w2, w2
and w3, w3, w2 // B is cleared
orr w3, w3, w1
strb w3, [x0]
ret
SetC: ldrb w3, [x0]
mov w2, 0x1F
and w1, w1, w2
lsl w1, w1, 3
lsl w2, w2, 3
mvn w2, w2
and w3, w3, w2
orr w3, w3, w1
strb w3, [x0]
ret
.end

View file

@ -0,0 +1,10 @@
SetB: ldrb w3, [x0]
and w1, w1, 3 // value &= 3
lsl w1, w1, 1
mov w2, 6
mvn w2, w2
and w3, w3, w2 // B is cleared
orr w3, w3, w1
strb w3, [x0]
ret

View file

@ -8,7 +8,7 @@ struct BF {
unsigned char noBF = 0;
#undef C
#define C
#ifdef C
/* Note the absence of defensive programming such as checking
to ensure that byte is not null and that bit_number is not

129
section_2/bitfields/test.s Normal file
View file

@ -0,0 +1,129 @@
.section __TEXT,__text,regular,pure_instructions
.build_version macos, 12, 0 sdk_version 12, 3
.globl _ClearA ; -- Begin function ClearA
.p2align 2
_ClearA: ; @ClearA
.cfi_startproc
; %bb.0:
ldrb w8, [x0]
and w8, w8, #0xfe
strb w8, [x0]
ret
.cfi_endproc
; -- End function
.globl _SetA ; -- Begin function SetA
.p2align 2
_SetA: ; @SetA
.cfi_startproc
; %bb.0:
ldrb w8, [x0]
orr w8, w8, #0x1
strb w8, [x0]
ret
.cfi_endproc
; -- End function
.globl _ClearB ; -- Begin function ClearB
.p2align 2
_ClearB: ; @ClearB
.cfi_startproc
; %bb.0:
ldrb w8, [x0]
and w8, w8, #0xfffffff9
strb w8, [x0]
ret
.cfi_endproc
; -- End function
.globl _SetB ; -- Begin function SetB
.p2align 2
_SetB: ; @SetB
.cfi_startproc
; %bb.0:
ldrb w8, [x0]
and w8, w8, #0xfffffff9
ubfiz w9, w1, #1, #2
orr w8, w8, w9
strb w8, [x0]
ret
.cfi_endproc
; -- End function
.globl _ClearC ; -- Begin function ClearC
.p2align 2
_ClearC: ; @ClearC
.cfi_startproc
; %bb.0:
ldrb w8, [x0]
and w8, w8, #0x7
strb w8, [x0]
ret
.cfi_endproc
; -- End function
.globl _SetC ; -- Begin function SetC
.p2align 2
_SetC: ; @SetC
.cfi_startproc
; %bb.0:
ldrb w8, [x0]
bfi w8, w1, #3, #8
strb w8, [x0]
ret
.cfi_endproc
; -- End function
.globl _main ; -- Begin function main
.p2align 2
_main: ; @main
.cfi_startproc
; %bb.0:
sub sp, sp, #48
stp x20, x19, [sp, #16] ; 16-byte Folded Spill
stp x29, x30, [sp, #32] ; 16-byte Folded Spill
add x29, sp, #32
.cfi_def_cfa w29, 16
.cfi_offset w30, -8
.cfi_offset w29, -16
.cfi_offset w19, -24
.cfi_offset w20, -32
ldrb w8, [sp, #15]
orr w8, w8, #0x1
strb w8, [sp, #15]
ldrb w8, [sp, #15]
and w8, w8, #0xfffffff9
orr w8, w8, #0x4
strb w8, [sp, #15]
ldrb w8, [sp, #15]
mov w19, #24
bfxil w19, w8, #0, #3
strb w19, [sp, #15]
mov w8, #58
adrp x9, _noBF@PAGE
strb w8, [x9, _noBF@PAGEOFF]
str x8, [sp]
Lloh0:
adrp x0, l_.str@PAGE
Lloh1:
add x0, x0, l_.str@PAGEOFF
bl _printf
str x19, [sp]
Lloh2:
adrp x0, l_.str.1@PAGE
Lloh3:
add x0, x0, l_.str.1@PAGEOFF
bl _printf
mov w0, #0
ldp x29, x30, [sp, #32] ; 16-byte Folded Reload
ldp x20, x19, [sp, #16] ; 16-byte Folded Reload
add sp, sp, #48
ret
.loh AdrpAdd Lloh2, Lloh3
.loh AdrpAdd Lloh0, Lloh1
.cfi_endproc
; -- End function
.globl _noBF ; @noBF
.zerofill __DATA,__common,_noBF,1,0
.section __TEXT,__cstring,cstring_literals
l_.str: ; @.str
.asciz "noBF should be 0x3A - value: 0x%X\n"
l_.str.1: ; @.str.1
.asciz "bf should be 0x1D - value: 0x%X\n"
.subsections_via_symbols