summaryrefslogtreecommitdiff
path: root/isa
diff options
context:
space:
mode:
authorDibyendu Majumdar <mobile@majumdar.org.uk>2023-08-19 08:52:57 -0700
committerDibyendu Majumdar <mobile@majumdar.org.uk>2026-10-08 16:52:27 -0700
commit4947515d6133ddbd0489c395024f4c1ddf7a499c (patch)
treedbc3d95a6c2a2a6d9e447ed14228e6b5c48b5cec /isa
downloadSimple-4947515d6133ddbd0489c395024f4c1ddf7a499c.tar.gz
Simple-4947515d6133ddbd0489c395024f4c1ddf7a499c.zip
Chapter 1: Introduction
Co-authored-by: Cliff Click <cliffc@acm.org> Co-authored-by: Thalia Archibald <thalia@archibald.dev> Co-authored-by: hadha <helsgithub15@gmail.com> Co-authored-by: XmiliaH <45106915+XmiliaH@users.noreply.github.com> Co-authored-by: Tamás Hadházy <85063808+Hels15@users.noreply.github.com> Co-authored-by: Jun Kawasaki <jun784@gmail.com>
Diffstat (limited to 'isa')
-rw-r--r--isa/Makefile16
-rw-r--r--isa/README.md79
-rw-r--r--isa/isa.iml18
-rw-r--r--isa/isa.mk5
-rw-r--r--isa/src/main/java/com/seaofnodes/isa/Arm64.java405
-rw-r--r--isa/src/main/java/com/seaofnodes/isa/CodeSink.java9
-rw-r--r--isa/src/main/java/com/seaofnodes/isa/RiscV.java127
-rw-r--r--isa/src/main/java/com/seaofnodes/isa/X86.java272
-rw-r--r--isa/src/test-support/isa-eval.iml13
-rw-r--r--isa/src/test-support/java/com/seaofnodes/isa/eval/EvalArm64.java790
-rw-r--r--isa/src/test-support/java/com/seaofnodes/isa/eval/EvalRisc5.java399
-rw-r--r--isa/src/test/java/com/seaofnodes/isa/RiscTest.java122
-rw-r--r--isa/src/test/java/com/seaofnodes/isa/X86Test.java128
13 files changed, 2383 insertions, 0 deletions
diff --git a/isa/Makefile b/isa/Makefile
new file mode 100644
index 00000000..b5a3f7b2
--- /dev/null
+++ b/isa/Makefile
@@ -0,0 +1,16 @@
+SHELL := /bin/bash
+.DEFAULT_GOAL := tests
+.DELETE_ON_ERROR:
+main_javas := $(wildcard src/main/java/com/seaofnodes/isa/*.java)
+test_javas := $(wildcard src/test/java/com/seaofnodes/isa/*.java)
+eval_javas := $(wildcard src/test-support/java/com/seaofnodes/isa/eval/*.java)
+
+.PHONY: tests
+tests: build/classes/.tag
+ java -ea -cp build/classes com.seaofnodes.isa.X86Test
+ java -ea -cp build/classes com.seaofnodes.isa.RiscTest
+
+build/classes/.tag: $(main_javas) $(test_javas) $(eval_javas)
+ @mkdir -p build/classes
+ javac -d build/classes $(main_javas) $(test_javas) $(eval_javas)
+ @touch $@
diff --git a/isa/README.md b/isa/README.md
new file mode 100644
index 00000000..29c6e28b
--- /dev/null
+++ b/isa/README.md
@@ -0,0 +1,79 @@
+# Shared instruction encoders
+
+This module contains the x86-64, AArch64, and RISC-V instruction encoders used
+by Chapters 21-25, plus their ARM/RISC-V test evaluators.
+It is compiler implementation code, shared because the same concrete instruction
+should have the same encoding in every chapter. It requires only the JDK.
+
+The compiler still decides which instruction to select, which physical registers
+to use, what an operand's width and extension mean, and where blocks, stack
+slots, constants, and relocation targets belong. The shared encoder receives
+those decisions as numbers and writes bytes. It imports no chapter classes and
+has no knowledge of nodes, types, calling conventions, or compilation phases.
+
+## Follow one instruction
+
+Chapter 21's `AddX86` selects opcode `0x03` and retains its register masks and
+two-address constraint. Its `RegX86` base obtains the allocated registers:
+
+```java
+@Override public void encoding(Encoding enc) {
+ X86.reg(enc,opcode(),enc.reg(in(1)),enc.reg(in(2)));
+}
+```
+
+The shared `X86.reg` emits REX.W, the opcode, and ModRM. For `add r9,r10`, those
+bytes are `4d 03 ca`: REX extends both register fields, `03` selects ADD with the
+destination in ModRM.reg, and `ca` selects register operands 1 and 2. Chapter 21
+records the resulting instruction offset and length, just as before.
+
+`Encoding` implements the small `CodeSink` interface using its existing `add1`,
+`add2`, `add4`, and `add8` methods. Shared functions append directly to the compiler's
+buffer; they do not allocate temporary instruction objects or byte arrays.
+
+## Current boundary
+
+`X86` owns prefixes and addresses, integer and scalar floating-point operations,
+loads/stores, comparisons, constant materialization, register/stack copies,
+calls, returns, and branch bytes. The chapter chooses branch targets and runs
+relaxation; shared helpers calculate instruction lengths and patch displacements.
+
+Registers are hardware indices 0-15 in the explicitly selected GPR or XMM bank.
+Memory operands take a base, an optional index (`-1` for absent), a displacement,
+and a scale encoded as 0-3. LEA also supports an absent base. Sizes are log2 of
+the byte width, 0-3. The chapter converts its own types and register numbering
+to these concrete operands; the encoder never examines compiler types.
+
+`Arm64` and `RiscV` own opcode constants and instruction-field packing. ARM's
+logical-immediate encoding and MOVZ/MOVN/MOVK constant construction are shared
+too. Their chapter wrappers resolve node operands and register banks, then
+append the resulting instruction words. Instruction selection, masks, scheduling,
+frame layout, relocations, constant placement, and object-file writing remain
+chapter-local.
+
+`src/test-support` contains one `EvalArm64` and one `EvalRisc5`. The chapter test
+harness still builds the image, links it, sets arguments and checks results.
+These small interpreters implement the tutorial's instruction subset, not full
+system emulation. Their default test-runtime convention recognizes `calloc` at
+address -4 and `write` at -8; an overload accepts different sentinel addresses.
+They import no compiler classes and are compiled only with tests.
+
+## Tests and builds
+
+`make -C isa tests` runs byte-exact tests without building any compiler. Expected
+bytes cover register extensions, low-byte register selection, signed loads,
+immediate/displacement boundaries, SIB cases, calls, branches, and prefix order.
+ARM/RISC-V checks use fixed instruction words, offset boundaries, and small
+evaluator programs, including ARM constants that need several instructions.
+The chapter suites continue to test compiler wiring and execute generated code.
+The root `make tests` runs both shared and chapter tests.
+
+Chapter 21-25 Makefiles include `isa.mk`; their Maven builds explicitly add the
+shared main and test-support source directories. Earlier chapters do not compile
+ISA sources. The linearized Maven build adds them when it reaches Chapter 21.
+`isa.iml` describes the IDEA encoder module; `src/test-support/isa-eval.iml`
+describes the evaluators, which chapter modules depend on in test scope.
+Linearized checkouts carry `isa/` beside the other shared modules. Release jars
+include the compiled encoders. Make's existing release recipe also bundles the
+chapter tests and their shared evaluators; Maven's ordinary main jar excludes
+test classes.
diff --git a/isa/isa.iml b/isa/isa.iml
new file mode 100644
index 00000000..2319435e
--- /dev/null
+++ b/isa/isa.iml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module type="JAVA_MODULE" version="4">
+ <component name="NewModuleRootManager" inherit-compiler-output="false">
+ <output url="file://$MODULE_DIR$/../build/idea/production/isa" />
+ <output-test url="file://$MODULE_DIR$/../build/idea/test/isa" />
+ <exclude-output />
+ <content url="file://$MODULE_DIR$">
+ <sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" />
+ <sourceFolder url="file://$MODULE_DIR$/src/test/java" isTestSource="true" />
+ <excludeFolder url="file://$MODULE_DIR$/src/test-support" />
+ <excludeFolder url="file://$MODULE_DIR$/build" />
+ <excludeFolder url="file://$MODULE_DIR$/target" />
+ </content>
+ <orderEntry type="inheritedJdk" />
+ <orderEntry type="module" module-name="isa-eval" scope="TEST" />
+ <orderEntry type="sourceFolder" forTests="false" />
+ </component>
+</module>
diff --git a/isa/isa.mk b/isa/isa.mk
new file mode 100644
index 00000000..7f24618c
--- /dev/null
+++ b/isa/isa.mk
@@ -0,0 +1,5 @@
+# Shared instruction encoders; works in both chapter and linearized checkouts.
+ISA_DIR := $(patsubst %/,%,$(dir $(lastword $(MAKEFILE_LIST))))
+isa_javas := $(wildcard $(ISA_DIR)/src/main/java/com/seaofnodes/isa/*.java)
+main_javas += $(isa_javas)
+isa_test_javas := $(wildcard $(ISA_DIR)/src/test-support/java/com/seaofnodes/isa/eval/*.java)
diff --git a/isa/src/main/java/com/seaofnodes/isa/Arm64.java b/isa/src/main/java/com/seaofnodes/isa/Arm64.java
new file mode 100644
index 00000000..ce7f7b11
--- /dev/null
+++ b/isa/src/main/java/com/seaofnodes/isa/Arm64.java
@@ -0,0 +1,405 @@
+package com.seaofnodes.isa;
+
+/** Instruction fields and concrete operand encodings; no compiler state. */
+public final class Arm64 {
+ private Arm64() {}
+
+ public static final int OP_ADD = 0b10_001_011;
+ public static final int OPF_ADD = 0b00_011_110;
+ public static final int OPF_OP_ADD = 0b00_101_0;
+ public static final int OPI_ADD = 0b10_010_00100;
+ public static final int OPI_SUB = 0b11_010_00100;
+ public static final int OP_UJMP = 0b000101;
+ public static final int OP_ADRP = 0b10000;
+ public static final int OP_ASR = 0b1010;
+ public static final int OPI_ASR = 0b10_0100_1101;
+ public static final int OP_LSL = 0b1000;
+ public static final int OPI_LSL =0b1101001101;
+ public static final int OP_LSR = 0b1001;
+ public static final int OPI_LSR = 0b1101001101;
+ public static final int OP_BRANCH = 0b01_0101_00;
+ public static final int OP_CALL = 0b10_010_1;
+ public static final int OP_CALLRARM = 0b1101011000111111000000;
+ public static final int OP_SUBS = 0b1111000100;
+ public static final int OP_CSET = 0b10011010100;
+ public static final int OP_CMP = 0b11101011;
+ public static final int OPI_CMP = 0b1111000100;
+ public static final int OP_DIV = 0b10011010110;
+ public static final int OPF_DIV = 0b000110;
+ public static final int OP_MUL = 0b10011011000;
+ public static final int OPF_MUL = 0b10;
+ public static final int OPF_ARM = 0b01011100;
+ public static final int OP_FLOAT_C = 0b10011110;
+ public static final int OP_XOR = 0b11001010;
+ public static final int OPI_XOR = 0b110100100;
+ public static final int OP_AND = 0b10_0010_10;
+ public static final int OPI_AND = 0b10_0100_100;
+ public static final int OP_OR = 0b10101010;
+ public static final int OPI_OR = 0b101100100;
+ public static final int OP_SUB = 0b11001011;
+ public static final int OPF_SUB = 0b1110;
+ public static final int OP_MOVK = 0b111100101;
+ public static final int OP_MOVN = 0b100100101;
+ public static final int OP_MOVZ = 0b110100101;
+ public static final int OP_RET = 0b1101011001011111000000;
+ public static final int OP_LOAD_R_64 = 0b11111000011;
+ public static final int OP_LOAD_R_32 = 0b10111000011;
+ public static final int OP_LOAD_R_16 = 0b01111000010;
+ public static final int OP_LOAD_R_8 = 0b00111000011;
+ public static final int OP_LOAD_IMM_64 = 0b1111100101;
+ public static final int OP_LOAD_IMM_32 = 0b1011100101;
+ public static final int OP_LOAD_IMM_16 = 0b0111100101;
+ public static final int OP_LOAD_IMM_8 = 0b0011100101;
+ public static final int OPF_LOAD_R_64 = 0b11111100011;
+ public static final int OPF_LOAD_R_32 = 0b10111100011;
+ public static final int OPF_LOAD_IMM_64 = 0b1111110101;
+ public static final int OPF_LOAD_IMM_32 = 0b1011110101;
+ public static final int OP_STORE_R_64 = 0b11111000001;
+ public static final int OP_STORE_R_32 = 0b10111000001;
+ public static final int OP_STORE_R_16 = 0b01111000001;
+ public static final int OP_STORE_R_8 = 0b00111000001;
+ public static final int OP_STORE_IMM_64 = 0b1111100100;
+ public static final int OP_STORE_IMM_32 = 0b1011100100;
+ public static final int OP_STORE_IMM_16 = 0b0111100100;
+ public static final int OP_STORE_IMM_8 = 0b0011100100;
+ public static final int OPF_STORE_R_64 = 0b11111100001;
+ public static final int OPF_STORE_R_32 = 0b10111100001;
+ public static final int OPF_STORE_IMM_32 = 0b1011110100;
+ public static final int OPF_STORE_IMM_64 = 0b1111110100;
+ public static final int OP_FMOV = 0b10011110;
+ public static final int OP_FMOV_REG = 0b00011110;
+ public static final int OP_MOV = 0b10101010000;
+
+ public enum OPTION {
+ UXTB,
+ UXTH,
+ UXTX,
+ SXTB,
+ SXTH,
+ SXTW,
+ SXTX,
+ }
+
+ public enum STORE_LOAD_OPTION {
+ UXTW, // base+ u32 index [<< logsize]
+ UXTX, // base+ u64 index [<< logsize]
+ SXTW, // base+ s32 index [<< logsize]
+ SXTX, // base+ s64 index [<< logsize]
+ }
+
+ public enum COND {
+ EQ,
+ NE,
+ CS,
+ CC,
+ MI,
+ PL,
+ VS,
+ VC,
+ HI,
+ LS,
+ GE,
+ LT,
+ GT,
+ LE,
+ AL,
+ NV
+ }
+
+ static public int cset(int opcode, int rm, COND cond, int rn, int rd) {
+ assert 0 <= rm && rm < 32;
+ assert 0 <= rn && rn < 32;
+ assert 0 <= rd && rd < 32;
+ // Is one of the standard conditions, excluding AL and NV, encoded in the "cond" field with its least significant bit inverted.
+ int actualCond = cond.ordinal() ^ 1;
+ return (opcode << 21) | (rm << 16) | (actualCond << 12) | (0b01 << 10) | (rn << 5) | rd;
+ }
+
+ static public int cset(int opcode, COND cond, int rn, int rd) {
+ assert 0 <= rn && rn < 32;
+ assert 0 <= rd && rd < 32;
+ return (opcode << 16) | (cond.ordinal() << 12) | (rn << 5) | rd;
+ }
+
+ public static int imm_inst(int opcode, int imm12, int rn, int rd) {
+ assert 0 <= rn && rn < 32;
+ assert 0 <= rd && rd < 32;
+ assert opcode >=0 && imm12 >= 0; // Caller zeros high order bits
+ return (opcode << 22) | (imm12 << 10) | (rn << 5) | rd;
+ }
+
+ public static int imm_shift(int opcode, int imm, int imms, int rn, int rd) {
+ assert 0 <= rn && rn < 32;
+ assert 0 <= rd && rd < 32;
+ return (opcode << 22) | (1 << 22) | (imm << 16) | (imms << 10) | (rn << 5) | rd;
+ }
+
+ public static int imm_inst_n(int opcode, int imm13, int rn, int rd) {
+ assert 0 <= rn && rn < 32;
+ assert 0 <= rd && rd < 32;
+ assert 0 <= imm13 && imm13 <= 0x1FFF;
+ return (opcode << 23) | (imm13 << 10) | (rn << 5) | rd;
+ }
+
+ public static int imm_inst_l(int opcode, int imm12, int self) {
+ int body = imm_inst(opcode, imm12&0xFFF, self, self);
+ return body;
+ }
+
+ public static int r_reg(int opcode, int shift, int rm, int imm6, int rn, int rd) {
+ assert 0 <= rm && rm < 32;
+ assert 0 <= rn && rn < 32;
+ assert 0 <= rd && rd < 32;
+ return (opcode << 24) | (shift << 21) | (rm << 16) | (imm6 << 10) | (rn << 5) | rd;
+ }
+
+ public static int shift_reg(int opcode, int rm, int op2, int rn, int rd) {
+ assert 0 <= rn && rn < 32;
+ assert 0 <= rm && rm < 32;
+ assert 0 <= rd && rd < 32;
+ return (opcode << 21) | (rm << 16) | (op2 << 10) | (rn << 5) | rd;
+ }
+
+ public static int madd(int opcode, int rm, int ra, int rn, int rd) {
+ assert 0 <= rn && rn < 32;
+ assert 0 <= rd && rd < 32;
+ assert 0 <= rm && rm < 32;
+ return (opcode << 21) | (rm << 16) | (ra << 10) | (rn << 5) | rd;
+ }
+
+ public static int mov(int opcode, int shift, int imm16, int rd) {
+ assert 0 <= rd && rd < 32;
+ return (opcode << 23) | (shift << 21) | (imm16 << 5) | rd;
+ }
+
+ public static int mov_reg(int opcode, int src, int rd) {
+ assert 0 <= rd && rd < 32;
+ return (opcode << 21) | (src << 16) | 0b11111 << 5 | rd;
+ }
+
+ public static int ret(int opcode) {
+ return (opcode << 10) | (30 << 5);
+ }
+
+ public static int f_scalar(int opcode, int ftype, int rm, int op, int rn, int rd) {
+ assert 0 <= rn && rn < 32;
+ assert 0 <= rd && rd < 32;
+ assert 0 <= rm && rm < 32;
+ return (opcode << 24) | (ftype << 22) | (1 << 21) | (rm << 16) | (op << 10) | (rn << 5) | rd;
+ }
+
+ public static int f_mov_reg(int opcode, int rn, int rd) {
+ assert 0 <= rn && rn < 32;
+ assert 0 <= rd && rd < 32;
+ return (opcode << 24) | (0b01100000010000 << 10) | (rn << 5) | rd;
+ }
+
+ public static int f_mov_general(int opcode, int ftype, int rmode, int opcode1, int rn, int rd) {
+ assert 0 <= rn && rn < 32;
+ assert 0 <= rd && rd < 32;
+ return (opcode << 24) |(ftype << 22) | (1 << 21) | (rmode << 19) | (opcode1 << 16) | (rn << 5) | rd;
+ }
+
+ public static int load_pc(int opcode, int offset, int rt) {
+ assert -(1<<20) <= offset && offset < (1<<20);
+ assert (offset&3)==0;
+ offset = (offset>>2)&0x7FFFF;
+ return (opcode << 24) | (offset << 5) | rt;
+ }
+
+ public static int adrp(int op, int imlo,int opcode, int imhi, int rd) {
+ assert 0 <= rd && rd < 32;
+ assert 0 <= imlo && imlo <= 0x3; // 2 bits
+ assert 0 <= imhi && imhi <= 0x7FFFF; // 19 bits
+ return (op << 31) | (imlo << 29) |(opcode << 24) | (imhi << 5) | rd;
+ }
+
+ public static int indr_adr(int opcode, int off, STORE_LOAD_OPTION option, int s, int ptr, int rt) {
+ assert 0 <= ptr && ptr < 32;
+ assert 0 <= rt && rt < 32;
+ return (opcode << 21) | (off << 16) | (option.ordinal() << 13) | (s << 12) | (2 << 10) | (ptr << 5) | rt;
+ }
+
+ public static int load_str_imm(int opcode, int imm12, int ptr, int rt, int size) {
+ assert 0 <= ptr && ptr < 32;
+ assert 0 <= rt && rt < 32;
+
+ if(size == 8) imm12 = imm12 >> 3;
+ if(size == 4) imm12 = imm12 >> 2;
+ if(size == 2) imm12 = imm12 >> 1;
+ // size == 1 imm12 = imm12
+ return (opcode << 22) | ((imm12) << 10) | (ptr << 5) | rt;
+ }
+
+ public static int f_convert(int opcode_1, int opcode_2, int opcode_3, int opcode_4, int vd, int vm) {
+ return (opcode_1 << 28) | (opcode_2 << 24) | (opcode_3 << 20) | (opcode_4 << 16) |
+ (vd << 12) | (0x01100010 << 4) | vm;
+ }
+
+ /** FCVTZS Xd,Dn: truncate, saturate overflow, and convert NaN to zero. */
+ public static int floatToInteger(int rn, int rd) {
+ assert 0<=rn && rn<32 && 0<=rd && rd<32;
+ return 0x9E780000 | (rn<<5) | rd;
+ }
+
+ public static int float_cast(int opcode, int ftype, int rn, int rd) {
+ assert 0 <= rd && rd < 32;
+ assert 0 <= rn && rn < 32;
+ return (opcode << 24) | (ftype << 22) | (2176 << 10) | (rn << 5) | rd;
+ }
+
+ public static int f_cmp(int opcode, int ftype, int rm, int rn) {
+ assert 0 <= rn && rn < 32;
+ assert 0 <= rm && rm < 32;
+ // Todo: |8 is not needed
+ return (opcode << 24) | (ftype << 21) | (rm << 16) | (8 << 10) | (rn << 5);
+ }
+
+ public static COND make_condition(String bop) { return make_condition(bop,false); }
+
+ public static COND make_condition(String bop, boolean fp) {
+ // One of {N,Z,C} and !V
+ // OR { !N, !Z, C, V }
+ return fp
+ ? switch (bop) { // FP uses unsigned encodings
+ case "==" -> COND.EQ; // Z
+ case "!=" -> COND.NE; // !Z
+ case "<" -> COND.MI; // N
+ case "<=" -> COND.LS; // Z || !C
+ case ">=" -> COND.PL; // !N
+ case ">" -> COND.HI; // !Z && C
+ default -> throw new UnsupportedOperationException();
+ }
+ : switch (bop) {
+ case "==" -> COND.EQ; // Z
+ case "!=" -> COND.NE; // !Z
+ case "<" -> COND.LT; // N != V
+ case "<=" -> COND.LE; // Z || N != V
+ case ">=" -> COND.GE; // N == V
+ case ">" -> COND.GT; // !Z && N == V
+ default -> throw new UnsupportedOperationException();
+ };
+ }
+
+ public static int b_cond(int opcode, int delta, COND cond) {
+ // 24-5 == 19bits offset range
+ assert -(1<<20) <= delta && delta < (1<<20);
+ assert (delta&3)==0;
+ delta>>=2;
+ delta &= (1L<<19)-1; // Zero extend
+ return (opcode << 24) | ((delta)<< 5) | cond.ordinal();
+ }
+
+ public static int cond_set(int opcode, int rm, COND cond, int rn, int rd) {
+ assert 0 <= rd && rd < 32;
+ assert 0 <= rn && rn < 32;
+ return (opcode << 21) | (rm << 16) | (cond.ordinal() << 12) | (rn << 5) | rd;
+ }
+
+ public static int blr(int opcode, int rd) {
+ assert 0 <= rd && rd < 32;
+ return opcode << 10 | rd << 5;
+ }
+
+ public static int b(int opcode, int delta) {
+ assert -(1<<27) <= delta && delta < (1<<27);
+ assert (delta&3)==0;
+ delta>>=2;
+ delta &= (1L<<26)-1; // Zero extend
+ return (opcode << 26) | delta;
+ }
+
+ public static int b_calloc(int opcode, int delta) {
+ return b(opcode,delta);
+ }
+
+ public static long decodeImm12(int imm12) {
+ int immr = (imm12 >> 6) & 0x3F;
+ int imms = imm12 & 0x3F;
+ int size;
+ if ((imm12 & 0x1000) != 0) {
+ size = 64;
+ } else {
+ size = 31-(imms >> 1);
+ size |= size >> 1;
+ size |= size >> 2;
+ size |= size >> 4;
+ size++;
+ imms &= ~((32-size) << 1);
+ }
+ long val = (2L << imms)-1;
+ while (size < 64) {
+ val |= val << size;
+ size <<= 1;
+ }
+ val = (val >>> immr) | val << (64-immr);
+ return val;
+ }
+
+ public static int logicalImmediate(long val) {
+ if (val == 0 || val == -1) return -1; // Special cases are not allowed
+ int immr = 0;
+ // Rotate until we have 0[...]1
+
+ // Rotate until:
+ // The number is negative (MSB is 1)
+ // Or the LSB is not 1
+ while (val < 0 || (val & 1)==0) {
+ // circular rotation
+ val = (val >>> 63) | (val << 1);
+ immr++;
+ }
+ // Start by assuming that val might be made of two 32-bit chunks that repeat.
+ int size = 32;
+ long pattern = val;
+ // Is upper half of pattern the same as the lower?
+ while ((pattern & ((1L<<size)-1)) == (pattern >> size)) {
+ // Then only take one half
+ pattern >>= size;
+ size >>= 1;
+ }
+ size <<= 1;
+ int imms = Long.bitCount(pattern);
+ // Pattern should now be zeros followed by ones 0000011111
+ if (pattern != (1L<<imms)-1) return -1;
+ imms--;
+ if (size == 64) return 0x1000 | immr << 6 | imms;
+ return (32-size)<<1 | immr << 6 | imms;
+ }
+ /** Materialize a 64-bit constant with MOVZ/MOVN and MOVK. */
+ public static void constant(CodeSink out, int self, long x) {
+ int nb0 = 0;
+ int nb1 = 0;
+ // Count number of 0000 and FFFF blocks
+ for (int i=0; i<64; i+=16) {
+ int block = (int)(x >> i) & 0xFFFF;
+ if (block == 0) nb0++;
+ if (block == 0xFFFF) nb1++;
+ }
+ int pattern;
+ int op;
+ if(nb0 >= nb1) {
+ // More 0 blocks then F blocks, use movz
+ pattern = 0;
+ op = OP_MOVZ;
+ } else {
+ // More F blocks then 0 blocks, use movn
+ pattern = 0xFFFF;
+ op = OP_MOVN;
+ }
+ int invert = pattern;
+ for (int i=0; i<4; i++) {
+ int block = (int)x & 0xFFFF;
+ x >>= 16;
+ if (block != pattern) {
+ out.add4(mov(op, i, block ^ invert, self));
+ op = OP_MOVK;
+ invert = 0;
+ }
+ }
+ if (op != OP_MOVK) {
+ // All blocks are the same, special case
+ out.add4(mov(op, 0, 0, self));
+ }
+ }
+}
diff --git a/isa/src/main/java/com/seaofnodes/isa/CodeSink.java b/isa/src/main/java/com/seaofnodes/isa/CodeSink.java
new file mode 100644
index 00000000..0c2d4936
--- /dev/null
+++ b/isa/src/main/java/com/seaofnodes/isa/CodeSink.java
@@ -0,0 +1,9 @@
+package com.seaofnodes.isa;
+
+/** Instruction bytes go straight into the chapter's code buffer. */
+public interface CodeSink {
+ CodeSink add1(int value);
+ default CodeSink add2(int value) { return add1(value).add1(value>>8); }
+ default CodeSink add4(int value) { return add2(value).add2(value>>16); }
+ default CodeSink add8(long value) { return add4((int)value).add4((int)(value>>32)); }
+}
diff --git a/isa/src/main/java/com/seaofnodes/isa/RiscV.java b/isa/src/main/java/com/seaofnodes/isa/RiscV.java
new file mode 100644
index 00000000..0ecd1cec
--- /dev/null
+++ b/isa/src/main/java/com/seaofnodes/isa/RiscV.java
@@ -0,0 +1,127 @@
+package com.seaofnodes.isa;
+
+/** Instruction fields and concrete operand encodings; no compiler state. */
+public final class RiscV {
+ private RiscV() {}
+
+ public static final int OP_LOAD = 0b00_000_11;
+ public static final int OP_LOADFP = 0b00_001_11;
+ public static final int OP_CUSTOM0 = 0b00_010_11;
+ public static final int OP_IMM = 0b00_100_11;
+ public static final int OP_AUIPC = 0b00_101_11;
+ public static final int OP_STORE = 0b01_000_11;
+ public static final int OP_STOREFP = 0b01_001_11;
+ public static final int OP_CUSTOM1 = 0b01_010_11;
+ public static final int OP = 0b01_100_11;
+ public static final int OP_LUI = 0b01_101_11;
+ public static final int OP_CUSTOM2 = 0b10_010_11;
+ public static final int OP_FP = 0b10_100_11;
+ public static final int OP_BRANCH = 0b11_000_11;
+ public static final int OP_JALR = 0b11_001_11;
+ public static final int OP_RESERVED= 0b11_010_11;
+ public static final int OP_JAL = 0b11_011_11;
+
+ public enum RM {
+ RNE, // Round to Nearest, ties to Even
+ RTZ, // Round towards Zero
+ RDN, // Round Down
+ RUP, // Round Up
+ DIRECT, // Round to Nearest, ties to Max Magnitude
+ RESERVED1, // Reserved for futue use
+ RESERVED2, // Reserved for future use
+ DYN, // In instruction’s rm field, selects dynamic rounding mode; In Rounding Mode register, reserved
+ }
+
+ public static int r_type(int opcode, int rd, int func3, int rs1, int rs2, int func7) {
+ assert 0 <= rs1 && rs1 < 32;
+ assert 0 <= rs2 && rs2 < 32;
+ assert 0 <= rd && rd < 32;
+ return (func7 << 25) | (rs2 << 20) | (rs1 << 15) | (func3 << 12) | (rd << 7) | opcode;
+ }
+
+ public static int u_type(int opcode, int rd, int imm20) {
+ assert 0 <= rd && rd < 32;
+ return (imm20 << 12) | (rd << 7) | opcode;
+ }
+
+ public static int j_type(int opcode, int rd, int delta) {
+ assert -(1L<<20) <= delta && delta < (1L<<20);
+ assert 0 <= rd && rd < 32;
+ // Messy branch offset encoding
+ // 31 30-21 20 19-12 11-7 6-0
+ // 20 10- 1 11 19-12 rpc JAL
+ assert (delta&1)==0; // Low bit is always zero, not encoded
+ int imm10_01 = (delta>> 1) & 0x3FF;
+ int imm11 = (delta>>11) & 1;
+ int imm12_19 = (delta>>12) & 0xFF;
+ int imm20 = (delta>>20) & 1;
+ int bits = imm20<<19 | imm10_01 << 9 | imm11 << 8 | imm12_19;
+ return bits << 12 | rd << 7 | opcode;
+ }
+
+ /** FCVT.L.D with RTZ saturates overflow; explicitly map either NaN to zero. */
+ public static void floatToInteger(CodeSink out, int dst, int src) {
+ out.add4(r_type(OP_FP,dst,1,src,0,0x71)); // FCLASS.D
+ out.add4(i_type(OP_IMM,dst,7,dst,0x300)); // ANDI: NaN classification bits
+ out.add4(b_type(OP_BRANCH,1,(short)dst,(short)0,12)); // BNE nan
+ out.add4(r_type(OP_FP,dst,RM.RTZ.ordinal(),src,2,0x61)); // FCVT.L.D
+ out.add4(j_type(OP_JAL,0,8)); // J done
+ out.add4(i_type(OP_IMM,dst,0,0,0)); // nan: LI dst,0
+ }
+
+ public static int i_type(int opcode, int rd, int func3, int rs1, int imm12) {
+ assert 0 <= rd && rd < 32;
+ assert 0 <= rs1 && rs1 < 32;
+ assert opcode >= 0 && func3 >=0 && imm12 >= 0; // Zero-extend by caller
+ return (imm12 << 20) | (rs1 << 15) | (func3 << 12) | (rd << 7) | opcode;
+ }
+
+ public static int s_type(int opcode, int func3, int rs1, int rs2, int imm12) {
+ assert 0 <= rs1 && rs1 < 32;
+ assert 0 <= rs2 && rs2 < 32;
+ assert 0 <= func3;
+
+ 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 b_type(int opcode, int func3, short rs1, short rs2, int delta) {
+ assert 0 <= rs1 && rs1 < 32;
+ assert 0 <= rs2 && rs2 < 32;
+ assert -4*1024 <= delta && delta < 4*1024;
+ assert (delta&1)==0; // Low bit is always zero, not encoded
+ // Messy branch offset encoding
+ // 31 30 29 28 27 26 25 24-20 19-15 14-12 11 10 9 8 7 6-0
+ // 12 10 9 8 7 6 5 SRC2 SRC1 FUNC3 4 3 2 1 11 OP
+ int imm4_1 = (delta>> 1) & 0xF;
+ int imm10_5= (delta>> 5) &0x3F;
+ int imm11 = (delta>>11) & 1;
+ int imm12 = (delta>>12) & 1;
+ int imm5 = imm4_1<<1 | imm11;
+ int imm7 = imm12<<6 | imm10_5;
+ return (imm7 << 25 ) | (rs2 << 20) | (rs1 << 15) | (func3 << 12) | (imm5 << 7) | opcode;
+ }
+
+ static public int jumpop(String op) {
+ return switch(op) {
+ case "==" -> 0x0;
+ case "!=" -> 0x1;
+ case "<" -> 0x4;
+ case ">=" -> 0x5;
+ case "u<" -> 0x6;
+ case "u<=" -> 0x7;
+ default -> throw new UnsupportedOperationException();
+ };
+ }
+
+ static public int fsetop(String op) {
+ return switch(op) {
+ case "<" -> 1;
+ case "<=" -> 0;
+ case "==" -> 2;
+ default -> throw new UnsupportedOperationException();
+ };
+ }
+}
diff --git a/isa/src/main/java/com/seaofnodes/isa/X86.java b/isa/src/main/java/com/seaofnodes/isa/X86.java
new file mode 100644
index 00000000..78fa3da5
--- /dev/null
+++ b/isa/src/main/java/com/seaofnodes/isa/X86.java
@@ -0,0 +1,272 @@
+package com.seaofnodes.isa;
+
+/** x86-64 instruction bytes. Registers are hardware numbers 0..15, in the chosen bank. */
+public final class X86 {
+ private X86() {}
+ public enum MOD { INDIRECT, INDIRECT_disp8, INDIRECT_disp32, DIRECT }
+
+ public static int modrm(MOD mod, int reg, int rm) {
+ if( reg==-1 ) reg=0;
+ assert 0<=reg && reg<16;
+ assert 0<=rm && rm<16;
+ return (mod.ordinal()<<6) | ((reg&7)<<3) | (rm&7);
+ }
+ public static int sib(int scale, int index, int base) {
+ assert 0<=scale && scale<=3;
+ assert 0<=index && index<16;
+ assert 0<=base && base<16;
+ return (scale<<6) | ((index&7)<<3) | (base&7);
+ }
+ public static int rex(int reg, int base, int index, boolean wide) {
+ assert -1<=reg && reg<16;
+ assert -1<=base && base<16;
+ assert -1<=index && index<16;
+ return (wide ? 0x48 : 0x40) | (reg>=8 ? 4 : 0) |
+ (index>=8 ? 2 : 0) | (base>=8 ? 1 : 0);
+ }
+ public static int rex(int reg, int base, int index) { return rex(reg,base,index,true); }
+
+ /** Omit a redundant REX; byte-register instructions sometimes need it explicitly. */
+ public static byte rexF(int reg, int base, int index, boolean wide, CodeSink out) {
+ int bits=rex(reg,base,index,wide);
+ if( bits==0x40 ) return 0;
+ out.add1(bits);
+ return 1;
+ }
+
+ /** ModRM/SIB/displacement for [base + index*2^scale + offset]; -1 means no index. */
+ public static void indirectAdr(int scale, int index, int base, int offset, int reg, CodeSink out) {
+ assert 0<=base && base<16;
+ assert -1<=index && index<16 && index!=4;
+ MOD mod=offset==0 ? MOD.INDIRECT : imm8(offset) ? MOD.INDIRECT_disp8 : MOD.INDIRECT_disp32;
+ // The no-displacement r/m=5 form is RIP-relative, even with REX.B.
+ if( mod==MOD.INDIRECT && (base==5 || base==13) ) mod=MOD.INDIRECT_disp8;
+ // r/m=4 always introduces SIB. Index=4 with no REX.X means no index.
+ if( index==-1 && (base==4 || base==12) ) index=4;
+ if( index==-1 ) out.add1(modrm(mod,reg,base));
+ else out.add1(modrm(mod,reg,4)).add1(sib(scale,index,base));
+ if( mod==MOD.INDIRECT_disp8 ) out.add1(offset);
+ if( mod==MOD.INDIRECT_disp32 ) out.add4(offset);
+ }
+ private static boolean imm8(int value) { return -128<=value && value<=127; }
+ public static void unary(CodeSink out, int op, int digit, int dst) {
+ out.add1(rex(0,dst,-1)).add1(op).add1(modrm(MOD.DIRECT,digit,dst));
+ }
+ public static void shift(CodeSink out, int digit, int dst, int count) {
+ unary(out,0xC1,digit,dst);
+ out.add1(count); // A shift always takes imm8; the processor masks its count.
+ }
+ public static void divide(CodeSink out, int src) {
+ out.add1(0x48).add1(0x99); // CQO: sign-extend rax into rdx:rax.
+ unary(out,0xF7,7,src);
+ }
+ /** Truncate f64 to i64, saturating overflow and mapping NaN to zero. */
+ public static void floatToInteger(CodeSink out, int dst, int src) {
+ sse(out,0xF2,0x0F2C,true,dst,src); // CVTTSD2SI
+ imm(out,0x81,7,dst,1); // CMP dst,1: OF iff dst is MIN_VALUE
+ out.add1(0x71).add1((src>=8 ? 5 : 4)+24); // JNO done
+ sse(out,0x66,0x0F2E,false,src,src); // UCOMISD: unordered iff NaN
+ out.add1(0x7A).add1(19); // JP nan
+ sse(out,0x66,0x0F7E,true,src,dst); // MOVQ: inspect the float's sign
+ shift(out,7,dst,63); // SAR: 0 or -1
+ unary(out,0xF7,2,dst); // NOT: -1 or 0
+ reg(out,0x0FBA,7,dst); // BTC sign bit: MAX_VALUE or MIN_VALUE
+ out.add1(63);
+ out.add1(0xEB).add1(3); // JMP done
+ reg(out,0x33,dst,dst); // nan: XOR dst,dst
+ }
+
+ public static void sse(CodeSink out, int prefix, int op, boolean wide, int dst, int src) {
+ if( prefix!=0 ) out.add1(prefix);
+ rexF(dst,src,-1,wide,out);
+ opcode(out,op);
+ out.add1(modrm(MOD.DIRECT,dst,src));
+ }
+ public static void memory(CodeSink out, int prefix, int op, boolean wide,
+ int reg, int base, int index, int offset, int scale) {
+ if( prefix!=0 ) out.add1(prefix);
+ rexF(reg,base,index,wide,out);
+ opcode(out,op);
+ indirectAdr(scale,index,base,offset,reg,out);
+ }
+ public static void cmpMem(CodeSink out, int logSize, int reg, int base, int index,
+ int offset, int scale, boolean swap) {
+ if( logSize==1 ) out.add1(0x66);
+ if( logSize==0 && reg>=4 ) out.add1(rex(reg,base,index,false));
+ else rexF(reg,base,index,logSize==3,out);
+ out.add1((logSize==0 ? 0x38 : 0x39)+(swap ? 2 : 0));
+ indirectAdr(scale,index,base,offset,reg,out);
+ }
+ public static void addMem(CodeSink out, int src, int value, int base, int index, int offset, int scale) {
+ memory(out,0,src==-1 ? (value==1 ? 0xFF : 0x81) : 0x01,true,
+ src==-1 ? 0 : src,base,index,offset,scale);
+ if( src==-1 && value!=1 ) out.add4(value);
+ }
+ public static void extend(CodeSink out, int bits, boolean signed, int dst, int src) {
+ out.add1(rex(dst,src,-1,signed || bits<32));
+ opcode(out,bits==32 ? (signed ? 0x63 : 0x8B) : (signed ? 0x0FBE : 0x0FB6)+(bits==16 ? 1 : 0));
+ out.add1(modrm(MOD.DIRECT,dst,src));
+ }
+ /** SETcc dstb; MOVZX dst32,dstb. Condition is the low four bits of Jcc/SETcc. */
+ public static void set(CodeSink out, int condition, int dst) {
+ if( dst>=4 ) out.add1(rex(0,dst,-1,false));
+ out.add1(0x0F).add1(0x90|condition).add1(modrm(MOD.DIRECT,0,dst));
+ if( dst>=4 ) out.add1(rex(dst,dst,-1,false));
+ out.add1(0x0F).add1(0xB6).add1(modrm(MOD.DIRECT,dst,dst));
+ }
+ public static void constant(CodeSink out, int dst, long value) {
+ if( value==0 ) {
+ if( dst>=8 ) out.add1(rex(dst,dst,-1));
+ out.add1(0x33).add1(modrm(MOD.DIRECT,dst,dst));
+ } else if( Integer.MIN_VALUE<=value && value<0 ) {
+ unary(out,0xC7,0,dst);
+ out.add4((int)value);
+ } else if( 0<=value && value<=0xFFFFFFFFL ) {
+ rexF(0,dst,-1,false,out);
+ out.add1(0xB8+(dst&7)).add4((int)value);
+ } else {
+ out.add1(rex(0,dst,-1)).add1(0xB8+(dst&7)).add8(value);
+ }
+ }
+ /** RIP-relative operand; returns length so the chapter can record its relocation. */
+ public static byte rip(CodeSink out, int prefix, int op, boolean wide, int dst) {
+ int size=4+1+(op>255 ? 2 : 1);
+ if( prefix!=0 ) { out.add1(prefix); size++; }
+ size+=rexF(dst,-1,-1,wide,out);
+ opcode(out,op);
+ out.add1(modrm(MOD.INDIRECT,dst,5)).add4(0);
+ return (byte)size;
+ }
+ public static void call(CodeSink out) { out.add1(0xE8).add4(0); }
+ public static void callRegister(CodeSink out, int src) {
+ rexF(0,src,-1,false,out);
+ out.add1(0xFF).add1(modrm(MOD.DIRECT,2,src));
+ }
+ public static void ret(CodeSink out) { out.add1(0xC3); }
+ public static void branch(CodeSink out, int condition) {
+ out.add1(condition<0 ? 0xEB : 0x70|condition).add1(0);
+ }
+ public static byte branchSize(int delta, boolean conditional) {
+ return (byte)(imm8(delta-2) ? 2 : conditional ? 6 : 5);
+ }
+ public static void patchBranch(byte[] bytes, int start, int size, int delta, int condition) {
+ delta-=size;
+ if( size==2 ) {
+ assert (byte)delta==delta;
+ bytes[start+1]=(byte)delta;
+ } else {
+ int pos=start;
+ if( condition<0 ) bytes[pos++]=(byte)0xE9;
+ else { bytes[pos++]=0x0F; bytes[pos++]=(byte)(0x80|condition); }
+ for( int i=0; i<4; i++ ) bytes[pos+i]=(byte)(delta>>(8*i));
+ }
+ }
+ public static void flags(CodeSink out, int reg, boolean toFlags) {
+ if( !toFlags ) out.add1(0x9C); // PUSHF
+ rexF(0,reg,-1,false,out);
+ out.add1((toFlags ? 0x50 : 0x58)+(reg&7));
+ if( toFlags ) out.add1(0x9D); // POPF
+ }
+ public static void stackCopy(CodeSink out, int srcOffset, int dstOffset) {
+ memory(out,0,0xFF,false,6,4,-1,srcOffset,0); // PUSH [rsp+srcOffset]
+ memory(out,0,0x8F,false,0,4,-1,dstOffset,0); // POP uses the restored rsp.
+ }
+ public static void move(CodeSink out, boolean dstXmm, boolean srcXmm, int dst, int src) {
+ if( dstXmm ^ srcXmm ) out.add1(0x66);
+ if( !dstXmm && srcXmm ) { int tmp=dst; dst=src; src=tmp; }
+ reg(out,!dstXmm && !srcXmm ? 0x8B : dstXmm && srcXmm ? 0x0F28 : dstXmm ? 0x0F6E : 0x0F7E,dst,src);
+ }
+ private static void opcode(CodeSink out, int op) {
+ if( op>255 ) out.add1(op>>8);
+ out.add1(op);
+ }
+
+ /** REX.W + opcode + /r, with the destination in ModRM.reg. */
+ public static void reg(CodeSink out, int op, int dst, int src) {
+ out.add1(rex(dst,src,-1));
+ opcode(out,op);
+ out.add1(modrm(MOD.DIRECT,dst,src));
+ }
+ /** Group-1 arithmetic: 81 /digit id or 83 /digit ib. */
+ public static void imm(CodeSink out, int op, int digit, int dst, int value) {
+ out.add1(rex(0,dst,-1)).add1(op+(imm8(value) ? 2 : 0));
+ out.add1(modrm(MOD.DIRECT,digit,dst));
+ if( imm8(value) ) out.add1(value); else out.add4(value);
+ }
+ public static void imul(CodeSink out, int dst, int src, int value) {
+ out.add1(rex(dst,src,-1)).add1(imm8(value) ? 0x6B : 0x69);
+ out.add1(modrm(MOD.DIRECT,dst,src));
+ if( imm8(value) ) out.add1(value); else out.add4(value);
+ }
+ public static void not(CodeSink out, int dst, int src) {
+ reg(out,0x85,src,src); // TEST src,src
+ set(out,4,dst); // SETZ; MOVZX
+ }
+ public static void lea(CodeSink out, int dst, int base, int index, int offset, int scale) {
+ out.add1(rex(dst,base,index)).add1(0x8D);
+ if( base==-1 ) {
+ assert 0<=index && index<16 && index!=4;
+ out.add1(modrm(MOD.INDIRECT,dst,4)).add1(sib(scale,index,5)).add4(offset);
+ } else indirectAdr(scale,index,base,offset,dst,out);
+ }
+
+ /** Load into a GPR (extending to 64 bits) or an XMM register (scalar 32/64 bits). */
+ public static void load(CodeSink out, int logSize, boolean signed, boolean xmm,
+ int dst, int base, int index, int offset, int scale) {
+ assert 0<=logSize && logSize<=3;
+ if( xmm ) {
+ assert logSize==2 || logSize==3;
+ out.add1(logSize==2 ? 0xF3 : 0xF2);
+ rexF(dst,base,index,false,out);
+ out.add1(0x0F).add1(0x10);
+ } else {
+ rexF(dst,base,index,logSize!=2 || signed,out);
+ int op=switch(logSize) {
+ case 0 -> signed ? 0x0FBE : 0x0FB6;
+ case 1 -> signed ? 0x0FBF : 0x0FB7;
+ case 2 -> signed ? 0x63 : 0x8B;
+ default -> 0x8B;
+ };
+ opcode(out,op);
+ }
+ indirectAdr(scale,index,base,offset,dst,out);
+ }
+ public static void store(CodeSink out, int logSize, boolean xmm,
+ int src, int base, int index, int offset, int scale) {
+ assert 0<=logSize && logSize<=3;
+ if( xmm ) {
+ assert logSize==2 || logSize==3;
+ out.add1(logSize==2 ? 0xF3 : 0xF2);
+ rexF(src,base,index,false,out);
+ out.add1(0x0F).add1(0x11);
+ } else {
+ // Legacy prefixes precede REX, which must immediately precede the opcode.
+ if( logSize==1 ) out.add1(0x66);
+ if( logSize==0 && src>=4 ) out.add1(rex(src,base,index,false));
+ else rexF(src,base,index,logSize==3,out);
+ out.add1(logSize==0 ? 0x88 : 0x89);
+ }
+ indirectAdr(scale,index,base,offset,src,out);
+ }
+ public static void storeImm(CodeSink out, int logSize, int value,
+ int base, int index, int offset, int scale) {
+ memImm(out,logSize,0xC6,0xC7,0,value,base,index,offset,scale);
+ }
+ public static void cmpImm(CodeSink out, int logSize, int value,
+ int base, int index, int offset, int scale) {
+ memImm(out,logSize,0x80,0x81,7,value,base,index,offset,scale);
+ }
+ private static void memImm(CodeSink out, int logSize, int op8, int op, int digit, int value,
+ int base, int index, int offset, int scale) {
+ assert 0<=logSize && logSize<=3;
+ if( logSize==1 ) out.add1(0x66);
+ rexF(-1,base,index,logSize==3,out);
+ out.add1(logSize==0 ? op8 : op);
+ indirectAdr(scale,index,base,offset,digit,out);
+ switch(logSize) {
+ case 0 -> out.add1(value);
+ case 1 -> out.add2(value);
+ default -> out.add4(value); // Even a 64-bit operand takes only imm32.
+ }
+ }
+}
diff --git a/isa/src/test-support/isa-eval.iml b/isa/src/test-support/isa-eval.iml
new file mode 100644
index 00000000..733c5919
--- /dev/null
+++ b/isa/src/test-support/isa-eval.iml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module type="JAVA_MODULE" version="4">
+ <component name="NewModuleRootManager" inherit-compiler-output="false">
+ <output url="file://$MODULE_DIR$/../../../build/idea/production/isa-eval" />
+ <exclude-output />
+ <content url="file://$MODULE_DIR$">
+ <sourceFolder url="file://$MODULE_DIR$/java" isTestSource="false" />
+ </content>
+ <orderEntry type="inheritedJdk" />
+ <orderEntry type="sourceFolder" forTests="false" />
+ <orderEntry type="module" module-name="isa" />
+ </component>
+</module>
diff --git a/isa/src/test-support/java/com/seaofnodes/isa/eval/EvalArm64.java b/isa/src/test-support/java/com/seaofnodes/isa/eval/EvalArm64.java
new file mode 100644
index 00000000..1ff77f59
--- /dev/null
+++ b/isa/src/test-support/java/com/seaofnodes/isa/eval/EvalArm64.java
@@ -0,0 +1,790 @@
+package com.seaofnodes.isa.eval;
+
+import com.seaofnodes.isa.Arm64;
+import java.io.ByteArrayOutputStream;
+
+public class EvalArm64 {
+ // Memory image, always 0-based
+ public final byte[] _buf;
+
+ // GPRs
+ public final long[] regs;
+ // FRs
+ public final double[] fregs;
+
+ public int _pc;
+
+ // Start of free memory for allocation
+ public int _heap;
+
+ // Standard out and err streams
+ public ByteArrayOutputStream _stdout, _stderr;
+
+ // Cycle counters
+ public int _cycle;
+
+ // flags
+ public boolean N = false;
+ public boolean Z = false;
+ public boolean C = false;
+ public boolean V = false;
+
+ // Default test-runtime entry points; callers can supply another link convention.
+ private final int _calloc, _write;
+ public EvalArm64(byte[] buf, int stackSize) { this(buf,stackSize,-4,-8); }
+ public EvalArm64(byte[] buf, int stackSize, int calloc, int write) {
+ _calloc = calloc;
+ _write = write;
+ _buf = buf;
+ regs = new long[32];
+ fregs = new double[32];
+ // Stack grows down, heap grows up
+ regs[31] = _heap = stackSize;
+ _pc = 0;
+ }
+
+ // Little endian
+ public int ld1s(int x) { return _buf[x ] ; }
+ public int ld1z(int x) { return _buf[x ]&0xFF; }
+ public int ld2s(int x) { return ld1z(x) | ld1s(x+1)<< 8; }
+ public int ld2z(int x) { return ld1z(x) | ld1z(x+1)<< 8; }
+ public int ld4s(int x) { return ld2z(x) | ld2s(x+2)<<16; }
+ public long ld8 (int x) { return (long)ld4s(x)&0xFFFFFFFFL | ((long)ld4s(x+4))<<32; }
+
+
+ public float ld4f(int x) { return Float.intBitsToFloat(ld4s(x)); }
+ public double ld8f(int x) { return Double.longBitsToDouble(ld8(x)); }
+
+ public void st1(int x, int val) { _buf[x] = (byte)val; }
+ public void st2(int x, int val) { st1(x,val); st1(x+1,val>> 8); }
+ public void st4(int x, int val) { st2(x,val); st2(x+2,val>>16); }
+ public void st8(int x, long val) { st4(x,(int)val); st4(x+4,(int)(val>>32)); }
+
+ public int step(int maxops) {
+ int trap = 0;
+ long rval = 0;
+ double frval = 0;
+ int pc = _pc;
+ int cycle = _cycle;
+ outer:
+ for(int icount = 0; icount < maxops; icount++) {
+ boolean is_f = false;
+ int ir = 0;
+ rval = 0;
+ frval = 0;
+ cycle++;
+ if( pc >= _buf.length ) {
+ trap = 1 + 1; // Handle access violation on instruction read.
+ break;
+ }
+ if( (pc & 3)!=0 ) {
+ trap = 1; // Handle PC-misaligned access
+ break;
+ }
+
+ // Load instruction from image buffer at PC
+ ir = ld4s( pc );
+
+ int rdid = ir & 0x1f;
+ // [31:24]
+ int opcode1 = (ir >> 24) & 0xFF;
+
+ // for the opcode we just match for bits[31:24];
+ switch( opcode1 ) {
+ case 0x14, 0x15, 0x16, 0x17: { //b
+ int imm26 = ir << 6 >> 6;
+ pc = pc + (imm26 * 4) - 4;
+ rdid = -1;
+ break;
+ }
+ case 0x1E: { // floats
+ is_f = true;
+ int rs1 = (ir >> 5) & 0x1F;
+ int rs2 = (ir >> 16) & 0x1F;
+
+ int op = (ir >> 10) & 0x3F;
+ switch(op) {
+ case 16: frval = fregs[rs1]; break; // fmov
+ case 10: frval = fregs[rs1] + fregs[rs2]; break; // fadd
+
+ case 6: if (fregs[rs2] == 0) frval = 0; else frval = fregs[rs1] / fregs[rs2]; break; // fdiv
+ case 8: {
+ // fcmp
+ double lhs = fregs[rs1];
+ double rhs = fregs[rs2];
+
+ Z = (lhs == rhs);
+ N = (lhs < rhs);
+ C = (lhs > rhs);
+ V = Double.isNaN(lhs) || Double.isNaN(rhs);
+ rdid = -1;
+ break;
+ }
+ case 2: frval = fregs[rs1] * fregs[rs2]; break; // fmul
+ case 14: frval = fregs[rs1] - fregs[rs2]; break; // fsub
+ default: trap = (2+1);
+ }
+ break;
+ }
+
+ case 0x38: {
+ // STRB/LDRB(register)
+ int rn = (ir >> 5) & 0x1F;
+ int rm = (ir >> 16) & 0x1F;
+ // full opcode
+ opcode1 = (ir >> 21) & 0x7FF;
+ switch( ir >> 21 & 0x7 ) {
+ case 0x1 -> { // store
+ if( opcode1 == Arm64.OP_STORE_R_8 ) st1((int)regs[rn] + (int)regs[rm], (int)regs[rdid]);
+ else trap = (2+1);
+ rdid = -1;
+ }
+ case 0x3 -> { // load
+ if( opcode1 == Arm64.OP_LOAD_R_8 ) rval = ld1s((int)regs[rn] + (int)regs[rm]);
+ else trap = (2+1);
+ }
+ default -> trap = (2+1);
+ }
+ break;
+ }
+ case 0x39: {
+ // STRB/LDRB(immediate)
+ // str/ldr(immediate)
+ // rn
+ int base = ir >> 5 & 0x1F;
+ int imm = (ir >> 10) & 0xFFF;
+ rval = regs[base] + imm;
+ int opc = (ir >> 22) & 0x3;
+ // full opcode
+ opcode1 = (ir >> 22) & 0x3FF;
+
+ switch(opc) {
+ case 0x0: {
+ // store
+ if(opcode1 == Arm64.OP_STORE_IMM_8) {
+ st1((int)rval, (int)regs[rdid]);
+ } else trap = (2+1);
+ rdid = -1;
+
+ break;
+ }
+ case 0x1: {
+ // LDR (immediate)
+ if(opcode1 == Arm64.OP_LOAD_IMM_8) {
+ rval = ld1s((int)rval);
+ } else trap = (2+1);
+ rdid = -1;
+ break;
+ }
+ default: trap = (2+1);
+ }
+ break;
+ }
+ case 0x54: {
+ // b.cond
+ int imm19 = ir << 8 >> 13;
+ imm19 = pc + (imm19 * 4) - 4;
+ rdid = -1;
+
+ switch(ir & 0xF) {
+ case 0x0: if(Z) pc = imm19; break; // eq
+ case 0x1: if(!Z) pc = imm19; break; // ne
+ case 0x2: if(C) pc = imm19; break; // cs
+ case 0x3: if(!C) pc = imm19; break;
+ case 0x4: if(N) pc = imm19; break; // mi
+ case 0x5: if(!N) pc = imm19; break; // pl
+ case 0x6: if(V) pc = imm19; break; // vs
+ case 0x7: if(!V) pc = imm19; break; // vc
+ case 0x8: if(C && !Z) pc = imm19; break; // hi
+ case 0x9: if(!C || Z) pc = imm19; break; // ls
+ case 0xA: if(N == V) pc = imm19; break; // ge
+ case 0xB: if(N != V) pc = imm19; break; // lt
+ case 0xC: if(!Z && N == V) pc = imm19; break; // gt
+ case 0xD: if(Z || N != V) pc = imm19; break; // le
+ case 0xE: pc = imm19; break; // always executed(al)
+ default: trap = (2+1);
+ }
+ break;
+ }
+
+ case 0x5c: {
+ // ldr
+ is_f = true;
+ // address = delta
+ int address = ir << 8 >> 13;
+ address *= 4;
+ if(address >= _buf.length-3) trap = (5 + 1);
+ frval = ld8f(pc + address);
+ break;
+ }
+
+ case 0x78: {
+ // STRH/LDRH(register)
+ int opc = ir >> 21 & 0x7;
+ int rn = (ir >> 5) & 0x1F;
+ int rm = (ir >> 16) & 0x1F;
+ // full opcode
+ opcode1 = (ir >> 21) & 0x7FF;
+ switch(opc) {
+ case 0x1: {
+ // store
+ if(opcode1 == Arm64.OP_STORE_R_16) {
+ st1((int)regs[rn] + (int)regs[rm], (int)regs[rdid]);
+ }else {
+ trap = (2+1);
+ }
+ rdid = -1;
+ break;
+ }
+ case 0x3: {
+ // load
+ if(opcode1 == Arm64.OP_LOAD_R_16) {
+ rval = ld1s((int)regs[rn] + (int)regs[rm]);
+ } else {
+ trap = (2+1);
+ }
+ break;
+ }
+ default: trap = (2+1);
+ }
+ break;
+ }
+
+ case 0x79: {
+ // STRH/LDRH(immediate)
+ int base = ir >> 5 & 0x1F;
+ int imm = (ir >> 10) & 0xFFF;
+ imm *= 2;
+ rval = regs[base] + imm;
+ int opc = (ir >> 22) & 0x3;
+ // full opcode
+ opcode1 = (ir >> 22) & 0x3FF;
+
+ switch(opc) {
+ case 0x0: {
+ // store
+ if(opcode1 == Arm64.OP_STORE_IMM_16) {
+ st2((int)rval, (int)regs[rdid]);
+ } else trap = (2+1);
+ rdid = -1;
+
+ break;
+ }
+ case 0x1: {
+ // LDR (immediate)
+ if(opcode1 == Arm64.OP_LOAD_IMM_16) {
+ rval = ld2s((int)rval);
+ } else trap = (2+1);
+ rdid = -1;
+ break;
+ }
+ default: trap = (2+1);
+ }
+ break;
+ }
+
+ case 0x8A: { // AND
+ int rn = (ir >> 5) & 0x1F;
+ int rm = (ir >> 16) & 0x1F;
+ rval = regs[rn] & regs[rm];
+ break;
+ }
+
+ case 0x8B, 0xCB: {
+ // add/sub(register), unshifted form emitted by Simple
+ int rn = (ir >> 5) & 0x1F;
+ int rm = (ir >> 16) & 0x1F;
+ long lhs = regs[rn];
+ long rhs = regs[rm];
+ rval = opcode1==0x8B ? lhs + rhs : lhs - rhs;
+ break;
+ }
+
+ case 0x90, 0xB0, 0xD0, 0xF0: { // adrp
+ int immhi = ( ir << 8 ) >>> (8+5);
+ int immlo = (ir>>29)& 3;
+ int imm = ((immhi<<2) | immlo);
+ imm = (imm << 11) >> 11;
+ imm <<= 12;
+ rval = (pc & ~0xFFF) + imm;
+ break;
+ }
+
+ case 0x91, 0xD1: { // ADD/SUB (immediate), unsigned imm12, optional LSL #12
+ int rn = (ir >> 5) & 0x1F;
+ long immediate = (ir >>> 10) & 0xFFF;
+ if( (ir & (1<<22))!=0 ) immediate <<= 12;
+ rval = opcode1==0x91 ? regs[rn] + immediate : regs[rn] - immediate;
+ break;
+ }
+ case 0x92: { // MOVN or AND (immediate)
+ if( (ir & 0x00800000)!=0 ) {
+ int shift = ((ir >> 21) & 3)*16;
+ rval = ~(((ir >> 5) & 0xFFFFL) << shift);
+ break;
+ }
+ int imm12 = (ir >> 10) & 0x1FFF;
+ long immediate = Arm64.decodeImm12(imm12);
+ int rn = (ir >> 5) & 0x1F;
+ rval = regs[rn] & immediate;
+ break;
+ }
+ case 0xB2: { // ORR (immediate)
+ int rn = (ir >>> 5) & 31;
+ rval = (rn==31 ? 0 : regs[rn]) | Arm64.decodeImm12((ir >>> 10) & 0x1FFF);
+ break;
+ }
+ case 0x93, 0xD3: { // SBFM/UBFM: shifts and signed/unsigned extracts
+ int immr = (ir >>> 16) & 63;
+ int imms = (ir >>> 10) & 63;
+ int rn = (ir >>> 5) & 31;
+ boolean signed = (ir & 0x40000000)==0;
+ long value = regs[rn] << (63-imms);
+ value = signed ? value >> (63-imms) : value >>> (63-imms);
+ rval = immr > imms ? value << (64-immr)
+ : signed ? value >> immr : value >>> immr;
+ break;
+ }
+
+ case 0x94, 0x95, 0x96, 0x97: { // bl
+ rdid = -1; // BL writes only X30, not a displacement-selected register.
+ rval = pc + 4;
+ regs[30] = rval;
+ int imm26 = (ir & 0x03FFFFFF);
+ int imm = (imm26 << 6) >> 6;
+ pc = pc + (imm << 2) ;
+ if( pc == _calloc ) {
+ assert (_heap&7) == 0; // 8-byte aligned
+ long size = regs[0]*regs[1];
+ size = (size+7) & -8; // 8-byte aligned
+ regs[0] = _heap;
+ _heap += (int)size;
+ pc = (int)rval;
+ }
+ if( pc == _write ) {
+ ByteArrayOutputStream baos = switch((int)regs[0]) {
+ case 1 -> _stdout==null ? (_stdout = new ByteArrayOutputStream()) : _stdout;
+ case 2 -> _stderr==null ? (_stderr = new ByteArrayOutputStream()) : _stderr;
+ default -> throw new IllegalArgumentException();
+ };
+ baos.write(_buf,(int)regs[1],(int)regs[2]);
+ regs[0] = regs[2];
+ pc = (int)rval;
+ }
+ pc -= 4;
+ rdid = -1;
+ break;
+ }
+
+ case 0x9A: {
+ if( (ir & 0xFFE0FC00)==0x9AC00C00 ) { // SDIV Xd,Xn,Xm
+ int rn=(ir >>> 5)&31, rm=(ir >>> 16)&31;
+ long lhs=rn==31 ? 0 : regs[rn], rhs=rm==31 ? 0 : regs[rm];
+ rval = rhs==0 ? 0 : lhs/rhs;
+ break;
+ }
+ int encodedCond = (ir >> 12) & 0xF;
+ int decodedCond = encodedCond ^ 1;
+ // conditional select(csel)
+ boolean cond = switch (decodedCond) {
+ case 0x0 -> Z ; // eq
+ case 0x1 -> !Z ; // ne
+ case 0x2 -> C ; // cs
+ case 0x3 -> !C ; // cc
+ case 0x4 -> N ; // mi
+ case 0x5 -> !N ; // pl
+ case 0x6 -> V ; // vs
+ case 0x7 -> !V ; // vc
+ case 0x8 -> ( C && !Z ) ; // hi
+ case 0x9 -> (!C || Z ) ; // ls
+ case 0xA -> ( N == V) ; // ge
+ case 0xB -> ( N != V) ; // lt
+ case 0xC -> (!Z && N == V) ; // gt
+ case 0xD -> ( Z || N != V ); // le
+ case 0xE -> true ; // always
+ case 0xF -> false ; // never
+ default -> throw new UnsupportedOperationException();
+ };
+ rval = cond ? 1 : 0;
+ break;
+ }
+ case 0x9B: { // mul
+ int rn = (ir >> 5) & 0x1F;
+ int rm = (ir >> 16) & 0x1F;
+ rval = regs[rn] * regs[rm];
+ break;
+ }
+ case 0x9E: {
+ int rs1 = (ir >> 5) & 0x1F;
+ if( (ir & 0xFFFFFC00)==0x9E780000 ) { // FCVTZS Xd,Dn
+ rval = (long)fregs[rs1];
+ } else { // SCVTF
+ is_f = true;
+ frval = (double)regs[rs1];
+ }
+ break;
+ }
+
+ case 0xAA: {
+ // MOV(register)
+ int rm = (ir >> 16) & 0x1F;
+ rval = regs[rm];
+ break;
+ }
+
+ case 0xB8: {
+ // LDR/STR (register + index) 32 bits
+ int opc = ir >> 21 & 0x7;
+ int rn = (ir >> 5) & 0x1F;
+ int rm = (ir >> 16) & 0x1F;
+ // full opcode
+ opcode1 = (ir >> 21) & 0x7FF;
+ switch(opc) {
+ case 0x1: {
+ if(opcode1 == Arm64.OP_STORE_R_32) {
+ st4((int)regs[rn] + (int)regs[rm], (int)regs[rdid]);
+ }else {
+ trap = (2+1);
+ }
+ // store(expected to store the length here)
+
+ rdid = -1;
+ break;
+ }
+ case 0x3: {
+ // load
+ if( opcode1 == Arm64.OP_LOAD_R_32) {
+ rval = ld4s((int) (regs[rn] + regs[rm]));
+ } else {
+ trap = (2+1);
+ }
+ break;
+ }
+ default: trap = (2+1);
+ }
+
+ break;
+ }
+
+ case 0xB9: {
+ // str/ldr(immediate) 32 bits
+ int base = ir >> 5 & 0x1F;
+ int imm = (ir >> 10) & 0xFFF;
+ imm *= 4;
+
+ rval = regs[base] + imm;
+ int opc = (ir >> 22) & 0x3;
+
+ opcode1 = (ir >> 22) & 0x3FF;
+ switch(opc) {
+ case 0x0: {
+ // store
+ if(opcode1 == Arm64.OP_STORE_IMM_32) {
+ st4((int)rval, (int)regs[rdid]);
+ } else trap = (2+1);
+
+ rdid = -1;
+ break;
+ }
+ case 0x1: {
+ // LDR (immediate)
+ if(opcode1 == Arm64.OP_LOAD_IMM_32) {
+ rval = ld4s((int) rval);
+ } else trap = (2+1);
+ break;
+ }
+ default: trap = (2+1);
+ }
+ break;
+ }
+
+ case 0xBC: {
+ // LDR/STR (register + index) float 32 bits
+ int opc = ir >> 21 & 0x7;
+ int rn = (ir >> 5) & 0x1F;
+ int rm = (ir >> 16) & 0x1F;
+ is_f = true;
+ // full opcode
+ opcode1 = (ir >> 21) & 0x7FF;
+ switch(opc) {
+ case 0x1: {
+ if(opcode1 == Arm64.OPF_STORE_R_32) {
+ st4((int)regs[rn] + (int)regs[rm], Float.floatToRawIntBits((float)fregs[rdid]));
+ }else {
+ trap = (2+1);
+ }
+ // store(expected to store the length here)
+
+ rdid = -1;
+ break;
+ }
+ case 0x3: {
+ // load
+ if(opcode1 == Arm64.OPF_LOAD_R_32) {
+ frval = ld4f((int) (regs[rn] + regs[rm]));
+ } else {
+ trap = (2+1);
+ }
+ break;
+ }
+ default: trap = (2+1);
+ }
+
+ break;
+ }
+
+ case 0xBD: {
+ // LDR/STR(immediate) 32 bits floats
+ int base = ir >> 5 & 0x1F;
+ int imm = (ir >> 10) & 0xFFF;
+
+ imm *= 4;
+ is_f = true;
+ rval = regs[base] + imm;
+ int opc = (ir >> 22) & 0x3;
+ // full opcode
+ opcode1 = (ir >> 22) & 0x3FF;
+ switch(opc) {
+ case 0x0: {
+ // store
+ if(opcode1 == Arm64.OPF_STORE_IMM_32) {
+ st4((int)rval, Float.floatToRawIntBits((float)fregs[rdid]));
+ } else trap = (2+1);
+
+ rdid = -1;
+ break;
+ }
+ case 0x1: {
+ // LDR (immediate)
+ if(opcode1 == Arm64.OPF_LOAD_IMM_32) {
+ frval = ld4f((int)rval);
+ } else trap = (2+1);
+ break;
+ }
+ default: trap = (2+1);
+ }
+ break;
+ }
+
+ case 0xD2: // MOVZ
+ case 0xF2: { // MOVK preserves the other halfwords
+ // movz
+ // get immediate
+ int shift = ((ir >> 21) & 3)*16;
+ rval = ((ir >> 5) & 0xFFFFL) << shift;
+ if( opcode1==0xF2 ) rval |= regs[rdid] & ~(0xFFFFL << shift);
+ break;
+ }
+
+ case 0xEB: {
+ // subs(shifted register)
+ int rn = (ir >> 5) & 0x1F;
+ int rm = (ir >> 16) & 0x1F;
+ long lhs = regs[rn];
+ long rhs = regs[rm];
+ rval = lhs - rhs;
+
+ // set flags as a side effect
+ Z = (rval == 0);
+ N = (rval < 0);
+ C = lhs >= rhs;
+
+ boolean sign_lhs = (lhs >>> 63) != 0;
+ boolean sign_rhs = (rhs >>> 63) != 0;
+ boolean sign_res = (rval >>> 63) != 0;
+ V = (sign_lhs != sign_rhs) && (sign_res != sign_lhs);
+ rdid = -1;
+ break;
+ }
+
+ case 0xD6: {
+ int op = ir & 0xFFFFFC1F;
+ int rn = (ir >>> 5) & 31;
+ int target = (int)regs[rn]; // Read before BLR overwrites X30.
+ rdid = -1;
+ if( op==0xD63F0000 ) regs[30] = pc+4; // BLR
+ else if( op!=0xD65F0000 ) { trap = 3; break; } // RET
+ pc = target;
+ if( op==0xD65F0000 && pc==0 ) break outer;
+ pc -= 4;
+ break;
+ }
+
+ case 0xF1: {
+ // subs(immediate)
+ int rn = (ir >> 5) & 0x1F;
+ int immediate = ir << 10 >> 20;
+ long lhs = regs[rn];
+
+ rval = lhs - immediate;
+
+ // set flags as a side effect
+ Z = (rval == 0);
+ N = (rval < 0);
+ C = lhs >= immediate;
+
+ boolean sign_lhs = (lhs >>> 63) != 0;
+ boolean sign_rhs = (immediate >>> 63) != 0;
+ boolean sign_res = (rval >>> 63) != 0;
+ V = (sign_lhs != sign_rhs) && (sign_res != sign_lhs);
+ rdid = -1;
+ break;
+ }
+ case 0xF8: {
+ // LDR/STR (register + index) 64 bits
+ int opc = ir >> 21 & 0x7;
+ int rn = (ir >> 5) & 0x1F;
+ int rm = (ir >> 16) & 0x1F;
+ // full opcode
+ opcode1 = (ir >> 21) & 0x7FF;
+ switch(opc) {
+ case 0x1: {
+ if(opcode1 == Arm64.OP_STORE_R_64) {
+ st8((int)regs[rn] + (int)regs[rm], regs[rdid]);
+ }else {
+ trap = (2+1);
+ }
+ // store(expected to store the length here)
+
+ rdid = -1;
+ break;
+ }
+ case 0x3: {
+ // load
+ if(opcode1 == Arm64.OP_LOAD_R_64) {
+ rval = ld8((int) (regs[rn] + regs[rm]));
+ } else {
+ trap = (2+1);
+ }
+ break;
+ }
+ default: trap = (2+1);
+ }
+
+ break;
+ }
+ case 0xF9: {
+ // str/ldr(immediate) 64 bits
+ int base = ir >> 5 & 0x1F;
+ int imm = (ir >> 10) & 0xFFF;
+
+ imm *= 8;
+
+ rval = regs[base] + imm;
+ int opc = (ir >> 22) & 0x3;
+ // full opcode
+ opcode1 = (ir >> 22) & 0x3FF;
+ switch(opc) {
+ case 0x0: {
+ // store
+ if(opcode1 == Arm64.OP_STORE_IMM_64) {
+ st8((int)rval, regs[rdid]);
+ } else trap = (2+1);
+
+ rdid = -1;
+ break;
+ }
+ case 0x1: {
+ // LDR (immediate)
+ if(opcode1 == Arm64.OP_LOAD_IMM_64) {
+ rval = ld8((int)rval);
+ } else trap = (2+1);
+ break;
+ }
+ default: trap = (2+1);
+ }
+ break;
+ }
+
+ case 0xFC: {
+ // LDR/STR (register + index) float 64 bits
+ int opc = ir >> 21 & 0x7;
+ int rn = (ir >> 5) & 0x1F;
+ int rm = (ir >> 16) & 0x1F;
+ is_f = true;
+ // full opcode
+ opcode1 = (ir >> 21) & 0x7FF;
+ switch(opc) {
+ case 0x1: {
+ if(opcode1 == Arm64.OPF_STORE_R_64) {
+ st8((int)regs[rn] + (int)regs[rm], Double.doubleToRawLongBits(fregs[rdid]));
+ }else {
+ trap = (2+1);
+ }
+ // store(expected to store the length here)
+
+ rdid = -1;
+ break;
+ }
+ case 0x3: {
+ // load
+ if(opcode1 == Arm64.OPF_LOAD_R_64) {
+ frval = ld8f((int) (regs[rn] + regs[rm]));
+ } else {
+ trap = (2+1);
+ }
+ break;
+ }
+ default: trap = (2+1);
+ }
+
+ break;
+ }
+ case 0xFD: {
+ // LDR/STR(immediate) 64 bits floats
+ int base = ir >> 5 & 0x1F;
+ int imm = (ir >> 10) & 0xFFF;
+
+ imm *= 8;
+ is_f = true;
+ rval = regs[base] + imm;
+ int opc = (ir >> 22) & 0x3;
+ // full opcode
+ opcode1 = (ir >> 22) & 0x3FF;
+ switch(opc) {
+ case 0x0: {
+ // store
+ if(opcode1 == Arm64.OPF_STORE_IMM_64) {
+ st8((int)rval, Double.doubleToRawLongBits(fregs[rdid]));
+ } else trap = (2+1);
+
+ rdid = -1;
+ break;
+ }
+ case 0x1: {
+ // LDR (immediate)
+ if(opcode1 == Arm64.OPF_LOAD_IMM_64) {
+ frval = ld8f((int)rval);
+ } else trap = (2+1);
+ break;
+ }
+ default: trap = (2+1);
+ }
+ break;
+ }
+
+ default: trap = (2+1);
+ }
+
+ if(trap != 0) {
+ _pc = pc;
+ break;
+ }
+ if(rdid != -1) {
+ if(is_f) fregs[rdid] = frval;
+ // Register 31 is XZR except for ADD/SUB immediate, which writes SP.
+ else if( rdid!=31 || opcode1==0x91 || opcode1==0xD1 ) regs[rdid] = rval;
+ }
+
+ pc += 4;
+ }
+ // Handle traps and interrupts.
+ if(trap != 0)
+ return trap;
+ _cycle = cycle;
+ _pc = pc;
+ return 0;
+ }
+
+
+}
diff --git a/isa/src/test-support/java/com/seaofnodes/isa/eval/EvalRisc5.java b/isa/src/test-support/java/com/seaofnodes/isa/eval/EvalRisc5.java
new file mode 100644
index 00000000..e6860ccd
--- /dev/null
+++ b/isa/src/test-support/java/com/seaofnodes/isa/eval/EvalRisc5.java
@@ -0,0 +1,399 @@
+ package com.seaofnodes.isa.eval;
+
+/** Simple RISC5 CPU Emulator
+ *
+ * Original works credit Charles Lohr.
+ * This is a complete rewrite in Java.
+ *
+ * Load code into memory and go!
+ */
+
+import java.io.ByteArrayOutputStream;
+import java.util.Arrays;
+
+public class EvalRisc5 {
+
+ // Memory image, always 0-based
+ public final byte[] _buf;
+
+ // GPRs
+ public final long[] regs;
+ // FRs
+ public final double[] fregs;
+ // PC
+ public int _pc;
+
+ // Start of free memory for allocation
+ public int _heap;
+
+ // Standard out and err streams
+ public ByteArrayOutputStream _stdout, _stderr;
+
+ // Cycle counters
+ public int _cycle;
+
+ // Default test-runtime entry points; callers can supply another link convention.
+ private final int _calloc, _write;
+ public EvalRisc5(byte[] buf, int stackSize) { this(buf,stackSize,-4,-8); }
+ public EvalRisc5(byte[] buf, int stackSize, int calloc, int write) {
+ _calloc = calloc;
+ _write = write;
+ _buf = buf;
+ regs = new long[32];
+ fregs = new double[32];
+ // Stack grows down, heap grows up
+ regs[2] = _heap = stackSize;
+ _pc = 0;
+ }
+
+ // Little endian
+ public int ld1s(int x) { return _buf[x ] ; }
+ public int ld1z(int x) { return _buf[x ]&0xFF; }
+ public int ld2s(int x) { return ld1z(x) | ld1s(x+1)<< 8; }
+ public int ld2z(int x) { return ld1z(x) | ld1z(x+1)<< 8; }
+ public int ld4s(int x) { return ld2z(x) | ld2s(x+2)<<16; }
+ public long ld8 (int x) { return (long)ld4s(x)&0xFFFFFFFFL | ((long)ld4s(x+4))<<32; }
+
+ public float ld4f(int x) { return Float.intBitsToFloat(ld4s(x)); }
+ public double ld8f(int x) { return Double.longBitsToDouble(ld8(x)); }
+
+
+ public void st1(int x, int val) { _buf[x] = (byte)val; }
+ public void st2(int x, int val) { st1(x,val); st1(x+1,val>> 8); }
+ public void st4(int x, int val) {st2(x,val); st2(x+2,val>>16); }
+ public void st8(int x, long val) { st4(x,(int)val); st4(x+4,(int)(val>>32)); }
+
+ // Note: only a few bits are used. (Machine = 3, User = 0)
+ // Bits 0..1 = privilege.
+ // Bit 2 = WFI (Wait for interrupt)
+ // Bit 3+ = Load/Store reservation LSBs.
+ public int _extraflags;
+
+ public int step( int maxops ) {
+ int trap = 0;
+ long rval = 0;
+ double frval = 0;
+ int pc = _pc;
+ int cycle = _cycle;
+ boolean is_f = false;
+ outer:
+ for( int icount = 0; icount < maxops; icount++ ) {
+ int ir = 0;
+ rval = 0;
+ cycle++;
+
+ if( pc >= _buf.length ) {
+ trap = 1 + 1; // Handle access violation on instruction read.
+ break;
+ }
+ if( (pc & 3)!=0 ) {
+ trap = 1; // Handle PC-misaligned access
+ break;
+ }
+ // Load instruction from image buffer at PC
+ ir = ld4s( pc );
+ int rdid = (ir >> 7) & 0x1f;
+
+ switch( ir & 0x7f ) {
+ case 0x37: // LUI (0b0110111)
+ rval = ( ir & 0xfffff000 );
+ break;
+ case 0x17: // AUIPC (0b0010111)
+ rval = pc + ( ir & 0xfffff000 );
+ break;
+ case 0x6F: { // JAL (0b1101111)
+ int reladdy = ((ir & 0x80000000)>>11) | ((ir & 0x7fe00000)>>20) | ((ir & 0x00100000)>>9) | ((ir&0x000ff000));
+ if( (reladdy & 0x00100000)!=0 ) reladdy |= 0xffe00000; // Sign extension.
+ rval = pc + 4;
+ pc = pc + reladdy - 4;
+ if( pc+4 == _write ) {
+ ByteArrayOutputStream baos = switch((int)regs[10]) {
+ case 1 -> _stdout==null ? (_stdout = new ByteArrayOutputStream()) : _stdout;
+ case 2 -> _stderr==null ? (_stderr = new ByteArrayOutputStream()) : _stderr;
+ default -> throw new IllegalArgumentException();
+ };
+ baos.write(_buf,(int)regs[11],(int)regs[12]);
+ regs[10] = regs[12];
+ rdid = 0;
+ pc = (int)(rval - 4); // Unwind PC, as-if returned from write
+ }
+ break;
+ }
+ case 0x67: { // JALR (0b1100111)
+ int imm_se = ir >> 20; // Sign extend 12 bits
+ rval = pc + 4;
+ pc = ((int)regs[ (ir >> 15) & 0x1f ] + imm_se) & ~1;
+ // Return from top-level ; exit sim
+ if( pc==0 ) {
+ if( rdid!=0 ) regs[rdid] = rval; // Write PC into register before exit
+ break outer;
+ }
+ // Inline CALLOC effect
+ if( pc == _calloc ) {
+ assert (_heap&7) == 0; // 8-byte aligned
+ int size = (int)(regs[10]*regs[11]);
+ size = (size+7) & -8; // 8-byte aligned
+ regs[10] = _heap; // Return next free address
+ _heap += size;
+ pc = (int)(rval - 4); // Unwind PC, as-if returned from calloc
+ } else
+ pc -= 4;
+ break;
+ }
+ case 0x63: { // Branch (0b1100011)
+ int immm4 = ((ir & 0xf00)>>7) | ((ir & 0x7e000000)>>20) | ((ir & 0x80) << 4) | ((ir >> 31)<<12);
+ if( (immm4 & 0x1000)!=0 ) immm4 |= 0xffffe000;
+ long rs1 = regs[(ir >> 15) & 0x1f];
+ long rs2 = regs[(ir >> 20) & 0x1f];
+ immm4 = pc + immm4 - 4;
+ rdid = 0;
+ if( switch( (ir >> 12) & 0x7 ) {
+ case 0 -> rs1 == rs2;
+ case 1 -> rs1 != rs2;
+ case 4 -> rs1 < rs2;
+ case 5 -> rs1 >= rs2;
+ case 6 -> Long.compareUnsigned(rs1,rs2) < 0; //BLTU
+ case 7 -> Long.compareUnsigned(rs1,rs2) >= 0; //BGEU
+ default -> { trap = (2+1); yield false; }
+ } ) pc = immm4;
+ break;
+ }
+ case 0x03: { // Load (0b0000011)
+ int rs1 = (int)regs[(ir >> 15) & 0x1f]; // Address chop to 32b
+ int imm = ir >> 20;
+ int imm_se = imm | (( (imm & 0x800)!=0 ) ? 0xfffff000 : 0 );
+ int rsval = rs1 + imm_se;
+
+ if( rsval >= _buf.length-3 ) {
+ trap = (5+1);
+ rval = rsval;
+ } else {
+ switch( ( ir >> 12 ) & 0x7 ) {
+ //LB, LH, LW, LBU, LHU
+ case 0: rval = ld1s( rsval ); break;
+ case 1: rval = ld2s( rsval ); break;
+ case 2: rval = ld4s( rsval ); break;
+ case 3: rval = ld8 ( rsval ); break;
+ case 4: rval = ld1z( rsval ); break;
+ case 5: rval = ld2z( rsval ); break;
+ case 6: rval = ld4s( rsval ); break;
+ default: trap = (2+1);
+ }
+ }
+ break;
+ }
+ // floats
+ case 0x7: {
+ is_f = true;
+ int rs1 = (int)regs[(ir >> 15) & 0x1f]; // Address chop to 32b
+ int imm = ir >> 20;
+ int imm_se = imm | (( (imm & 0x800)!=0 ) ? 0xfffff000 : 0 );
+ int rsval = rs1 + imm_se;
+
+ if( rsval >= _buf.length-3 ) {
+ trap = (5+1);
+ rval = rsval;
+ } else {
+ switch( ( ir >> 12 ) & 0x7 ) {
+ case 2: frval = ld4f( rsval ); break;
+ case 3: frval = ld8f( rsval ); break;
+ default: trap = (2+1);
+ }
+ }
+ break;
+ }
+
+ case 0x27: { // FSW/FSD
+ int rs1=(int)regs[(ir >>> 15)&31], rs2=(ir >>> 20)&31;
+ int addr=rs1+((ir >> 25)<<5)+((ir >>> 7)&31);
+ switch((ir >>> 12)&7) {
+ case 2: st4(addr,Float.floatToRawIntBits((float)fregs[rs2])); break;
+ case 3: st8(addr,Double.doubleToRawLongBits(fregs[rs2])); break;
+ default: trap=3;
+ }
+ rdid=0;
+ break;
+ }
+ case 0x23: { // Store 0b0100011
+ int rs1 = (int)regs[(ir >> 15) & 0x1f]; // Address chop to 32b
+ long rs2 = regs[(ir >> 20) & 0x1f];
+ int addy = ( ( ir >> 7 ) & 0x1f ) | ( ( ir & 0xfe000000 ) >> 20 );
+ if( (addy & 0x800)!=0 ) addy |= 0xfffff000;
+ addy += rs1;
+ rdid = 0;
+ if( addy >= _buf.length-3 ) {
+ trap = (7+1); // Store access fault.
+ rval = addy;
+ } else {
+ switch( ( ir >> 12 ) & 0x7 ) { //SB, SH, SW
+ case 0: st1( addy, (int)rs2 ); break;
+ case 1: st2( addy, (int)rs2 ); break;
+ case 2: st4( addy, (int)rs2 ); break;
+ case 3: st8( addy, rs2 ); break;
+ default: trap = (2+1);
+ }
+ }
+ break;
+ }
+ case 0x13: // Op-immediate 0b0010011
+ case 0x33: { // Op 0b0110011
+ int imm = ir >> 20;
+ imm = imm | (( (imm & 0x800)!=0 ) ? 0xfffff000 : 0);
+ long rs1 = regs[(ir >> 15) & 0x1f];
+ boolean is_reg = (ir & 0x20)!=0;
+ long rs2 = is_reg ? regs[imm & 0x1f] : imm;
+
+ // Insert the detection here
+ int rs1id = (ir >> 15) & 0x1f;
+ int funct3 = (ir >> 12) & 0x7;
+
+ if( is_reg && ( ir & 0x02000000 )!=0 ) {
+ switch( (ir>>12)&7 ) { //0x02000000 = RV32M
+ case 0: rval = rs1 * rs2; break; // MUL
+ case 1: rval = (int)((long)((int)rs1) * (long)((int)rs2)) >> 32; break; // MULH
+ case 2: rval = (int)((long)((int)rs1) * (long)rs2) >> 32; break; // MULHSU
+ case 3: rval = (int)((long)rs1 * (long)rs2) >> 32; break; // MULHU
+ case 4: if( rs2 == 0 ) rval = -1; else rval = ((int)rs1 == Integer.MIN_VALUE && (int)rs2 == -1) ? rs1 : ((int)rs1 / (int)rs2); break; // DIV
+ case 5: if( rs2 == 0 ) rval = 0xffffffff; else rval = rs1 / rs2; break; // DIVU
+ case 6: if( rs2 == 0 ) rval = rs1; else rval = ((int)rs1 == Integer.MIN_VALUE && (int)rs2 == -1) ? 0 : ((int)((int)rs1 % (int)rs2)); break; // REM
+ case 7: if( rs2 == 0 ) rval = rs1; else rval = rs1 % rs2; break; // REMU
+ }
+ } else {
+ rval = switch( (ir >> 12) & 7 ) { // These could be either op-immediate or op commands. Be careful.
+ case 0 -> (is_reg && (ir & 0x40000000) != 0) ? (rs1 - rs2) : (rs1 + rs2);
+ case 1 -> rs1 << (rs2 & 0x3F);
+ case 2 -> (rs1 < rs2) ? 1 : 0;
+ case 3 -> Long.compareUnsigned(rs1,rs2)<0 ? 1 : 0;
+ case 4 -> rs1 ^ rs2;
+ case 5 -> (ir & 0x40000000) != 0 ? (rs1 >> (rs2 & 0x3F)) : (rs1 >>> (rs2 & 0x3F));
+ case 6 -> rs1 | rs2;
+ case 7 -> rs1 & rs2;
+ default -> rval;
+ };
+ }
+ break;
+ }
+ case 0x0f: // 0b0001111
+ rdid = 0; // fencetype = (ir >> 12) & 0b111; We ignore fences in this impl.
+ break;
+ case 0x73: // Zifencei+Zicsr (0b1110011)
+ trap = (2+1); // Note micrrop 0b100 == undefined.
+ break;
+
+ case 0x2f: { // RV32A (0b00101111)
+ long rs1 = regs[(ir >> 15) & 0x1f];
+ long rs2 = regs[(ir >> 20) & 0x1f];
+ int irmid = ( ir>>27 ) & 0x1f;
+
+ // We don't implement load/store from UART or CLNT with RV32A here.
+ if( rs1 >= _buf.length-3 ) {
+ trap = (7+1); //Store/AMO access fault
+ rval = rs1;
+ } else {
+ rval = ld4s( (int)rs1 );
+
+ // Referenced a little bit of https://github.com/franzflasch/riscv_em/blob/master/src/core/core.c
+ boolean dowrite = true;
+ switch( irmid ) {
+ case 2: //LR.W (0b00010)
+ dowrite = false;
+ _extraflags = (_extraflags & 0x07) | (int)(rs1<<3);
+ break;
+ case 3: { //SC.W (0b00011) (Make sure we have a slot, and, it's valid)
+ boolean xflag = _extraflags >> 3 != ( rs1 & 0x1fffffff );
+ rval = xflag ? 1 : 0; // Validate that our reservation slot is OK.
+ dowrite = !xflag; // Only write if slot is valid.
+ break;
+ }
+ case 0: rs2 += rval; break; // AMOADD.W (0b00000)
+ case 1: break; // AMOSWAP.W (0b00001)
+ case 4: rs2 ^= rval; break; // AMOXOR.W (0b00100)
+ case 8: rs2 |= rval; break; // AMOOR.W (0b01000)
+ case 12: rs2 &= rval; break; // AMOAND.W (0b01100)
+ case 16: rs2 = Math.min( rs2, rval ); break; //AMOMIN.W (0b10000)
+ case 20: rs2 = Math.max( rs2, rval ); break; //AMOMAX.W (0b10100)
+ case 24: rs2 = Math.min( rs2, rval ); break; //AMOMINU.W (0b11000)
+ case 28: rs2 = Math.max( rs2, rval ); break; //AMOMAXU.W (0b11100)
+ default: trap = (2+1); dowrite = false; break; //Not supported.
+ }
+ if( dowrite ) st4( (int)rs1, (int)rs2 );
+ }
+ break;
+ }
+
+ case 0x53: {
+ is_f = true;
+ int rs1 = (ir >> 15) & 0x1F;
+ int rs2 = (ir >> 20) & 0x1F;
+ double lhs = fregs[rs1];
+ double rhs = fregs[rs2];
+ int func5 = (ir >> 27) & 0x1F;
+ int func2 = (ir >> 25) & 0x3;
+ if( func2 != 1 ) throw new UnsupportedOperationException(); // 1==double
+ switch( func5 ) {
+ case 0: frval = lhs + rhs; break; // fadd.d
+ case 1: frval = lhs - rhs; break; // fsub.d
+ case 2: frval = lhs * rhs; break; // fmul.d
+ case 3: frval = lhs / rhs; break; // fdiv.d
+ case 5: frval = Math.min(lhs,rhs); break; // fmin.d, also used for move
+ case 20:
+ is_f = false; // Output into a GPR
+ rval = switch( (ir >> 12) & 7 ) {
+ case 0 -> lhs <= rhs ? 1 : 0;
+ case 1 -> lhs < rhs ? 1 : 0;
+ case 2 -> lhs == rhs ? 1 : 0;
+ default -> throw new UnsupportedOperationException();
+ };
+ break;
+ case 24: // FCVT.L.D, RTZ
+ if( rs2!=2 || ((ir>>12)&7)!=1 ) throw new UnsupportedOperationException();
+ is_f = false;
+ rval = Double.isNaN(lhs) ? Long.MAX_VALUE : (long)lhs;
+ break;
+ case 26: frval = (double)regs[rs1]; break; // fcvt.d.l
+ case 28: { // FCLASS.D
+ if( rs2!=0 || ((ir>>12)&7)!=1 ) throw new UnsupportedOperationException();
+ is_f = false;
+ long bits = Double.doubleToRawLongBits(lhs);
+ boolean neg = bits<0;
+ int kind = Double.isNaN(lhs) ? ((bits&(1L<<51))==0 ? 8 : 9)
+ : Double.isInfinite(lhs) ? (neg ? 0 : 7)
+ : lhs==0 ? (neg ? 3 : 4)
+ : Math.abs(lhs)<Double.MIN_NORMAL ? (neg ? 2 : 5)
+ : (neg ? 1 : 6);
+ rval = 1L<<kind;
+ break;
+ }
+ default:
+ trap = (2+1); // Fault: Invalid opcode.
+ throw new UnsupportedOperationException();
+ }
+ break;
+ }
+
+ default: trap = (2+1); // Fault: Invalid opcode.
+ }
+
+ // If there was a trap, do NOT allow register writeback.
+ if( trap!=0 ) {
+ _pc = pc;
+ break;
+ }
+
+ if( rdid!=0 || is_f) {
+ if(is_f) {fregs[rdid] = frval; is_f = false;} // Write back register.
+ else regs[rdid] = rval; // Write back register.
+ }
+
+ pc += 4;
+ }
+
+ // Handle traps and interrupts.
+ if( trap!=0 )
+ return trap;
+
+ _cycle = cycle;
+ _pc = pc;
+ return 0;
+ }
+}
diff --git a/isa/src/test/java/com/seaofnodes/isa/RiscTest.java b/isa/src/test/java/com/seaofnodes/isa/RiscTest.java
new file mode 100644
index 00000000..d10f97a4
--- /dev/null
+++ b/isa/src/test/java/com/seaofnodes/isa/RiscTest.java
@@ -0,0 +1,122 @@
+package com.seaofnodes.isa;
+
+import com.seaofnodes.isa.eval.EvalArm64;
+import com.seaofnodes.isa.eval.EvalRisc5;
+
+/** Fixed instruction words plus execution checks of the shared test emulators. */
+public class RiscTest {
+ static void word(int expected, int actual) {
+ assert expected==actual : String.format("Expected %08x, got %08x",expected,actual);
+ }
+ public static void main(String[] args) {
+ // Instructions encountered by dynamic allocation measurements.
+ for( long[] pair : new long[][]{{17,3},{-17,3},{17,0},{Long.MIN_VALUE,-1}} ) {
+ var a=new EvalArm64(new byte[1024],512);
+ a.st4(4,0x9AC20C20); // sdiv x0,x1,x2
+ a._pc=4;a.regs[1]=pair[0];a.regs[2]=pair[1];
+ assert a.step(1)==0;
+ assert a.regs[0]==(pair[1]==0 ? 0 : pair[0]/pair[1]);
+ }
+ var discard=new EvalArm64(new byte[1024],512);
+ discard.st4(4,0x3862683F); // ldrb wzr,[x1,x2]; discard without changing SP
+ discard._pc=4;discard.regs[1]=100;discard.regs[2]=4;discard.st1(104,91);
+ assert discard.step(1)==0 && discard.regs[31]==512;
+ for(long value:new long[]{-1,0,1,Long.MIN_VALUE}) {
+ var unsigned=new EvalRisc5(new byte[1024],512);
+ unsigned.st4(4,0x00113193); // sltiu x3,x2,1 (seqz)
+ unsigned._pc=4;unsigned.regs[2]=value;
+ assert unsigned.step(1)==0;
+ assert unsigned.regs[3]==(value==0 ? 1 : 0);
+ }
+ var logical=new EvalArm64(new byte[1024],512);
+ logical.st4(4,0xB27F0020); // orr x0,x1,#2
+ logical.st4(8,0x9A9F17E2); // cset x2,eq; false must not retain prior result
+ logical._pc=4;logical.regs[1]=8;
+ assert logical.step(2)==0;
+ assert logical.regs[0]==10 && logical.regs[2]==0;
+ for(double rhs:new double[]{1,2,3,Double.NaN}) {
+ var r=new EvalRisc5(new byte[1024],512);
+ r.st4(4,0xA220A1D3); // feq.d x3,f1,f2
+ r.st4(8,0xA2209253); // flt.d x4,f1,f2
+ r._pc=4;r.fregs[1]=2;r.fregs[2]=rhs;
+ assert r.step(2)==0;
+ assert r.regs[3]==(2==rhs ? 1 : 0);
+ assert r.regs[4]==(2<rhs ? 1 : 0);
+ }
+ var store=new EvalRisc5(new byte[1024],512);
+ store.st4(4,0x00113827); // fsd f1,16(sp)
+ store.st4(8,0xFE112E27); // fsw f1,-4(sp)
+ store._pc=4;store.fregs[1]=-3.25;
+ assert store.step(2)==0;
+ assert store.ld8(528)==Double.doubleToRawLongBits(-3.25);
+ assert store.ld4s(508)==Float.floatToRawIntBits(-3.25f);
+ word(0x9E780020,Arm64.floatToInteger(1,0)); // FCVTZS x0,d1
+ for( double value : new double[]{0.0,-0.0,6.92,-6.92,0.99,-0.99,
+ 0x1p63,Math.nextDown(0x1p63),-0x1p63,Math.nextDown(-0x1p63),
+ Double.POSITIVE_INFINITY,Double.NEGATIVE_INFINITY,Double.NaN,
+ Double.longBitsToDouble(0xFFF0000000000001L)} ) {
+ var a=new EvalArm64(new byte[1024],512);
+ a.st4(4,0x9E780020);
+ a._pc=4;
+ a.fregs[1]=value;
+ a.step(1);
+ assert a.regs[0]==(long)value : "ARM conversion: "+value;
+
+ var bytes=new X86Test.Bytes();
+ RiscV.floatToInteger(bytes,10,1);
+ var r=new EvalRisc5(new byte[1024],512);
+ System.arraycopy(bytes.toByteArray(),0,r._buf,4,bytes.size());
+ r._pc=4;
+ r.fregs[1]=value;
+ // NaN takes four instructions; a finite value takes five.
+ r.step(Double.isNaN(value) ? 4 : 5);
+ assert r.regs[10]==(long)value : "RISC-V conversion: "+value;
+ }
+ word(0x8B020020,Arm64.r_reg(Arm64.OP_ADD,0,2,0,1,0)); // add x0,x1,x2
+ word(0x9100A420,Arm64.imm_inst(Arm64.OPI_ADD,41,1,0));
+ word(0xD2800540,Arm64.mov(Arm64.OP_MOVZ,0,42,0));
+ word(0xF9400BE0,Arm64.load_str_imm(Arm64.OP_LOAD_IMM_64,16,31,0,8));
+ word(0xF9000BE0,Arm64.load_str_imm(Arm64.OP_STORE_IMM_64,16,31,0,8));
+ word(0xD65F03C0,Arm64.ret(Arm64.OP_RET));
+ word(0x17FFFFFF,Arm64.b(Arm64.OP_UJMP,-4));
+ word(0x16000000,Arm64.b(Arm64.OP_UJMP,-(1<<27)));
+ word(0x54000040,Arm64.b_cond(Arm64.OP_BRANCH,8,Arm64.COND.EQ));
+ word(0x54800001,Arm64.b_cond(Arm64.OP_BRANCH,-(1<<20),Arm64.COND.NE));
+ word(0x5CFFFFE0,Arm64.load_pc(Arm64.OPF_ARM,-4,0));
+
+ word(0x002081B3,RiscV.r_type(RiscV.OP,3,0,1,2,0)); // add x3,x1,x2
+ word(0x02A00513,RiscV.i_type(RiscV.OP_IMM,10,0,0,42));
+ word(0x123451B7,RiscV.u_type(RiscV.OP_LUI,3,0x12345));
+ word(0x00B13823,RiscV.s_type(RiscV.OP_STORE,3,2,11,16));
+ word(0x00208463,RiscV.b_type(RiscV.OP_BRANCH,0,(short)1,(short)2,8));
+ word(0xFE208EE3,RiscV.b_type(RiscV.OP_BRANCH,0,(short)1,(short)2,-4));
+ word(0x008000EF,RiscV.j_type(RiscV.OP_JAL,1,8));
+ word(0xFFDFF0EF,RiscV.j_type(RiscV.OP_JAL,1,-4));
+ word(0x000800EF,RiscV.j_type(RiscV.OP_JAL,1,1<<19));
+ word(0x800000EF,RiscV.j_type(RiscV.OP_JAL,1,-(1<<20)));
+
+ // Constants exercise MOVN, MOVZ and multiple MOVK lanes, including all 0/1.
+ for( long x : new long[]{0,-1,42,0xFFFF0000FFFFL,0x1122334455667788L,Long.MIN_VALUE} ) {
+ var bytes=new X86Test.Bytes();
+ Arm64.constant(bytes,0,x);
+ byte[] image=new byte[1024];
+ System.arraycopy(bytes.toByteArray(),0,image,4,bytes.size());
+ var arm=new EvalArm64(image,512);
+ arm._pc=4;
+ arm.step(bytes.size()/4);
+ assert arm.regs[0]==x : String.format("Constant %016x became %016x",x,arm.regs[0]);
+ }
+ // Raw instructions make these emulator checks independent of the encoder.
+ var arm=new EvalArm64(new byte[1024],512);
+ arm.st4(4,0xD2800540); // mov x0,#42
+ arm._pc=4;
+ arm.step(1);
+ assert arm.regs[0]==42;
+ var risc=new EvalRisc5(new byte[1024],512);
+ risc.st4(4,0x02A00513); // addi a0,zero,42
+ risc._pc=4;
+ risc.step(1);
+ assert risc.regs[10]==42;
+ System.out.println("Shared ARM/RISC-V encoding and evaluator tests passed");
+ }
+}
diff --git a/isa/src/test/java/com/seaofnodes/isa/X86Test.java b/isa/src/test/java/com/seaofnodes/isa/X86Test.java
new file mode 100644
index 00000000..8af84ae8
--- /dev/null
+++ b/isa/src/test/java/com/seaofnodes/isa/X86Test.java
@@ -0,0 +1,128 @@
+package com.seaofnodes.isa;
+
+import java.io.ByteArrayOutputStream;
+import java.util.HexFormat;
+import java.util.function.Consumer;
+
+/** Fixed instruction bytes, independent of the compiler and register allocator. */
+public class X86Test {
+ static class Bytes extends ByteArrayOutputStream implements CodeSink {
+ @Override public Bytes add1(int value) { write(value); return this; }
+ }
+ static void check(String expected, Consumer<CodeSink> emit) {
+ var bytes=new Bytes();
+ emit.accept(bytes);
+ String actual=HexFormat.of().formatHex(bytes.toByteArray());
+ assert expected.equals(actual) : "Expected "+expected+", got "+actual;
+ }
+ public static void main(String[] args) {
+ check("f2480f2cc04883f801711c660f2ec07a1366480f7ec048c1f83f48f7d0480fbaf83feb034833c0",
+ b -> X86.floatToInteger(b,0,0));
+ check("f24d0f2cca4983f901711d66450f2ed27a13664d0f7ed149c1f93f49f7d1490fbaf93feb034d33c9",
+ b -> X86.floatToInteger(b,9,10));
+ check("4d03ca", b -> X86.reg(b,0x03,9,10));
+ check("4d0fafca", b -> X86.reg(b,0x0FAF,9,10));
+ check("4981c17fffffff", b -> X86.imm(b,0x81,0,9,-129));
+ check("4983c180", b -> X86.imm(b,0x81,0,9,-128));
+ check("4983c17f", b -> X86.imm(b,0x81,0,9,127));
+ check("4981c180000000", b -> X86.imm(b,0x81,0,9,128));
+ check("4d6bca80", b -> X86.imul(b,9,10,-128));
+ check("4d69ca80000000", b -> X86.imul(b,9,10,128));
+
+ // TEST; SETZ; MOVZX. Both overlapping and distinct input/output registers.
+ int[][] regs={{0,0},{1,1},{7,7},{9,9},{1,9},{9,1},{6,0},{15,8}};
+ String[] not={"4885c00f94c00fb6c0","4885c90f94c10fb6c9",
+ "4885ff400f94c7400fb6ff","4d85c9410f94c1450fb6c9",
+ "4d85c90f94c10fb6c9","4885c9410f94c1450fb6c9",
+ "4885c0400f94c6400fb6f6","4d85c0410f94c7450fb6ff"};
+ for( int i=0; i<regs.length; i++ ) {
+ int dst=regs[i][0], src=regs[i][1];
+ check(not[i],b -> X86.not(b,dst,src));
+ }
+
+ // SIB, rbp/r13 zero displacement, displacement boundaries, and an extended index.
+ check("488b0424", b -> X86.load(b,3,false,false,0,4,-1,0,0));
+ check("498b0424", b -> X86.load(b,3,false,false,0,12,-1,0,0));
+ check("488b4500", b -> X86.load(b,3,false,false,0,5,-1,0,0));
+ check("498b4500", b -> X86.load(b,3,false,false,0,13,-1,0,0));
+ check("488b437f", b -> X86.load(b,3,false,false,0,3,-1,127,0));
+ check("488b8380000000", b -> X86.load(b,3,false,false,0,3,-1,128,0));
+ check("488b4380", b -> X86.load(b,3,false,false,0,3,-1,-128,0));
+ check("488b837fffffff", b -> X86.load(b,3,false,false,0,3,-1,-129,0));
+ check("4b8b44e508", b -> X86.load(b,3,false,false,0,13,12,8,3));
+ check("4f8d4ca508", b -> X86.lea(b,9,13,12,8,2));
+ check("4e8d0ce520000000", b -> X86.lea(b,9,-1,12,32,3));
+
+ check("480fbe03", b -> X86.load(b,0,true,false,0,3,-1,0,0));
+ check("480fb603", b -> X86.load(b,0,false,false,0,3,-1,0,0));
+ check("480fbf03", b -> X86.load(b,1,true,false,0,3,-1,0,0));
+ check("480fb703", b -> X86.load(b,1,false,false,0,3,-1,0,0));
+ check("486303", b -> X86.load(b,2,true,false,0,3,-1,0,0));
+ check("8b03", b -> X86.load(b,2,false,false,0,3,-1,0,0));
+ check("f3470f104ca508", b -> X86.load(b,2,false,true,9,13,12,8,2));
+ check("f2470f104ca508", b -> X86.load(b,3,false,true,9,13,12,8,2));
+
+ check("408823", b -> X86.store(b,0,false,4,3,-1,0,0)); // spl, not ah
+ check("408833", b -> X86.store(b,0,false,6,3,-1,0,0)); // sil, not dh
+ check("44883b", b -> X86.store(b,0,false,15,3,-1,0,0));
+ check("6647894ca508", b -> X86.store(b,1,false,9,13,12,8,2));
+ check("47894ca508", b -> X86.store(b,2,false,9,13,12,8,2));
+ check("4f894ca508", b -> X86.store(b,3,false,9,13,12,8,2));
+ check("f3470f114ca508", b -> X86.store(b,2,true,9,13,12,8,2));
+ check("f2470f114ca508", b -> X86.store(b,3,true,9,13,12,8,2));
+ check("c603ff", b -> X86.storeImm(b,0,-1,3,-1,0,0));
+ check("6643c744a5083412", b -> X86.storeImm(b,1,0x1234,13,12,8,2));
+ check("c70378563412", b -> X86.storeImm(b,2,0x12345678,3,-1,0,0));
+ check("48c703ffffffff", b -> X86.storeImm(b,3,-1,3,-1,0,0));
+ check("803bff", b -> X86.cmpImm(b,0,-1,3,-1,0,0));
+ check("6643817ca5083412", b -> X86.cmpImm(b,1,0x1234,13,12,8,2));
+ check("813b78563412", b -> X86.cmpImm(b,2,0x12345678,3,-1,0,0));
+ check("48813b15cd5b07", b -> X86.cmpImm(b,3,123456789,3,-1,0,0));
+ check("49f7da", b -> X86.unary(b,0xF7,3,10));
+ check("49c1e1ff", b -> X86.shift(b,4,9,255));
+ check("489949f7fa", b -> X86.divide(b,10));
+ check("f2450f58ca", b -> X86.sse(b,0xF2,0x0F58,false,9,10));
+ check("f24d0f2aca", b -> X86.sse(b,0xF2,0x0F2A,true,9,10));
+ check("66450f2eca", b -> X86.sse(b,0x66,0x0F2E,false,9,10));
+ check("4d0fbeca", b -> X86.extend(b,8,true,9,10));
+ check("4c0fb6ce", b -> X86.extend(b,8,false,9,6));
+ check("6647394ca508", b -> X86.cmpMem(b,1,9,13,12,8,2,false));
+ check("66473b4ca508", b -> X86.cmpMem(b,1,9,13,12,8,2,true));
+ check("403833", b -> X86.cmpMem(b,0,6,3,-1,0,0,false));
+ check("400f95c6400fb6f6", b -> X86.set(b,5,6));
+ check("410f95c1450fb6c9", b -> X86.set(b,5,9));
+ check("4d33c9", b -> X86.constant(b,9,0));
+ check("49c7c1ffffffff", b -> X86.constant(b,9,-1));
+ check("41b9ffffffff", b -> X86.constant(b,9,0xFFFFFFFFL));
+ check("49b98877665544332211", b -> X86.constant(b,9,0x1122334455667788L));
+ check("4c8d0d00000000", b -> { assert X86.rip(b,0,0x8D,true,9)==7; });
+ check("f2440f100d00000000", b -> { assert X86.rip(b,0xF2,0x0F10,false,9)==9; });
+ check("e800000000c3", b -> { X86.call(b); X86.ret(b); });
+ check("ffd0", b -> X86.callRegister(b,0));
+ check("41ffd2", b -> X86.callRegister(b,10));
+ check("41519d", b -> X86.flags(b,9,true));
+ check("9c4159", b -> X86.flags(b,9,false));
+ check("ff7424088f442410", b -> X86.stackCopy(b,8,16));
+ check("4d8bca", b -> X86.move(b,false,false,9,10));
+ check("4d0f28ca", b -> X86.move(b,true,true,9,10));
+ check("664d0f6eca", b -> X86.move(b,true,false,9,10));
+ check("664d0f7ed1", b -> X86.move(b,false,true,9,10));
+ branch("eb7f",129,-1);
+ branch("eb80",-126,-1);
+ branch("e97d000000",130,-1);
+ branch("e97cffffff",-127,-1);
+ branch("757f",129,5);
+ branch("0f857c000000",130,5);
+ branch("0f857bffffff",-127,5);
+ System.out.println("Shared x86 encoding tests passed");
+ }
+ static void branch(String expected, int delta, int condition) {
+ int size=X86.branchSize(delta,condition>=0);
+ byte[] bytes=new byte[size];
+ var shortForm=new Bytes();
+ X86.branch(shortForm,condition);
+ System.arraycopy(shortForm.toByteArray(),0,bytes,0,2);
+ X86.patchBranch(bytes,0,size,delta,condition);
+ assert expected.equals(HexFormat.of().formatHex(bytes));
+ }
+}