summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCliff Click <cliffc@acm.org>2025-07-03 09:50:14 -0700
committerCliff Click <cliffc@acm.org>2025-07-03 09:50:14 -0700
commitacd05ca485baa282e05e73f9ca61ec91b055ab7c (patch)
tree6e19847131f84f6d47940bb0e1c08980c1e4c854
parent0154c739b94d842898c36566c24e0f8b6d133951 (diff)
downloadSimple-acd05ca485baa282e05e73f9ca61ec91b055ab7c.tar.gz
Simple-acd05ca485baa282e05e73f9ca61ec91b055ab7c.zip
Update README.md
-rw-r--r--chapter21/README.md668
1 files changed, 339 insertions, 329 deletions
diff --git a/chapter21/README.md b/chapter21/README.md
index ec47ac3b..c5b41bc7 100644
--- a/chapter21/README.md
+++ b/chapter21/README.md
@@ -27,17 +27,16 @@ You can also read [this chapter](https://github.com/SeaOfNodes/Simple/tree/linea
1. [Instruction Encodings](#instruction-encodings)
2. [Infrastructure](#infrastructure)
-3. [AMD64](#amd64)
- - [REX PREFIX](#rex-prefix)
- - [Large constants](#large-constants)
- - [MODR/M](#modrm)
- - [SIB](#sib)
- - [Displacement](#displacement)
- - [Immediate](#immediate)
+3. [Endianness](#endianness)
+4. [RISCV](#riscv)
+ - [Instruction formats](#instruction-formats)
- [Float](#float)
- - [Indirect](#indirectmemop)
- - [Conditional flags](#conditional-flags)
-4. [ARM](#arm)
+ - [Indirect](#indirectmemop-2)
+ - [Branch](#branch)
+ - [Function Constant](#function-constant)
+ - [LUI](#lui)
+ - [Large constants](#large-constants)
+5. [ARM](#arm)
- [Reg-form](#reg-form)
- [Shift immediates](#shift-immediates)
- [asri](#asrimmediate)
@@ -53,15 +52,17 @@ You can also read [this chapter](https://github.com/SeaOfNodes/Simple/tree/linea
- [Branching](#branching-)
- [Rip relative](#rip-relative)
- [Compare](#compare)
-
-5. [RISCV](#riscv)
- - [Instruction formats](#instruction-formats)
- - [Float](#float)
- - [Indirect](#indirectmemop-2)
- - [Branch](#branch)
- - [Function Constant](#function-constant)
- - [LUI](#lui)
+6. [AMD64](#amd64)
+ - [REX PREFIX](#rex-prefix)
- [Large constants](#large-constants)
+ - [MODR/M](#modrm)
+ - [SIB](#sib)
+ - [Displacement](#displacement)
+ - [Immediate](#immediate)
+ - [Float](#float)
+ - [Indirect](#indirectmemop)
+ - [Conditional flags](#conditional-flags)
+
This chapter will add instruction encodings. Instruction encoding refers to
the representation of machine instructions in a specific binary format defined
@@ -75,9 +76,6 @@ To avoid ambiguity, we define the sources of the information we use to encode t
Currently, we support these architectures:
-### AMD64:
-For *x86-64(amd64):*, we use the latest Intel manual for the [encoding rules](https://www.felixcloutier.com/x86/).
-
### RISC-V:
For *riscv* we currently target [RVA23U64](https://msyksphinz-self.github.io/riscv-isadoc/html/).
@@ -86,7 +84,10 @@ For *riscv* we currently target [RVA23U64](https://msyksphinz-self.github.io/ris
For *arm*(aarch64) we use this collection for the [encoding rules](https://docsmirror.github.io/A64/20a23-06/index.html).
*Note*: We only support 64 bits ARM encodings.
----
+### AMD64:
+For *x86-64(amd64):*, we use the latest Intel manual for the [encoding rules](https://www.felixcloutier.com/x86/).
+
+--------------------------------
## Instruction Encodings
@@ -150,266 +151,134 @@ to the right, we can bring the next byte into the least significant position,
allowing us to write each byte in sequence.
-## AMD64 X86-64
-
-As opposed to riscv arhitectures, where the instruction width is fixed,
-*x86-64* has variable width instructions. This is common with *CISC (Complex
-Instruction Set Computer)* architectures, where the instruction width can vary
-from 1 to 15 bytes. Since AMD64 supports many indirect addressing modes, the
-goal with *CISC* in general is to complete a task in as few lines of assembly
-as possible.
-
-### REX PREFIX
-
-Since we are targeting the 64-bit version of x86, we need to handle this
-prefix. In 32-bit mode, however, it is typically unnecessary.
-
-Generally speaking the *REX* prefix must be encoded when:
- - using one of the extended registers (R8 to R15, XMM8 to XMM15, YMM8 to YMM15, CR8 to CR15 and DR8 to DR15);
- - using 64-bit operand size and the instruction does not default to 64-bit operand size(most ALU ops)
- - using a 8-bit operand with RSI, RDI, RBP
-
-A *REX* prefix must not be encoded when:
- - using one of the high byte registers AH, CH, BH or DH (not done currently in Simple)
+--------------------------------
-*Note:* When encoding SSE instruction, the *REX* prefix (0x40) must come after
-the `SSE` prefix. In all other cases, it is ignored.
+## RISCV
-In Simple, the REX prefix is just appended to the beginning of the bit stream
-(except SEE float instructions). The layout is the following:
+RISCV instructions are all 32 bits wide, and are always little-endian. RISCV
+is a RISC architecture, it prioritizes "register-to-register" forms, and has
+very regular encodings. We present RISCV first because it is the simplest
+encoding.
-| **Field** | **Length** | **Description** |
-|:---------:|:----------:|---------------------------------------------------------------------------------|
-| `0100` | 4 bits | Fixed bit pattern |
-| `W` | 1 bit | When 1, a 64-bit operand size is used. Otherwise, 32-bit operand size is used. |
-| `R` | 1 bit | Extension to the **MODRM.reg** field. |
-| `X` | 1 bit | Extension to the **SIB.index** field. |
-| `B` | 1 bit | Extension to the **MODRM.rm** or **SIB.base** field. |
+#### Instruction formats
+##### R-TYPE
+This encoding format is used for reg-to-reg operations.
+Such as `AddRISC`, `MulRISC` etc.
```java
- public static int REX_W = 0x48;
-```
-
-```java
-public static int rex(int reg, int ptr, int idx, boolean wide) {
- // assuming 64 bit by default so: 0100 1000
- int rex = wide ? REX_W : REX;
- if( 8 <= reg && reg <= 15 ) rex |= 0b00000100; // REX.R
- if( 8 <= ptr && ptr <= 15 ) rex |= 0b00000001; // REX.B
- if( 8 <= idx && idx <= 15 ) rex |= 0b00000010; // REX.X
- return rex;
+public static int r_type(int opcode, int rd, int func3, int rs1, int rs2, int func7) {
+ return (func7 << 25) | (rs2 << 20) | (rs1 << 15) | (func3 << 12) | (rd << 7) | opcode;
}
-
- ...
- enc.add1(x86_64_v2.rex(dst, src, 0));
-```
-
-Setting THE `W` bit gives us:
-`0b01001000 = 0x48;`
-
-#### Opcode
-In our case, the opcode is a 1 byte field that specifies the operation to be performed.
-```
-enc.add1(opcode()); // opcode
```
-#### MODR/M
-
-| **Field** | **Length** | **Description** |
-|:-------------:|:----------:|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
-| **MODRM.mod** | 2 bits | In general, when this field is `b11`, then register-direct addressing mode is used; otherwise register-indirect addressing mode is used.
-| **MODRM.reg** | 3 bits | This field can have one of two values:<br><br>• A 3-bit opcode extension, which is used by some instructions but has no further meaning other than distinguishing the instruction from other instructions.<br><br>• A 3-bit register reference, which can be used as the source or the destination of an instruction (depending on the instruction). The referenced register depends on the operand-size of the instruction and the instruction itself. The `REX.R`, `VEX.~R` or `XOP.~R` field can extend this field with 1 most-significant bit to 4 bits total. |
-| **MODRM.rm** | 3 bits | Specifies a direct or indirect register operand, optionally with a displacement. The `REX.B`, `VEX.~B` or `XOP.~B` field can extend this field with 1 most-significant bit to 4 bits total. |
-
-
-We deal with the MODR/M byte in a similar way:
-
-The first thing we need for the modrm byte is the 2 bits mod.
-
-##### mod
+##### I-TYPE
+This layout is used for immediate formats. Such as `AddIRISC`
-The layout for mod is the following:
-```
-public enum MOD {
- INDIRECT, // [mem]
- INDIRECT_disp8, // [mem + 0x12]
- INDIRECT_disp32,// [mem + 0x12345678]
- DIRECT, // mem
-};
+```java
+ public static int i_type(int opcode, int rd, int func3, int rs1, int imm12) {
+ assert opcode >= 0 && rd >=0 && func3 >=0 && rs1 >=0 && imm12 >= 0; // Zero-extend by caller
+ return (imm12 << 20) | (rs1 << 15) | (func3 << 12) | (rd << 7) | opcode;
+ }
```
-##### reg
-The reg field is a 3 bit field capable of encoding 15 different registers.
-
-##### r/m
-
-The rm field specifies a direct or indirect register operand and is capable of encoding 15 different registers.
+##### S-TYPE
+This encoding format is used for `StoreRISC`.
```java
-public static int modrm(MOD mod, int reg, int m_r) {
- // combine all the bits
- return (mod.ordinal() << 6) | ((reg & 0x07) << 3) | m_r & 0x07;
-}
-```
-
-The `modrm` function is then used to build up the `ModR/M` byte.
-Usually this follows after the opcode.
-
-```
-enc.add1(x86_64_v2.modrm(x86_64_v2.MOD.DIRECT, dst, src));
-```
-
-Encoding a reg form might look like this:
-```
-short dst = enc.reg(this ); // src1
-short src = enc.reg(in(2)); // src2
-enc.add1(x86_64_v2.rex(dst, src, 0));
-enc.add1(opcode()); // opcode
-enc.add1(x86_64_v2.modrm(x86_64_v2.MOD.DIRECT, dst, src));
-```
-
-#### SIB
-The SIB byte has the following fields:
-
-| **Field** | **Length** | **Description** |
-|:-------------:|:----------:|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
-| **SIB.scale** | 2 bits | This field indicates the scaling factor of `SIB.index`, where **s** (as used in the tables) equals 2^SIB.scale^. |
-| **SIB.index** | 3 bits | The index register to use. See Registers for the values to use for each of the registers. The `REX.X`, `VEX.~X` or `XOP.~X` field can extend this field with 1 most-significant bit to 4 bits total. |
-| **SIB.base** | 3 bits | The base register to use. See Registers for the values to use for each of the registers. The `REX.B`, `VEX.~B` or `XOP.~B` field can extend this field with 1 most-significant bit to 4 bits total. |
-
-It has the same layout as the MODR/M byte.
-```java
-public static int sib(int scale, int index, int base) {
- return (scale << 6) | ((index & 0x07) << 3) | base & 0x07;
+public static int s_type(int opcode, int func3, int rs1, int rs2, int imm12) {
+ assert imm12 >= 0; // Masked to high zero bits by caller
+ int imm_lo = imm12 & 0x1F;
+ int imm_hi = imm12 >> 5;
+ return (imm_hi << 25) | (rs2 << 20) | (rs1 << 15) | (func3 << 12) | (imm_lo << 7) | opcode;
}
```
-```java
-enc.add1(x86_64_v2.sib(_scale, idx, x86_64_v2.RBP));
-```
+##### B-TYPE
-#### Displacement
-Displacement can happen in 2 forms:
+This encoding layout is used by `BranchRISC`.
```java
-public enum MOD {
-INDIRECT, // [mem]
-INDIRECT_disp8, // [mem + 0x12]
-INDIRECT_disp32,// [mem + 0x12345678]
-DIRECT, // mem
-};
+ // immf = first imm
+ // immd = second imm
+ // BRANCH
+public static int b_type(int opcode, int immf, int func3, int rs1, int rs2, int immd) {
+ return (immd << 25 ) | (rs2 << 20) | (rs1 << 15) | (func3 << 12) | (immf << 7) | opcode;
+ }
```
-```java
-if( mod == MOD.INDIRECT_disp8 ) {
- enc.add1(offset);
-} else if( mod == MOD.INDIRECT_disp32 ) {
- enc.add4(offset);
-}
-```
-The displacement goes before the immediate, so for example:
-```
-sub [eax + 2], 4
-```
-```
-bytes.write(0x83);
-bytes.write(modrm(INDIRECT8, 5, 0 /* eax */));
-bytes.write(2); // displcement goes first
-bytes.write(4);
-```
+##### U-TYPE
+This layout is used for `AUIPC` and `LUI`(upper immediate is 20-bits).
-#### Immediate
-The immediate byte/bytes go after the `ModR/M` byte, we are capable of encoding 4 bytes of immediates depending on the need.
-```java
-// immediate(4 bytes) 32 bits or (1 byte)8 bits
-if( x86_64_v2.imm8(_imm) ) enc.add1(_imm);
-else enc.add4(_imm);
+```java
+ public static int u_type(int opcode, int rd, int imm20) {
+ return (imm20 << 12) | (rd << 7) | opcode;
+ }
```
-#### INDIRECT(MemOp)
-X86-64 support multiple indirect addressing modes:
-
-
-#### Float
-For float operations we use `SEE SIMD` instructions that operate on scalar single precision floating0-point values located in the
-XMM registers or memory.
-*Note*: When using XMM registers for ALU operations, the encoding is really similar with the exception that we need
-to subtract the `XMM_OFFSET` from the register number.
-We count the registers continuously, so we need to subtract the offset to get the GPR register pair that corresponds to the XMM register.
-
+##### J-TYPE
+Used for unconditional jumps. (`UJmpRISC`)
```java
-short dst = (short)(enc.reg(this ) - x86_64_v2.XMM_OFFSET);
+ public static int j_type(int opcode, int rd, int imm20) {
+ return imm20 << 12 | rd << 7 | opcode;
+ }
```
-```java
-public static int XMM_OFFSET = 16;
+#### FLOAT
+For loading float constants, we use indirect memory load(PC-relative).
+```
+AUIPC dst,#hi20_constant_pool
+Load dst,[dst+#low12_constant_pool]
```
-| **X.Reg** | **8-bit GP** | **16-bit GP** | **32-bit GP** | **64-bit GP** | **80-bit x87** | **64-bit MMX** | **128-bit XMM** | **256-bit YMM** | **16-bit Segment** | **32-bit Control** | **32-bit Debug** |
-|:--------:|:------------:|:-------------:|:-------------:|:-------------:|:-------------:|:-------------:|:--------------:|:--------------:|:-----------------:|:-----------------:|:---------------:|
-| `0.000 (0)` | AL | AX | EAX | RAX | ST0 | MMX0 | XMM0 | YMM0 | ES | CR0 | DR0 |
-
-We see that `XMM0` corresponds to `RAX` and so on.
-
-#### Large constants
-
-##### INT: We just use `MOV r64, imm64` instruction form.
+#### INDIRECT(MemOp)
-##### FLOAT: We use relocation and use constant from memory.
+[Load](https://msyksphinz-self.github.io/riscv-isadoc/html/rvi.html#lw): `lw rd,offset(rs1)`
-#### Function constant
-To load a function constant we use lea which will load the address into the specified reg
-relative to the instruction pointer.
-`lea rax, [rip+disp32]`
+[Store](https://msyksphinz-self.github.io/riscv-isadoc/html/rvi.html#sw): `sw rs2,offset(rs1)`
-#### Conditional flags:
-The FLAGS register is the status register that contains the current state of an x86 CPU.
+#### BRANCH
+RISC-V is unusual because branch instructions include the comparison and branch target in one instruction.
-Let's consider a simple example that presents all the instructions that we need to encode for
-conditional code.
+#### Immediates
+We only encode the imm form for the *ALU* operations if they fit into 12 bits (signed).
+Otherwise we do a Load Upper Immediate (high 20 bits) or both.
```
-int square(int a) {
- // cmp DWORD PTR [rbp-0x8],0x1
- // `sete al`
- bool da = a == 1;
-
- // cmp eax,0x1
- if(da == 1) {
- return 1;
- }
- return 2;
-}
+addiw a5,a5,123
```
-
-First we need a compare instruction comparing `a` with 1.
+vs
+(do it with extra load):
```
-cmp DWORD PTR [rbp-0x8],0x1
+addw a5,a5,a4
```
-This compare instruction then sets the conditional flags:
-> The CF, OF, SF, ZF, AF, and PF flags are set according to the result.
-
-We then use the `sete` instruction to save out the result of the comparison to a register:
+### FLOAT Constants
+- Stored in the **constant pool**.
+- Accessed via **PC-relative loads**.
-`sete al`
+### INT Constants
+- **12-bit constants**
+ - Use `ADDI`
+- **20-bit constants**
+ - Use `LUI`
+- **32-bit constants**
+ - Use `LUI` + `ADDI` combo
+ -
-For the if condition we do an extra comparison based on the result of the first bool.
-```
- cmp eax,0x1
-```
+#### Function Constants
+In our RISC-V code, when we need to load a function-local constant, we store
+the current PC into a register and use that as a base to access the
+constant. However, we usually try to avoid loads, since simple arithmetic like
+addition is faster and more efficient than accessing memory.
-And then a conditional branch that is relying on the flags set by the comparison(`a == 1`).
+```java
+// auipc t0,0
+// addi t1,t0 + #0
```
-jne 1159 <square(int)+0x29>
-```
-
-We'll see how this differs in ARM and RISCV.
-
----
+--------------------------------
## ARM
Arm instructions are all 32 bits wide, and are always little-endian.
@@ -588,126 +457,266 @@ If the offset is not a con
> cond = is one of the standard conditions.
----
-## RISCV
-RISCV instructions are all 32 bits wide, and are always little-endian.
-RISCV is a RISC architecture, it prioritizes "register-to-register" forms.
+--------------------------------
+
+## AMD64 X86-64
+
+As opposed to riscv arhitectures, where the instruction width is fixed,
+*x86-64* has variable width instructions. This is common with *CISC (Complex
+Instruction Set Computer)* architectures, where the instruction width can vary
+from 1 to 15 bytes. Since AMD64 supports many indirect addressing modes, the
+goal with *CISC* in general is to complete a task in as few lines of assembly
+as possible.
+
+### REX PREFIX
+
+Since we are targeting the 64-bit version of x86, we need to handle this
+prefix. In 32-bit mode, however, it is typically unnecessary.
+
+Generally speaking the *REX* prefix must be encoded when:
+ - using one of the extended registers (R8 to R15, XMM8 to XMM15, YMM8 to YMM15, CR8 to CR15 and DR8 to DR15);
+ - using 64-bit operand size and the instruction does not default to 64-bit operand size(most ALU ops)
+ - using a 8-bit operand with RSI, RDI, RBP
+
+A *REX* prefix must not be encoded when:
+ - using one of the high byte registers AH, CH, BH or DH (not done currently in Simple)
+
+*Note:* When encoding SSE instruction, the *REX* prefix (0x40) must come after
+the `SSE` prefix. In all other cases, it is ignored.
+
+In Simple, the REX prefix is just appended to the beginning of the bit stream
+(except SEE float instructions). The layout is the following:
+
+| **Field** | **Length** | **Description** |
+|:---------:|:----------:|---------------------------------------------------------------------------------|
+| `0100` | 4 bits | Fixed bit pattern |
+| `W` | 1 bit | When 1, a 64-bit operand size is used. Otherwise, 32-bit operand size is used. |
+| `R` | 1 bit | Extension to the **MODRM.reg** field. |
+| `X` | 1 bit | Extension to the **SIB.index** field. |
+| `B` | 1 bit | Extension to the **MODRM.rm** or **SIB.base** field. |
-#### Instruction formats
-##### R-TYPE
-This encoding format is used for reg-to-reg operations.
-Such as `AddRISC`, `MulRISC` etc.
```java
-public static int r_type(int opcode, int rd, int func3, int rs1, int rs2, int func7) {
- return (func7 << 25) | (rs2 << 20) | (rs1 << 15) | (func3 << 12) | (rd << 7) | opcode;
+ public static int REX_W = 0x48;
+```
+
+```java
+public static int rex(int reg, int ptr, int idx, boolean wide) {
+ // assuming 64 bit by default so: 0100 1000
+ int rex = wide ? REX_W : REX;
+ if( 8 <= reg && reg <= 15 ) rex |= 0b00000100; // REX.R
+ if( 8 <= ptr && ptr <= 15 ) rex |= 0b00000001; // REX.B
+ if( 8 <= idx && idx <= 15 ) rex |= 0b00000010; // REX.X
+ return rex;
}
+
+ ...
+ enc.add1(x86_64_v2.rex(dst, src, 0));
```
-##### I-TYPE
-This layout is used for immediate formats. Such as `AddIRISC`
+Setting THE `W` bit gives us:
+`0b01001000 = 0x48;`
-```java
- public static int i_type(int opcode, int rd, int func3, int rs1, int imm12) {
- assert opcode >= 0 && rd >=0 && func3 >=0 && rs1 >=0 && imm12 >= 0; // Zero-extend by caller
- return (imm12 << 20) | (rs1 << 15) | (func3 << 12) | (rd << 7) | opcode;
- }
+#### Opcode
+In our case, the opcode is a 1 byte field that specifies the operation to be performed.
+```
+enc.add1(opcode()); // opcode
```
-##### S-TYPE
+#### MODR/M
+
+| **Field** | **Length** | **Description** |
+|:-------------:|:----------:|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| **MODRM.mod** | 2 bits | In general, when this field is `b11`, then register-direct addressing mode is used; otherwise register-indirect addressing mode is used.
+| **MODRM.reg** | 3 bits | This field can have one of two values:<br><br>• A 3-bit opcode extension, which is used by some instructions but has no further meaning other than distinguishing the instruction from other instructions.<br><br>• A 3-bit register reference, which can be used as the source or the destination of an instruction (depending on the instruction). The referenced register depends on the operand-size of the instruction and the instruction itself. The `REX.R`, `VEX.~R` or `XOP.~R` field can extend this field with 1 most-significant bit to 4 bits total. |
+| **MODRM.rm** | 3 bits | Specifies a direct or indirect register operand, optionally with a displacement. The `REX.B`, `VEX.~B` or `XOP.~B` field can extend this field with 1 most-significant bit to 4 bits total. |
+
+
+We deal with the MODR/M byte in a similar way:
+
+The first thing we need for the modrm byte is the 2 bits mod.
+
+##### mod
+
+The layout for mod is the following:
+```
+public enum MOD {
+ INDIRECT, // [mem]
+ INDIRECT_disp8, // [mem + 0x12]
+ INDIRECT_disp32,// [mem + 0x12345678]
+ DIRECT, // mem
+};
+```
+
+##### reg
+The reg field is a 3 bit field capable of encoding 15 different registers.
+
+##### r/m
+
+The rm field specifies a direct or indirect register operand and is capable of encoding 15 different registers.
-This encoding format is used for `StoreRISC`.
```java
-public static int s_type(int opcode, int func3, int rs1, int rs2, int imm12) {
- assert imm12 >= 0; // Masked to high zero bits by caller
- int imm_lo = imm12 & 0x1F;
- int imm_hi = imm12 >> 5;
- return (imm_hi << 25) | (rs2 << 20) | (rs1 << 15) | (func3 << 12) | (imm_lo << 7) | opcode;
+public static int modrm(MOD mod, int reg, int m_r) {
+ // combine all the bits
+ return (mod.ordinal() << 6) | ((reg & 0x07) << 3) | m_r & 0x07;
}
```
-##### B-TYPE
+The `modrm` function is then used to build up the `ModR/M` byte.
+Usually this follows after the opcode.
-This encoding layout is used by `BranchRISC`.
-```java
- // immf = first imm
- // immd = second imm
- // BRANCH
-public static int b_type(int opcode, int immf, int func3, int rs1, int rs2, int immd) {
- return (immd << 25 ) | (rs2 << 20) | (rs1 << 15) | (func3 << 12) | (immf << 7) | opcode;
- }
+```
+enc.add1(x86_64_v2.modrm(x86_64_v2.MOD.DIRECT, dst, src));
```
-##### U-TYPE
-This layout is used for `AUIPC` and `LUI`(upper immediate is 20-bits).
+Encoding a reg form might look like this:
+```
+short dst = enc.reg(this ); // src1
+short src = enc.reg(in(2)); // src2
+enc.add1(x86_64_v2.rex(dst, src, 0));
+enc.add1(opcode()); // opcode
+enc.add1(x86_64_v2.modrm(x86_64_v2.MOD.DIRECT, dst, src));
+```
+
+#### SIB
+The SIB byte has the following fields:
+
+| **Field** | **Length** | **Description** |
+|:-------------:|:----------:|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| **SIB.scale** | 2 bits | This field indicates the scaling factor of `SIB.index`, where **s** (as used in the tables) equals 2^SIB.scale^. |
+| **SIB.index** | 3 bits | The index register to use. See Registers for the values to use for each of the registers. The `REX.X`, `VEX.~X` or `XOP.~X` field can extend this field with 1 most-significant bit to 4 bits total. |
+| **SIB.base** | 3 bits | The base register to use. See Registers for the values to use for each of the registers. The `REX.B`, `VEX.~B` or `XOP.~B` field can extend this field with 1 most-significant bit to 4 bits total. |
+It has the same layout as the MODR/M byte.
```java
- public static int u_type(int opcode, int rd, int imm20) {
- return (imm20 << 12) | (rd << 7) | opcode;
- }
+public static int sib(int scale, int index, int base) {
+ return (scale << 6) | ((index & 0x07) << 3) | base & 0x07;
+}
```
-##### J-TYPE
-Used for unconditional jumps. (`UJmpRISC`)
-```java
- public static int j_type(int opcode, int rd, int imm20) {
- return imm20 << 12 | rd << 7 | opcode;
- }
+```java
+enc.add1(x86_64_v2.sib(_scale, idx, x86_64_v2.RBP));
```
-#### FLOAT
-For loading float constants, we use indirect memory load(PC-relative).
+#### Displacement
+Displacement can happen in 2 forms:
+```java
+public enum MOD {
+INDIRECT, // [mem]
+INDIRECT_disp8, // [mem + 0x12]
+INDIRECT_disp32,// [mem + 0x12345678]
+DIRECT, // mem
+};
+```
+
+```java
+if( mod == MOD.INDIRECT_disp8 ) {
+ enc.add1(offset);
+} else if( mod == MOD.INDIRECT_disp32 ) {
+ enc.add4(offset);
+}
+```
+The displacement goes before the immediate, so for example:
```
-AUIPC dst,#hi20_constant_pool
-Load dst,[dst+#low12_constant_pool]
+sub [eax + 2], 4
+```
+```
+bytes.write(0x83);
+bytes.write(modrm(INDIRECT8, 5, 0 /* eax */));
+bytes.write(2); // displcement goes first
+bytes.write(4);
```
-#### INDIRECT(MemOp)
+#### Immediate
+The immediate byte/bytes go after the `ModR/M` byte, we are capable of encoding 4 bytes of immediates depending on the need.
+```java
+// immediate(4 bytes) 32 bits or (1 byte)8 bits
+if( x86_64_v2.imm8(_imm) ) enc.add1(_imm);
+else enc.add4(_imm);
+```
-[Load](https://msyksphinz-self.github.io/riscv-isadoc/html/rvi.html#lw): `lw rd,offset(rs1)`
+#### INDIRECT(MemOp)
+X86-64 support multiple indirect addressing modes:
-[Store](https://msyksphinz-self.github.io/riscv-isadoc/html/rvi.html#sw): `sw rs2,offset(rs1)`
-#### BRANCH
-RISC-V is unusual because branch instructions include the comparison and branch target in one instruction.
+#### Float
+For float operations we use `SEE SIMD` instructions that operate on scalar single precision floating0-point values located in the
+XMM registers or memory.
+*Note*: When using XMM registers for ALU operations, the encoding is really similar with the exception that we need
+to subtract the `XMM_OFFSET` from the register number.
+We count the registers continuously, so we need to subtract the offset to get the GPR register pair that corresponds to the XMM register.
-#### Immediates
-We only encode the imm form for the *ALU* operations if they fit into 12 bits (signed).
-Otherwise we do a Load Upper Immediate (high 20 bits) or both.
+```java
+short dst = (short)(enc.reg(this ) - x86_64_v2.XMM_OFFSET);
+```
+```java
+public static int XMM_OFFSET = 16;
```
-addiw a5,a5,123
+
+| **X.Reg** | **8-bit GP** | **16-bit GP** | **32-bit GP** | **64-bit GP** | **80-bit x87** | **64-bit MMX** | **128-bit XMM** | **256-bit YMM** | **16-bit Segment** | **32-bit Control** | **32-bit Debug** |
+|:--------:|:------------:|:-------------:|:-------------:|:-------------:|:-------------:|:-------------:|:--------------:|:--------------:|:-----------------:|:-----------------:|:---------------:|
+| `0.000 (0)` | AL | AX | EAX | RAX | ST0 | MMX0 | XMM0 | YMM0 | ES | CR0 | DR0 |
+
+We see that `XMM0` corresponds to `RAX` and so on.
+
+#### Large constants
+
+##### INT: We just use `MOV r64, imm64` instruction form.
+
+##### FLOAT: We use relocation and use constant from memory.
+
+#### Function constant
+To load a function constant we use lea which will load the address into the specified reg
+relative to the instruction pointer.
+`lea rax, [rip+disp32]`
+
+#### Conditional flags:
+The FLAGS register is the status register that contains the current state of an x86 CPU.
+
+Let's consider a simple example that presents all the instructions that we need to encode for
+conditional code.
+
```
-vs
-(do it with extra load):
+int square(int a) {
+ // cmp DWORD PTR [rbp-0x8],0x1
+ // `sete al`
+ bool da = a == 1;
+
+ // cmp eax,0x1
+ if(da == 1) {
+ return 1;
+ }
+ return 2;
+}
```
-addw a5,a5,a4
+
+First we need a compare instruction comparing `a` with 1.
+```
+cmp DWORD PTR [rbp-0x8],0x1
```
-### FLOAT Constants
-- Stored in the **constant pool**.
-- Accessed via **PC-relative loads**.
+This compare instruction then sets the conditional flags:
+> The CF, OF, SF, ZF, AF, and PF flags are set according to the result.
-### INT Constants
-- **12-bit constants**
- - Use `ADDI`
-- **20-bit constants**
- - Use `LUI`
-- **32-bit constants**
- - Use `LUI` + `ADDI` combo
- -
+We then use the `sete` instruction to save out the result of the comparison to a register:
-#### Function Constant
-In our RISC-V code, when we need to load a function-local constant,
-we store the current PC into a register and use that as a base to access the constant. However, we usually try to avoid loads, since simple arithmetic like addition is faster and more efficient than accessing memory.
+`sete al`
-```java
-// auipc t0,0
-// addi t1,t0 + #0
+For the if condition we do an extra comparison based on the result of the first bool.
+```
+ cmp eax,0x1
```
+And then a conditional branch that is relying on the flags set by the comparison(`a == 1`).
+```
+jne 1159 <square(int)+0x29>
+```
+
+
+--------------------------------
## Relocation
@@ -720,21 +729,22 @@ refers to other code and data; `call` instructions target subroutines,
### Local Relocation
Local relocation patches up local, or self-referential, code; the most common
-form is branch targets. When writing instructions into the `BAOS` (bit stream)
-forwards branches towards future instructions have an unknown offset - the
-offset depends on the size of the encodings in between the branch and target.
-For `riscv` and `arm` targets this sounds easy (and instructions are 4 bytes,
-so just count instructions)... except that some encodings use multiple
-instructions so are not actually 4 bytes long. For X86's wild psuedo-random
-instruction lengths the act of figuring out the opcode length is just as hard
-as writing the opcode in the first place.
-
-Alson X86 adds another wrinkle: short and long form branches. If a branch
-target lies within a signed byte range (+/-127) X86 will use a 2 byte branch
-encoding, otherwise a 6 byte encoding. Simple includes a pass to compact these
-branches and use 2 bytes wherever possible - but this pass has to slide code
-around to make space for the extra 4 bytes as needed, which changes all the
-other branch offsets.
+form is branch targets. When writing instructions into the `BAOS` (Byte Array
+Output Stream) forwards branches towards future instructions have an unknown
+offset - the offset depends on the size of the encodings in between the branch
+and target. For `riscv` and `arm` targets this sounds easy (and instructions
+are 4 bytes, so just count instructions)... except that some encodings use
+multiple instructions so are not actually 4 bytes long. For X86's wild
+psuedo-random instruction lengths the act of figuring out the opcode length is
+just as hard as writing the opcode in the first place.
+
+X86 adds another wrinkle: short and long form branches. If a branch target
+lies within a signed byte range (+/-127) X86 will use a 2 byte branch encoding,
+otherwise a 6 byte encoding. Simple includes a pass to compact these branches
+and use 2 bytes wherever possible - but this pass has to slide code around
+which changes all the other branch offsets. The "compact" pass actually starts
+optimistically assuming all branches can use the 2 byte form, and then expands
+branches as needed (so really *expands* the code).
Once the code is finally settled into "shape", a local relocation pass is made
to update all the self-references. Basically, the branch offsets in the byte
@@ -789,7 +799,7 @@ varies according to placement - same as for calls crossing compilation units.
Also large constants can be shared across compilation units (one can imagine
repeated uses of `pi` in HPC codes); sharing reduces cache footprint. In any
-case, one the locations of the code and constants are known, the code needs to
+case, once the locations of the code and constants are known, the code needs to
be patched to load at the correct offset. Like the `call` example above, the
load instruction variant and patch variant all need to agree. An X86 might use
either a 4-byte address or an 8-byte address depending on X86 variant; the