diff --git a/.github/workflows/go-ci.yml b/.github/workflows/go-ci.yml index 7160f52..bed9091 100644 --- a/.github/workflows/go-ci.yml +++ b/.github/workflows/go-ci.yml @@ -56,6 +56,14 @@ jobs: - name: Go build run: go build ./... + - name: Go build (cmd/plan9asm) + run: go build ./... + working-directory: cmd/plan9asm + + - name: Go build (cmd/plan9asmll) + run: go build ./... + working-directory: cmd/plan9asmll + stdlib-corpus: runs-on: ${{ matrix.os }} strategy: @@ -143,6 +151,74 @@ jobs: - name: Go test run: go test ./... + - name: Go test (cmd/plan9asm) + run: go test ./... + working-directory: cmd/plan9asm + + - name: Go test (cmd/plan9asmll) + run: go test ./... + working-directory: cmd/plan9asmll + + arm-scan: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + goos: + - android + - freebsd + - linux + - netbsd + - openbsd + - plan9 + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Install LLVM 19 + run: | + echo "deb http://apt.llvm.org/$(lsb_release -cs)/ llvm-toolchain-$(lsb_release -cs)-19 main" | sudo tee /etc/apt/sources.list.d/llvm.list + wget -qO- https://apt.llvm.org/llvm-snapshot.gpg.key | sudo tee /etc/apt/trusted.gpg.d/llvm-snapshot.asc >/dev/null + for attempt in 1 2 3; do + sudo apt-get update && sudo apt-get install -y llvm-19-dev clang-19 libclang-19-dev lld-19 libunwind-19-dev libc++-19-dev && break + if [ "$attempt" -eq 3 ]; then + exit 1 + fi + sleep 5 + done + echo "PATH=/usr/lib/llvm-19/bin:$PATH" >> $GITHUB_ENV + + - name: Setup Go + uses: actions/setup-go@v5 + with: + go-version: '1.26.1' + cache: true + + - name: ARM scan gate + env: + TARGET_GOOS: ${{ matrix.goos }} + run: | + go run ./cmd/plan9asmscan -goos="$TARGET_GOOS" -goarch=arm -repo-root . -format json -out scan.json + python3 - "$PWD/scan.json" "$TARGET_GOOS/arm" <<'PY' + import json + import sys + + path, target = sys.argv[1], sys.argv[2] + with open(path, "r", encoding="utf-8") as f: + data = json.load(f) + + unsupported = data.get("unsupported", []) + parse_errs = data.get("parse_errs") or [] + print(f"scan {target}: packages={data['std_pkgs_with_sfile']} files={data['asm_files']} unsupported={len(unsupported)} parse_errs={len(parse_errs)}") + if unsupported: + top = ", ".join(f"{item['op']}({item['count']})" for item in unsupported[:12]) + raise SystemExit(f"{target}: unsupported ops remain: {top}") + if parse_errs: + top = ", ".join(f"{item['File']}: {item['Err']}" for item in parse_errs[:8]) + raise SystemExit(f"{target}: parse errors remain: {top}") + PY + race: runs-on: ubuntu-latest @@ -193,14 +269,26 @@ jobs: - name: Go test with coverage run: | - go test ./... -coverprofile=coverage.out - go tool cover -func=coverage.out | tail -n 1 + go test ./... -coverprofile=coverage-root.out + go tool cover -func=coverage-root.out | tail -n 1 + + - name: Go test with coverage (cmd/plan9asm) + run: | + go test ./... -coverprofile=coverage-plan9asm.out + go tool cover -func=coverage-plan9asm.out | tail -n 1 + working-directory: cmd/plan9asm + + - name: Go test with coverage (cmd/plan9asmll) + run: | + go test ./... -coverprofile=coverage-plan9asmll.out + go tool cover -func=coverage-plan9asmll.out | tail -n 1 + working-directory: cmd/plan9asmll - name: Upload coverage to Codecov uses: codecov/codecov-action@v5 with: use_oidc: true - files: ./coverage.out + files: ./coverage-root.out flags: unittests name: ubuntu-go1.25 fail_ci_if_error: false diff --git a/arm64_eval.go b/arm64_eval.go index 9094cca..7c230f9 100644 --- a/arm64_eval.go +++ b/arm64_eval.go @@ -142,11 +142,17 @@ func (c *arm64Ctx) eval64(op Operand, postInc bool) (string, error) { if err != nil { return "", err } + if op.ShiftReg != "" { + return "", fmt.Errorf("arm64: register-based shifts not supported: %s", op) + } t := c.newTmp() - if op.ShiftRight { + switch op.ShiftOp { + case ShiftRight: fmt.Fprintf(c.b, " %%%s = lshr i64 %s, %d\n", t, v, op.ShiftAmount) - } else { + case ShiftLeft: fmt.Fprintf(c.b, " %%%s = shl i64 %s, %d\n", t, v, op.ShiftAmount) + default: + return "", fmt.Errorf("arm64: unsupported shift op %q", op.ShiftOp) } return "%" + t, nil case OpFP: diff --git a/arm_atomic_test.go b/arm_atomic_test.go new file mode 100644 index 0000000..c0cbcf0 --- /dev/null +++ b/arm_atomic_test.go @@ -0,0 +1,33 @@ +package plan9asm + +import ( + "strings" + "testing" +) + +func TestTranslateARMLdrexStrex(t *testing.T) { + file, err := Parse(ArchARM, `TEXT ·cas(SB),NOSPLIT,$0-0 + LDREX (R1), R0 + STREX R3, (R1), R0 + RET +`) + if err != nil { + t.Fatalf("Parse() error = %v", err) + } + ll, err := Translate(file, Options{ + TargetTriple: "armv7-unknown-linux-gnueabihf", + Goarch: "arm", + ResolveSym: func(sym string) string { return "example." + strings.TrimPrefix(sym, "·") }, + Sigs: map[string]FuncSig{ + "example.cas": {Name: "example.cas", Ret: Void}, + }, + }) + if err != nil { + t.Fatalf("Translate() error = %v", err) + } + for _, want := range []string{"load atomic i32", "cmpxchg ptr", "%exclusive_valid"} { + if !strings.Contains(ll, want) { + t.Fatalf("missing %q in output:\n%s", want, ll) + } + } +} diff --git a/arm_batch1_test.go b/arm_batch1_test.go new file mode 100644 index 0000000..902e1fc --- /dev/null +++ b/arm_batch1_test.go @@ -0,0 +1,112 @@ +package plan9asm + +import ( + "strings" + "testing" +) + +func TestTranslateARMMOVMPushPop(t *testing.T) { + file, err := Parse(ArchARM, `TEXT ·f(SB),NOSPLIT,$0-0 + MOVM.DB.W [R0,R1], (R13) + MOVM.IA.W (R13), [R0,R1] + RET +`) + if err != nil { + t.Fatalf("Parse() error = %v", err) + } + ll, err := Translate(file, Options{ + TargetTriple: "armv7-unknown-linux-gnueabihf", + Goarch: "arm", + ResolveSym: func(sym string) string { return "example." + strings.TrimPrefix(sym, "·") }, + Sigs: map[string]FuncSig{ + "example.f": {Name: "example.f", Ret: Void}, + }, + }) + if err != nil { + t.Fatalf("Translate() error = %v", err) + } + for _, want := range []string{"store i32", "load i32", "%reg_R13"} { + if !strings.Contains(ll, want) { + t.Fatalf("missing %q in output:\n%s", want, ll) + } + } +} + +func TestTranslateARMSWI(t *testing.T) { + file, err := Parse(ArchARM, `TEXT ·raw(SB),NOSPLIT,$0-0 + MOVW $1, R7 + MOVW $2, R0 + SWI $0 + RET +`) + if err != nil { + t.Fatalf("Parse() error = %v", err) + } + ll, err := Translate(file, Options{ + TargetTriple: "armv7-unknown-linux-gnueabihf", + Goarch: "arm", + ResolveSym: func(sym string) string { return "example." + strings.TrimPrefix(sym, "·") }, + Sigs: map[string]FuncSig{ + "example.raw": {Name: "example.raw", Ret: I32}, + }, + }) + if err != nil { + t.Fatalf("Translate() error = %v", err) + } + if !strings.Contains(ll, "call i64 @syscall(") { + t.Fatalf("missing syscall call in output:\n%s", ll) + } +} + +func TestTranslateARMMOVDFloatSaveRestore(t *testing.T) { + file, err := Parse(ArchARM, `TEXT ·f(SB),NOSPLIT,$0-0 + MOVD F0, 8(R13) + MOVD 8(R13), F1 + RET +`) + if err != nil { + t.Fatalf("Parse() error = %v", err) + } + ll, err := Translate(file, Options{ + TargetTriple: "armv7-unknown-linux-gnueabihf", + Goarch: "arm", + ResolveSym: func(sym string) string { return "example." + strings.TrimPrefix(sym, "·") }, + Sigs: map[string]FuncSig{ + "example.f": {Name: "example.f", Ret: Void}, + }, + }) + if err != nil { + t.Fatalf("Translate() error = %v", err) + } + for _, want := range []string{"%freg_F0 = alloca i64", "store i64", "load i64"} { + if !strings.Contains(ll, want) { + t.Fatalf("missing %q in output:\n%s", want, ll) + } + } +} + +func TestTranslateARMMullu(t *testing.T) { + file, err := Parse(ArchARM, `TEXT ·mul(SB),NOSPLIT,$0-0 + MULLU R1, R0, (R2, R3) + RET +`) + if err != nil { + t.Fatalf("Parse() error = %v", err) + } + ll, err := Translate(file, Options{ + TargetTriple: "armv7-unknown-linux-gnueabihf", + Goarch: "arm", + ResolveSym: func(sym string) string { return "example." + strings.TrimPrefix(sym, "·") }, + Sigs: map[string]FuncSig{ + "example.mul": {Name: "example.mul", Ret: Void}, + }, + }) + if err != nil { + t.Fatalf("Translate() error = %v", err) + } + for _, want := range []string{"mul i64", "lshr i64", "store i32"} { + if !strings.Contains(ll, want) { + t.Fatalf("missing %q in output:\n%s", want, ll) + } + } +} diff --git a/arm_blocks.go b/arm_blocks.go new file mode 100644 index 0000000..4beb632 --- /dev/null +++ b/arm_blocks.go @@ -0,0 +1,118 @@ +package plan9asm + +import ( + "fmt" + "strings" +) + +type armBlock struct { + name string + instrs []Instr +} + +var armCondCodes = map[string]bool{ + "EQ": true, + "NE": true, + "CS": true, + "HS": true, + "CC": true, + "LO": true, + "MI": true, + "PL": true, + "VS": true, + "VC": true, + "HI": true, + "LS": true, + "GE": true, + "LT": true, + "GT": true, + "LE": true, + "AL": true, +} + +func armSplitBlocks(fn Func) []armBlock { + blocks := []armBlock{{name: "entry"}} + cur := 0 + anon := 0 + + startAnon := func() { + anon++ + blocks = append(blocks, armBlock{name: fmt.Sprintf("anon_%d", anon)}) + cur = len(blocks) - 1 + } + + isTerminator := func(ins Instr) bool { + if ins.Op == OpRET { + return true + } + baseOp, cond, _, _ := armDecodeOp(strings.ToUpper(string(ins.Op))) + switch baseOp { + case "B", "JMP": + return true + case "BEQ", "BNE", "BLT", "BGE", "BGT", "BLE", "BHS", "BHI", "BLS", "BLO", "BCC", "BCS", "BMI": + return true + } + return baseOp == "B" && cond != "" + } + + for _, ins := range fn.Instrs { + if ins.Op == OpLABEL && len(ins.Args) == 1 && ins.Args[0].Kind == OpLabel { + lbl := ins.Args[0].Sym + if len(blocks[cur].instrs) == 0 && strings.HasPrefix(blocks[cur].name, "anon_") { + blocks[cur].name = lbl + continue + } + blocks = append(blocks, armBlock{name: lbl}) + cur = len(blocks) - 1 + continue + } + + blocks[cur].instrs = append(blocks[cur].instrs, ins) + if isTerminator(ins) { + startAnon() + } + } + + if len(blocks) > 1 && len(blocks[len(blocks)-1].instrs) == 0 && strings.HasPrefix(blocks[len(blocks)-1].name, "anon_") { + blocks = blocks[:len(blocks)-1] + } + return blocks +} + +func armBranchTarget(op Operand) (string, bool) { + switch op.Kind { + case OpIdent: + return op.Ident, true + case OpSym: + s := strings.TrimSuffix(strings.TrimSuffix(op.Sym, "(SB)"), "<>") + if s == "" { + return "", false + } + return s, true + default: + return "", false + } +} + +func armDecodeOp(raw string) (base string, cond string, postInc bool, setFlags bool) { + base = strings.ToUpper(strings.TrimSpace(raw)) + if base == "" { + return "", "", false, false + } + parts := strings.Split(base, ".") + base = parts[0] + for _, p := range parts[1:] { + switch p { + case "P": + postInc = true + case "S": + setFlags = true + case "": + default: + if armCondCodes[p] { + cond = p + } + } + } + return base, cond, postInc, setFlags +} diff --git a/arm_ctx.go b/arm_ctx.go new file mode 100644 index 0000000..1487ba0 --- /dev/null +++ b/arm_ctx.go @@ -0,0 +1,303 @@ +package plan9asm + +import ( + "fmt" + "sort" + "strings" +) + +type armCtx struct { + b *strings.Builder + sig FuncSig + resolve func(string) string + sigs map[string]FuncSig + annotate bool + + tmp int + + blocks []armBlock + + usedRegs map[Reg]bool + regSlot map[Reg]string + usedFRegs map[Reg]bool + fRegSlot map[Reg]string + + flagsNSlot string + flagsZSlot string + flagsCSlot string + flagsVSlot string + flagsWritten bool + + exclusiveValidSlot string + exclusivePtrSlot string + exclusiveSizeSlot string + exclusiveValueSlot string + + fpParams map[int64]FrameSlot + fpResults []FrameSlot + fpResAllocaOff map[int64]string + fpResAllocaIdx map[int]string + fpResWritten map[int]bool + fpResAddrTaken map[int]bool +} + +func newARMCtx(b *strings.Builder, fn Func, sig FuncSig, resolve func(string) string, sigs map[string]FuncSig, annotate bool) *armCtx { + c := &armCtx{ + b: b, + sig: sig, + resolve: resolve, + sigs: sigs, + annotate: annotate, + blocks: armSplitBlocks(fn), + usedRegs: map[Reg]bool{}, + regSlot: map[Reg]string{}, + usedFRegs: map[Reg]bool{}, + fRegSlot: map[Reg]string{}, + fpParams: map[int64]FrameSlot{}, + fpResAllocaOff: map[int64]string{}, + fpResAllocaIdx: map[int]string{}, + fpResWritten: map[int]bool{}, + fpResAddrTaken: map[int]bool{}, + } + for _, s := range sig.Frame.Params { + c.fpParams[s.Offset] = s + } + c.fpResults = append([]FrameSlot(nil), sig.Frame.Results...) + return c +} + +func (c *armCtx) emitSourceComment(ins Instr) { + if !c.annotate { + return + } + emitIRSourceComment(c.b, ins.Raw) +} + +func (c *armCtx) newTmp() string { + c.tmp++ + return fmt.Sprintf("t%d", c.tmp) +} + +func (c *armCtx) slotName(r Reg) string { + return "%" + armLLVMBlockName("reg_"+string(r)) +} + +func (c *armCtx) scanUsedRegs() { + isFReg := func(r Reg) bool { + return strings.HasPrefix(string(r), "F") + } + markReg := func(r Reg) { + if r == "" { + return + } + if isFReg(r) { + c.usedFRegs[r] = true + } else { + c.usedRegs[r] = true + } + } + markOp := func(op Operand) { + switch op.Kind { + case OpReg, OpRegShift: + markReg(op.Reg) + markReg(op.ShiftReg) + case OpMem: + markReg(op.Mem.Base) + markReg(op.Mem.Index) + case OpRegList: + for _, r := range op.RegList { + markReg(r) + } + } + } + for _, blk := range c.blocks { + for _, ins := range blk.instrs { + for _, op := range ins.Args { + markOp(op) + } + } + } + if len(c.sig.ArgRegs) > 0 { + for i := 0; i < len(c.sig.Args) && i < len(c.sig.ArgRegs); i++ { + markReg(c.sig.ArgRegs[i]) + } + } else { + for i := 0; i < len(c.sig.Args) && i < 4; i++ { + markReg(Reg(fmt.Sprintf("R%d", i))) + } + } + for i := 0; i <= 15; i++ { + markReg(Reg(fmt.Sprintf("R%d", i))) + } + markReg(SP) +} + +func (c *armCtx) emitEntryAllocasAndArgInit() error { + c.scanUsedRegs() + regs := make([]string, 0, len(c.usedRegs)) + for r := range c.usedRegs { + regs = append(regs, string(r)) + } + sort.Strings(regs) + + c.b.WriteString(armLLVMBlockName("entry") + ":\n") + for _, rs := range regs { + r := Reg(rs) + slot := c.slotName(r) + c.regSlot[r] = slot + fmt.Fprintf(c.b, " %s = alloca i32\n", slot) + fmt.Fprintf(c.b, " store i32 0, ptr %s\n", slot) + } + fregs := make([]string, 0, len(c.usedFRegs)) + for r := range c.usedFRegs { + fregs = append(fregs, string(r)) + } + sort.Strings(fregs) + for _, rs := range fregs { + r := Reg(rs) + slot := "%" + armLLVMBlockName("freg_"+string(r)) + c.fRegSlot[r] = slot + fmt.Fprintf(c.b, " %s = alloca i64\n", slot) + fmt.Fprintf(c.b, " store i64 0, ptr %s\n", slot) + } + + c.flagsNSlot = "%flags_n" + c.flagsZSlot = "%flags_z" + c.flagsCSlot = "%flags_c" + c.flagsVSlot = "%flags_v" + fmt.Fprintf(c.b, " %s = alloca i1\n", c.flagsNSlot) + fmt.Fprintf(c.b, " store i1 false, ptr %s\n", c.flagsNSlot) + fmt.Fprintf(c.b, " %s = alloca i1\n", c.flagsZSlot) + fmt.Fprintf(c.b, " store i1 false, ptr %s\n", c.flagsZSlot) + fmt.Fprintf(c.b, " %s = alloca i1\n", c.flagsCSlot) + fmt.Fprintf(c.b, " store i1 false, ptr %s\n", c.flagsCSlot) + fmt.Fprintf(c.b, " %s = alloca i1\n", c.flagsVSlot) + fmt.Fprintf(c.b, " store i1 false, ptr %s\n", c.flagsVSlot) + + c.exclusiveValidSlot = "%exclusive_valid" + c.exclusivePtrSlot = "%exclusive_ptr" + c.exclusiveSizeSlot = "%exclusive_size" + c.exclusiveValueSlot = "%exclusive_value" + fmt.Fprintf(c.b, " %s = alloca i1\n", c.exclusiveValidSlot) + fmt.Fprintf(c.b, " store i1 false, ptr %s\n", c.exclusiveValidSlot) + fmt.Fprintf(c.b, " %s = alloca ptr\n", c.exclusivePtrSlot) + fmt.Fprintf(c.b, " store ptr null, ptr %s\n", c.exclusivePtrSlot) + fmt.Fprintf(c.b, " %s = alloca i8\n", c.exclusiveSizeSlot) + fmt.Fprintf(c.b, " store i8 0, ptr %s\n", c.exclusiveSizeSlot) + fmt.Fprintf(c.b, " %s = alloca i64\n", c.exclusiveValueSlot) + fmt.Fprintf(c.b, " store i64 0, ptr %s\n", c.exclusiveValueSlot) + + for _, r := range c.fpResults { + name := fmt.Sprintf("%%fp_ret_%d", r.Index) + c.fpResAllocaIdx[r.Index] = name + c.fpResAllocaOff[r.Offset] = name + fmt.Fprintf(c.b, " %s = alloca %s\n", name, r.Type) + fmt.Fprintf(c.b, " store %s %s, ptr %s\n", r.Type, llvmZeroValue(r.Type), name) + } + + if len(c.sig.ArgRegs) > 0 { + for i := 0; i < len(c.sig.Args) && i < len(c.sig.ArgRegs); i++ { + slot, ok := c.regSlot[c.sig.ArgRegs[i]] + if !ok { + continue + } + v, ok, err := armValueAsI32(c, c.sig.Args[i], fmt.Sprintf("%%arg%d", i)) + if err != nil { + return err + } + if !ok { + continue + } + fmt.Fprintf(c.b, " store i32 %s, ptr %s\n", v, slot) + } + } + return nil +} + +func armValueAsI32(c *armCtx, ty LLVMType, v string) (out string, ok bool, err error) { + switch ty { + case Ptr: + t := c.newTmp() + fmt.Fprintf(c.b, " %%%s = ptrtoint ptr %s to i32\n", t, v) + return "%" + t, true, nil + case I1: + t := c.newTmp() + fmt.Fprintf(c.b, " %%%s = zext i1 %s to i32\n", t, v) + return "%" + t, true, nil + case I8: + t := c.newTmp() + fmt.Fprintf(c.b, " %%%s = zext i8 %s to i32\n", t, v) + return "%" + t, true, nil + case I16: + t := c.newTmp() + fmt.Fprintf(c.b, " %%%s = zext i16 %s to i32\n", t, v) + return "%" + t, true, nil + case I32: + return v, true, nil + case I64: + t := c.newTmp() + fmt.Fprintf(c.b, " %%%s = trunc i64 %s to i32\n", t, v) + return "%" + t, true, nil + default: + return "", false, nil + } +} + +func (c *armCtx) loadReg(r Reg) (string, error) { + slot, ok := c.regSlot[r] + if !ok { + return "", fmt.Errorf("arm: unknown reg %s", r) + } + t := c.newTmp() + fmt.Fprintf(c.b, " %%%s = load i32, ptr %s\n", t, slot) + return "%" + t, nil +} + +func (c *armCtx) storeReg(r Reg, v string) error { + slot, ok := c.regSlot[r] + if !ok { + return fmt.Errorf("arm: unknown reg %s", r) + } + fmt.Fprintf(c.b, " store i32 %s, ptr %s\n", v, slot) + return nil +} + +func (c *armCtx) loadFReg(r Reg) (string, error) { + slot, ok := c.fRegSlot[r] + if !ok { + return "", fmt.Errorf("arm: unknown freg %s", r) + } + t := c.newTmp() + fmt.Fprintf(c.b, " %%%s = load i64, ptr %s\n", t, slot) + return "%" + t, nil +} + +func (c *armCtx) storeFReg(r Reg, v string) error { + slot, ok := c.fRegSlot[r] + if !ok { + return fmt.Errorf("arm: unknown freg %s", r) + } + fmt.Fprintf(c.b, " store i64 %s, ptr %s\n", v, slot) + return nil +} + +func (c *armCtx) ptrFromSB(sym string) (string, error) { + base, off, ok := parseSBRef(sym) + if !ok { + return "", fmt.Errorf("invalid (SB) sym ref: %q", sym) + } + base = strings.TrimPrefix(base, "$") + res := base + if strings.Contains(base, "·") || strings.Contains(base, "/") || strings.Contains(base, ".") { + res = c.resolve(base) + } else { + res = c.resolve("·" + base) + } + p := llvmGlobal(res) + if off == 0 { + return p, nil + } + t := c.newTmp() + fmt.Fprintf(c.b, " %%%s = getelementptr i8, ptr %s, i32 %d\n", t, p, off) + return "%" + t, nil +} diff --git a/arm_eval.go b/arm_eval.go new file mode 100644 index 0000000..7efb6f7 --- /dev/null +++ b/arm_eval.go @@ -0,0 +1,245 @@ +package plan9asm + +import ( + "fmt" + "strconv" + "strings" +) + +func (c *armCtx) imm32(n int64) string { + return strconv.FormatInt(n, 10) +} + +func (c *armCtx) addrI32(mem MemRef, postInc bool) (addr string, base Reg, inc int64, err error) { + base = mem.Base + baseVal, err := c.loadReg(base) + if err != nil { + return "", "", 0, err + } + off := mem.Off + if postInc { + inc = off + off = 0 + } + sum := baseVal + if mem.Index != "" { + idxVal, err := c.loadReg(mem.Index) + if err != nil { + return "", "", 0, err + } + if mem.Scale != 0 && mem.Scale != 1 { + t := c.newTmp() + fmt.Fprintf(c.b, " %%%s = mul i32 %s, %s\n", t, idxVal, c.imm32(mem.Scale)) + idxVal = "%" + t + } + t := c.newTmp() + fmt.Fprintf(c.b, " %%%s = add i32 %s, %s\n", t, sum, idxVal) + sum = "%" + t + } + if off != 0 { + t := c.newTmp() + fmt.Fprintf(c.b, " %%%s = add i32 %s, %s\n", t, sum, c.imm32(off)) + sum = "%" + t + } + return sum, base, inc, nil +} + +func (c *armCtx) updatePostInc(base Reg, inc int64) error { + if inc == 0 { + return nil + } + baseVal, err := c.loadReg(base) + if err != nil { + return err + } + t := c.newTmp() + fmt.Fprintf(c.b, " %%%s = add i32 %s, %s\n", t, baseVal, c.imm32(inc)) + return c.storeReg(base, "%"+t) +} + +func (c *armCtx) loadMem(mem MemRef, bits int, postInc bool, signed bool) (string, error) { + addr, base, inc, err := c.addrI32(mem, postInc) + if err != nil { + return "", err + } + pt := c.newTmp() + fmt.Fprintf(c.b, " %%%s = inttoptr i32 %s to ptr\n", pt, addr) + ptr := "%" + pt + + var out string + switch bits { + case 64: + t := c.newTmp() + fmt.Fprintf(c.b, " %%%s = load i64, ptr %s\n", t, ptr) + out = "%" + t + case 32: + t := c.newTmp() + fmt.Fprintf(c.b, " %%%s = load i32, ptr %s\n", t, ptr) + out = "%" + t + case 8: + t := c.newTmp() + fmt.Fprintf(c.b, " %%%s = load i8, ptr %s\n", t, ptr) + e := c.newTmp() + if signed { + fmt.Fprintf(c.b, " %%%s = sext i8 %%%s to i32\n", e, t) + } else { + fmt.Fprintf(c.b, " %%%s = zext i8 %%%s to i32\n", e, t) + } + out = "%" + e + default: + return "", fmt.Errorf("arm: unsupported load bits %d", bits) + } + if err := c.updatePostInc(base, inc); err != nil { + return "", err + } + return out, nil +} + +func (c *armCtx) storeMem(mem MemRef, bits int, postInc bool, v32 string) error { + addr, base, inc, err := c.addrI32(mem, postInc) + if err != nil { + return err + } + pt := c.newTmp() + fmt.Fprintf(c.b, " %%%s = inttoptr i32 %s to ptr\n", pt, addr) + ptr := "%" + pt + switch bits { + case 64: + fmt.Fprintf(c.b, " store i64 %s, ptr %s\n", v32, ptr) + case 32: + fmt.Fprintf(c.b, " store i32 %s, ptr %s\n", v32, ptr) + case 8: + t := c.newTmp() + fmt.Fprintf(c.b, " %%%s = trunc i32 %s to i8\n", t, v32) + fmt.Fprintf(c.b, " store i8 %%%s, ptr %s\n", t, ptr) + default: + return fmt.Errorf("arm: unsupported store bits %d", bits) + } + return c.updatePostInc(base, inc) +} + +func (c *armCtx) eval32(op Operand, postInc bool) (string, error) { + switch op.Kind { + case OpImm: + if op.ImmRaw != "" { + return "", fmt.Errorf("arm: unresolved symbolic immediate %q", op.ImmRaw) + } + return c.imm32(op.Imm), nil + case OpReg: + return c.loadReg(op.Reg) + case OpRegShift: + return c.evalShift(op) + case OpFP: + return c.evalFPValue32(op) + case OpFPAddr: + return c.evalFPAddr32(op) + case OpMem: + return c.loadMem(op.Mem, 32, postInc, false) + case OpSym: + sym := strings.TrimSpace(op.Sym) + if strings.HasPrefix(sym, "$") { + sym = strings.TrimPrefix(sym, "$") + } + if mem, ok := parseMem(sym); ok { + addr, _, _, err := c.addrI32(mem, false) + if err != nil { + return "", err + } + return addr, nil + } + p, err := c.ptrFromSB(sym) + if err != nil { + return "", err + } + t := c.newTmp() + fmt.Fprintf(c.b, " %%%s = ptrtoint ptr %s to i32\n", t, p) + return "%" + t, nil + case OpIdent: + return "0", nil + default: + return "", fmt.Errorf("arm: unsupported operand for i32: %s", op.String()) + } +} + +func (c *armCtx) evalShift(op Operand) (string, error) { + base, err := c.loadReg(op.Reg) + if err != nil { + return "", err + } + var sh string + if op.ShiftReg != "" { + sh, err = c.loadReg(op.ShiftReg) + if err != nil { + return "", err + } + } else { + sh = c.imm32(op.ShiftAmount) + } + t := c.newTmp() + switch op.ShiftOp { + case ShiftLeft: + fmt.Fprintf(c.b, " %%%s = shl i32 %s, %s\n", t, base, sh) + case ShiftRight: + fmt.Fprintf(c.b, " %%%s = lshr i32 %s, %s\n", t, base, sh) + case ShiftArith: + fmt.Fprintf(c.b, " %%%s = ashr i32 %s, %s\n", t, base, sh) + case ShiftRotate: + fmt.Fprintf(c.b, " %%%s = call i32 @llvm.fshr.i32(i32 %s, i32 %s, i32 %s)\n", t, base, base, sh) + default: + return "", fmt.Errorf("arm: unsupported shift op %q", op.ShiftOp) + } + return "%" + t, nil +} + +func (c *armCtx) evalFPValue32(op Operand) (string, error) { + slot, ok := c.fpParams[op.FPOffset] + if !ok { + return "", fmt.Errorf("arm: unsupported FP param slot: %s", op.String()) + } + if slot.Index < 0 || slot.Index >= len(c.sig.Args) { + return "", fmt.Errorf("arm: FP slot %s invalid arg index %d", op.String(), slot.Index) + } + arg := fmt.Sprintf("%%arg%d", slot.Index) + if slot.Field >= 0 { + aggTy := c.sig.Args[slot.Index] + t := c.newTmp() + fmt.Fprintf(c.b, " %%%s = extractvalue %s %s, %d\n", t, aggTy, arg, slot.Field) + arg = "%" + t + } + switch slot.Type { + case Ptr: + t := c.newTmp() + fmt.Fprintf(c.b, " %%%s = ptrtoint ptr %s to i32\n", t, arg) + return "%" + t, nil + case I1: + t := c.newTmp() + fmt.Fprintf(c.b, " %%%s = zext i1 %s to i32\n", t, arg) + return "%" + t, nil + case I8: + t := c.newTmp() + fmt.Fprintf(c.b, " %%%s = zext i8 %s to i32\n", t, arg) + return "%" + t, nil + case I16: + t := c.newTmp() + fmt.Fprintf(c.b, " %%%s = zext i16 %s to i32\n", t, arg) + return "%" + t, nil + case I32: + return arg, nil + case I64: + t := c.newTmp() + fmt.Fprintf(c.b, " %%%s = trunc i64 %s to i32\n", t, arg) + return "%" + t, nil + default: + return "", fmt.Errorf("arm: unsupported FP slot type %q", slot.Type) + } +} + +func (c *armCtx) evalFPAddr32(op Operand) (string, error) { + if slot, ok := c.fpResAllocaOff[op.FPOffset]; ok { + c.markFPResultAddrTaken(op.FPOffset) + t := c.newTmp() + fmt.Fprintf(c.b, " %%%s = ptrtoint ptr %s to i32\n", t, slot) + return "%" + t, nil + } + return "", fmt.Errorf("arm: unsupported FP addr slot: %s", op.String()) +} diff --git a/arm_flags.go b/arm_flags.go new file mode 100644 index 0000000..53ad1f5 --- /dev/null +++ b/arm_flags.go @@ -0,0 +1,174 @@ +package plan9asm + +import ( + "fmt" + "strings" +) + +func (c *armCtx) storeFlag(slot string, v string) { + fmt.Fprintf(c.b, " store i1 %s, ptr %s\n", v, slot) +} + +func (c *armCtx) storeFlagCond(cond, slot, v string) error { + if cond == "" || strings.EqualFold(cond, "AL") { + c.storeFlag(slot, v) + return nil + } + cv, err := c.condValue(cond) + if err != nil { + return err + } + oldV := c.newTmp() + sel := c.newTmp() + fmt.Fprintf(c.b, " %%%s = load i1, ptr %s\n", oldV, slot) + fmt.Fprintf(c.b, " %%%s = select i1 %s, i1 %s, i1 %%%s\n", sel, cv, v, oldV) + c.storeFlag(slot, "%"+sel) + return nil +} + +func (c *armCtx) setFlagsSub(cond, dst, src, res string) error { + c.flagsWritten = true + z := c.newTmp() + n := c.newTmp() + carry := c.newTmp() + fmt.Fprintf(c.b, " %%%s = icmp eq i32 %s, 0\n", z, res) + fmt.Fprintf(c.b, " %%%s = icmp slt i32 %s, 0\n", n, res) + fmt.Fprintf(c.b, " %%%s = icmp uge i32 %s, %s\n", carry, dst, src) + x1 := c.newTmp() + x2 := c.newTmp() + x3 := c.newTmp() + ov := c.newTmp() + fmt.Fprintf(c.b, " %%%s = xor i32 %s, %s\n", x1, dst, src) + fmt.Fprintf(c.b, " %%%s = xor i32 %s, %s\n", x2, dst, res) + fmt.Fprintf(c.b, " %%%s = and i32 %%%s, %%%s\n", x3, x1, x2) + fmt.Fprintf(c.b, " %%%s = icmp slt i32 %%%s, 0\n", ov, x3) + if err := c.storeFlagCond(cond, c.flagsZSlot, "%"+z); err != nil { + return err + } + if err := c.storeFlagCond(cond, c.flagsNSlot, "%"+n); err != nil { + return err + } + if err := c.storeFlagCond(cond, c.flagsCSlot, "%"+carry); err != nil { + return err + } + return c.storeFlagCond(cond, c.flagsVSlot, "%"+ov) +} + +func (c *armCtx) setFlagsAdd(cond, dst, src, res string) error { + c.flagsWritten = true + z := c.newTmp() + n := c.newTmp() + carry := c.newTmp() + fmt.Fprintf(c.b, " %%%s = icmp eq i32 %s, 0\n", z, res) + fmt.Fprintf(c.b, " %%%s = icmp slt i32 %s, 0\n", n, res) + fmt.Fprintf(c.b, " %%%s = icmp ult i32 %s, %s\n", carry, res, dst) + x1 := c.newTmp() + nx1 := c.newTmp() + x2 := c.newTmp() + x3 := c.newTmp() + ov := c.newTmp() + fmt.Fprintf(c.b, " %%%s = xor i32 %s, %s\n", x1, dst, src) + fmt.Fprintf(c.b, " %%%s = xor i32 %%%s, -1\n", nx1, x1) + fmt.Fprintf(c.b, " %%%s = xor i32 %s, %s\n", x2, dst, res) + fmt.Fprintf(c.b, " %%%s = and i32 %%%s, %%%s\n", x3, nx1, x2) + fmt.Fprintf(c.b, " %%%s = icmp slt i32 %%%s, 0\n", ov, x3) + if err := c.storeFlagCond(cond, c.flagsZSlot, "%"+z); err != nil { + return err + } + if err := c.storeFlagCond(cond, c.flagsNSlot, "%"+n); err != nil { + return err + } + if err := c.storeFlagCond(cond, c.flagsCSlot, "%"+carry); err != nil { + return err + } + return c.storeFlagCond(cond, c.flagsVSlot, "%"+ov) +} + +func (c *armCtx) setFlagsLogic(cond, res string) error { + c.flagsWritten = true + z := c.newTmp() + n := c.newTmp() + fmt.Fprintf(c.b, " %%%s = icmp eq i32 %s, 0\n", z, res) + fmt.Fprintf(c.b, " %%%s = icmp slt i32 %s, 0\n", n, res) + if err := c.storeFlagCond(cond, c.flagsZSlot, "%"+z); err != nil { + return err + } + return c.storeFlagCond(cond, c.flagsNSlot, "%"+n) +} + +func (c *armCtx) condValue(cond string) (string, error) { + if !c.flagsWritten { + return "", fmt.Errorf("arm: condition %s without any prior flags write", cond) + } + ldN := c.newTmp() + ldZ := c.newTmp() + ldC := c.newTmp() + ldV := c.newTmp() + fmt.Fprintf(c.b, " %%%s = load i1, ptr %s\n", ldN, c.flagsNSlot) + fmt.Fprintf(c.b, " %%%s = load i1, ptr %s\n", ldZ, c.flagsZSlot) + fmt.Fprintf(c.b, " %%%s = load i1, ptr %s\n", ldC, c.flagsCSlot) + fmt.Fprintf(c.b, " %%%s = load i1, ptr %s\n", ldV, c.flagsVSlot) + n := "%" + ldN + z := "%" + ldZ + carry := "%" + ldC + v := "%" + ldV + not := func(x string) string { + t := c.newTmp() + fmt.Fprintf(c.b, " %%%s = xor i1 %s, true\n", t, x) + return "%" + t + } + and := func(a, b string) string { + t := c.newTmp() + fmt.Fprintf(c.b, " %%%s = and i1 %s, %s\n", t, a, b) + return "%" + t + } + or := func(a, b string) string { + t := c.newTmp() + fmt.Fprintf(c.b, " %%%s = or i1 %s, %s\n", t, a, b) + return "%" + t + } + xor := func(a, b string) string { + t := c.newTmp() + fmt.Fprintf(c.b, " %%%s = xor i1 %s, %s\n", t, a, b) + return "%" + t + } + eq := func(a, b string) string { + t := c.newTmp() + fmt.Fprintf(c.b, " %%%s = icmp eq i1 %s, %s\n", t, a, b) + return "%" + t + } + switch strings.ToUpper(cond) { + case "EQ": + return z, nil + case "NE": + return not(z), nil + case "CS", "HS": + return carry, nil + case "CC", "LO": + return not(carry), nil + case "HI": + return and(carry, not(z)), nil + case "LS": + return or(not(carry), z), nil + case "LT": + return xor(n, v), nil + case "GE": + return eq(n, v), nil + case "GT": + return and(not(z), eq(n, v)), nil + case "LE": + return or(z, xor(n, v)), nil + case "MI": + return n, nil + case "PL": + return not(n), nil + case "VS": + return v, nil + case "VC": + return not(v), nil + case "AL": + return "true", nil + default: + return "", fmt.Errorf("arm: unsupported condition %q", cond) + } +} diff --git a/arm_flags_test.go b/arm_flags_test.go new file mode 100644 index 0000000..64db24a --- /dev/null +++ b/arm_flags_test.go @@ -0,0 +1,78 @@ +package plan9asm + +import ( + "strings" + "testing" +) + +func TestTranslateARMAddSFeedsConditional(t *testing.T) { + file, err := Parse(ArchARM, `TEXT ·f(SB),NOSPLIT,$0-0 + MOVW $0, R0 + ADD.S $0, R0 + MOVW.EQ $1, R1 + RET +`) + if err != nil { + t.Fatalf("Parse() error = %v", err) + } + ll, err := Translate(file, Options{ + TargetTriple: "armv7-unknown-linux-gnueabihf", + Goarch: "arm", + ResolveSym: func(sym string) string { return "example." + strings.TrimPrefix(sym, "·") }, + Sigs: map[string]FuncSig{ + "example.f": {Name: "example.f", Ret: Void}, + }, + }) + if err != nil { + t.Fatalf("Translate() error = %v", err) + } + if !strings.Contains(ll, "%flags_z") { + t.Fatalf("missing flag writes in output:\n%s", ll) + } +} + +func TestTranslateARMAdditionalConditions(t *testing.T) { + file, err := Parse(ArchARM, `TEXT ·f(SB),NOSPLIT,$0-0 + CMP R0, R0 + MOVW.PL $1, R1 + MOVW.VS $2, R2 + MOVW.VC $3, R3 + MOVW.AL $4, R4 + RET +`) + if err != nil { + t.Fatalf("Parse() error = %v", err) + } + _, err = Translate(file, Options{ + TargetTriple: "armv7-unknown-linux-gnueabihf", + Goarch: "arm", + ResolveSym: func(sym string) string { return "example." + strings.TrimPrefix(sym, "·") }, + Sigs: map[string]FuncSig{ + "example.f": {Name: "example.f", Ret: Void}, + }, + }) + if err != nil { + t.Fatalf("Translate() error = %v", err) + } +} + +func TestTranslateARMRejectsUnresolvedSymbolicImmediate(t *testing.T) { + file, err := Parse(ArchARM, `TEXT ·f(SB),NOSPLIT,$0-0 + MOVW $(16 + callbackArgs__size), R0 + RET +`) + if err != nil { + t.Fatalf("Parse() error = %v", err) + } + _, err = Translate(file, Options{ + TargetTriple: "armv7-unknown-linux-gnueabihf", + Goarch: "arm", + ResolveSym: func(sym string) string { return "example." + strings.TrimPrefix(sym, "·") }, + Sigs: map[string]FuncSig{ + "example.f": {Name: "example.f", Ret: Void}, + }, + }) + if err == nil || !strings.Contains(err.Error(), "unresolved symbolic immediate") { + t.Fatalf("Translate() error = %v, want unresolved symbolic immediate", err) + } +} diff --git a/arm_fp.go b/arm_fp.go new file mode 100644 index 0000000..c6e2ccb --- /dev/null +++ b/arm_fp.go @@ -0,0 +1,90 @@ +package plan9asm + +import "fmt" + +func (c *armCtx) fpResultSlotByOffset(off int64) (FrameSlot, bool) { + for _, s := range c.fpResults { + if s.Offset == off { + return s, true + } + } + return FrameSlot{}, false +} + +func (c *armCtx) markFPResultWritten(off int64) { + if s, ok := c.fpResultSlotByOffset(off); ok { + c.fpResWritten[s.Index] = true + } +} + +func (c *armCtx) markFPResultAddrTaken(off int64) { + if s, ok := c.fpResultSlotByOffset(off); ok { + c.fpResAddrTaken[s.Index] = true + } +} + +func (c *armCtx) storeFPResult32(off int64, v32 string) error { + slot, ok := c.fpResAllocaOff[off] + if !ok { + return fmt.Errorf("arm: unsupported FP result slot +%d(FP)", off) + } + meta, found := c.fpResultSlotByOffset(off) + if !found { + return fmt.Errorf("arm: missing FP result metadata for +%d(FP)", off) + } + switch meta.Type { + case I32: + fmt.Fprintf(c.b, " store i32 %s, ptr %s\n", v32, slot) + case I16, I8, I1: + t := c.newTmp() + fmt.Fprintf(c.b, " %%%s = trunc i32 %s to %s\n", t, v32, meta.Type) + fmt.Fprintf(c.b, " store %s %%%s, ptr %s\n", meta.Type, t, slot) + case Ptr: + t := c.newTmp() + fmt.Fprintf(c.b, " %%%s = inttoptr i32 %s to ptr\n", t, v32) + fmt.Fprintf(c.b, " store ptr %%%s, ptr %s\n", t, slot) + case I64: + t := c.newTmp() + fmt.Fprintf(c.b, " %%%s = zext i32 %s to i64\n", t, v32) + fmt.Fprintf(c.b, " store i64 %%%s, ptr %s\n", t, slot) + default: + return fmt.Errorf("arm: unsupported FP result slot type %q", meta.Type) + } + c.markFPResultWritten(off) + return nil +} + +func (c *armCtx) loadFPResult(slot FrameSlot) (string, error) { + p, ok := c.fpResAllocaIdx[slot.Index] + if !ok { + return "", fmt.Errorf("arm: missing FP result alloca for index %d", slot.Index) + } + t := c.newTmp() + fmt.Fprintf(c.b, " %%%s = load %s, ptr %s\n", t, slot.Type, p) + return "%" + t, nil +} + +func (c *armCtx) loadRetSlotFallback(slot FrameSlot) (string, error) { + v32, err := c.loadReg(Reg("R0")) + if err != nil { + return "", err + } + switch slot.Type { + case I32: + return v32, nil + case I16, I8, I1: + t := c.newTmp() + fmt.Fprintf(c.b, " %%%s = trunc i32 %s to %s\n", t, v32, slot.Type) + return "%" + t, nil + case Ptr: + t := c.newTmp() + fmt.Fprintf(c.b, " %%%s = inttoptr i32 %s to ptr\n", t, v32) + return "%" + t, nil + case I64: + t := c.newTmp() + fmt.Fprintf(c.b, " %%%s = zext i32 %s to i64\n", t, v32) + return "%" + t, nil + default: + return "", fmt.Errorf("arm: unsupported fallback return type %q", slot.Type) + } +} diff --git a/arm_lower_arith.go b/arm_lower_arith.go new file mode 100644 index 0000000..b2f8843 --- /dev/null +++ b/arm_lower_arith.go @@ -0,0 +1,521 @@ +package plan9asm + +import ( + "fmt" + "strings" +) + +func (c *armCtx) lowerArith(op, cond string, setFlags bool, ins Instr) (ok bool, terminated bool, err error) { + switch op { + case "ADD", "SUB", "AND", "ORR", "EOR", "RSB", "BIC": + return true, false, c.lowerARMALU(op, cond, setFlags, ins) + case "MVN": + return true, false, c.lowerARMMVN(cond, setFlags, ins) + case "ADC", "SBC": + return true, false, c.lowerARMADCSBC(op, cond, setFlags, ins) + case "MUL", "MULU": + return true, false, c.lowerARMMUL(cond, ins) + case "MULLU": + return true, false, c.lowerARMMULLU(cond, ins) + case "MULA": + return true, false, c.lowerARMMULA(cond, ins) + case "MULAL", "MULALU": + return true, false, c.lowerARMMULAL(cond, ins) + case "MULAWT": + return true, false, c.lowerARMMULAWT(cond, ins) + case "DIVUHW": + return true, false, c.lowerARMDIVUHW(cond, ins) + case "CLZ": + return true, false, c.lowerARMCLZ(cond, ins) + case "MRC": + return true, false, c.lowerARMMRC(ins) + case "CMP", "CMN", "TST", "TEQ": + return true, false, c.lowerARMCompare(op, ins) + } + return false, false, nil +} + +func (c *armCtx) lowerARMALU(op, cond string, setFlags bool, ins Instr) error { + if len(ins.Args) != 2 && len(ins.Args) != 3 { + return fmt.Errorf("arm %s expects 2 or 3 operands: %q", op, ins.Raw) + } + var src, lhs string + dst := Operand{} + var err error + if len(ins.Args) == 2 { + dst = ins.Args[1] + if dst.Kind != OpReg { + return fmt.Errorf("arm %s dst must be reg: %q", op, ins.Raw) + } + src, err = c.eval32(ins.Args[0], false) + if err != nil { + return err + } + lhs, err = c.loadReg(dst.Reg) + if err != nil { + return err + } + } else { + dst = ins.Args[2] + if dst.Kind != OpReg { + return fmt.Errorf("arm %s dst must be reg: %q", op, ins.Raw) + } + src, err = c.eval32(ins.Args[0], false) + if err != nil { + return err + } + lhs, err = c.eval32(ins.Args[1], false) + if err != nil { + return err + } + } + t := c.newTmp() + switch op { + case "ADD": + fmt.Fprintf(c.b, " %%%s = add i32 %s, %s\n", t, lhs, src) + case "SUB": + fmt.Fprintf(c.b, " %%%s = sub i32 %s, %s\n", t, lhs, src) + case "AND": + fmt.Fprintf(c.b, " %%%s = and i32 %s, %s\n", t, lhs, src) + case "ORR": + fmt.Fprintf(c.b, " %%%s = or i32 %s, %s\n", t, lhs, src) + case "EOR": + fmt.Fprintf(c.b, " %%%s = xor i32 %s, %s\n", t, lhs, src) + case "RSB": + fmt.Fprintf(c.b, " %%%s = sub i32 %s, %s\n", t, src, lhs) + case "BIC": + n := c.newTmp() + fmt.Fprintf(c.b, " %%%s = xor i32 %s, -1\n", n, src) + fmt.Fprintf(c.b, " %%%s = and i32 %s, %%%s\n", t, lhs, n) + } + if err := c.selectRegWrite(dst.Reg, cond, "%"+t); err != nil { + return err + } + if !setFlags { + return nil + } + switch op { + case "ADD": + return c.setFlagsAdd(cond, lhs, src, "%"+t) + case "SUB": + return c.setFlagsSub(cond, lhs, src, "%"+t) + case "RSB": + return c.setFlagsSub(cond, src, lhs, "%"+t) + case "AND", "ORR", "EOR", "BIC": + return c.setFlagsLogic(cond, "%"+t) + default: + return nil + } +} + +func (c *armCtx) lowerARMCompare(op string, ins Instr) error { + if len(ins.Args) != 2 { + return fmt.Errorf("arm %s expects 2 operands: %q", op, ins.Raw) + } + src, err := c.eval32(ins.Args[0], false) + if err != nil { + return err + } + lhs, err := c.eval32(ins.Args[1], false) + if err != nil { + return err + } + res := c.newTmp() + switch op { + case "CMP": + fmt.Fprintf(c.b, " %%%s = sub i32 %s, %s\n", res, lhs, src) + if err := c.setFlagsSub("", lhs, src, "%"+res); err != nil { + return err + } + case "CMN": + fmt.Fprintf(c.b, " %%%s = add i32 %s, %s\n", res, lhs, src) + if err := c.setFlagsAdd("", lhs, src, "%"+res); err != nil { + return err + } + case "TST": + fmt.Fprintf(c.b, " %%%s = and i32 %s, %s\n", res, lhs, src) + if err := c.setFlagsLogic("", "%"+res); err != nil { + return err + } + case "TEQ": + fmt.Fprintf(c.b, " %%%s = xor i32 %s, %s\n", res, lhs, src) + if err := c.setFlagsLogic("", "%"+res); err != nil { + return err + } + } + return nil +} + +func (c *armCtx) lowerARMMVN(cond string, setFlags bool, ins Instr) error { + if len(ins.Args) != 2 && len(ins.Args) != 3 { + return fmt.Errorf("arm MVN expects 2 or 3 operands: %q", ins.Raw) + } + var src string + var dst Operand + var err error + if len(ins.Args) == 2 { + src, err = c.eval32(ins.Args[0], false) + if err != nil { + return err + } + dst = ins.Args[1] + } else { + src, err = c.eval32(ins.Args[1], false) + if err != nil { + return err + } + dst = ins.Args[2] + } + if dst.Kind != OpReg { + return fmt.Errorf("arm MVN dst must be reg: %q", ins.Raw) + } + t := c.newTmp() + fmt.Fprintf(c.b, " %%%s = xor i32 %s, -1\n", t, src) + if err := c.selectRegWrite(dst.Reg, cond, "%"+t); err != nil { + return err + } + if setFlags { + return c.setFlagsLogic(cond, "%"+t) + } + return nil +} + +func (c *armCtx) lowerARMADCSBC(op, cond string, setFlags bool, ins Instr) error { + if len(ins.Args) != 2 && len(ins.Args) != 3 { + return fmt.Errorf("arm %s expects 2 or 3 operands: %q", op, ins.Raw) + } + var src, lhs string + dst := Operand{} + var err error + if len(ins.Args) == 2 { + dst = ins.Args[1] + if dst.Kind != OpReg { + return fmt.Errorf("arm %s dst must be reg: %q", op, ins.Raw) + } + src, err = c.eval32(ins.Args[0], false) + if err != nil { + return err + } + lhs, err = c.loadReg(dst.Reg) + if err != nil { + return err + } + } else { + dst = ins.Args[2] + if dst.Kind != OpReg { + return fmt.Errorf("arm %s dst must be reg: %q", op, ins.Raw) + } + src, err = c.eval32(ins.Args[0], false) + if err != nil { + return err + } + lhs, err = c.eval32(ins.Args[1], false) + if err != nil { + return err + } + } + cf := c.newTmp() + fmt.Fprintf(c.b, " %%%s = load i1, ptr %s\n", cf, c.flagsCSlot) + cin := c.newTmp() + if op == "SBC" { + ncf := c.newTmp() + fmt.Fprintf(c.b, " %%%s = xor i1 %%%s, true\n", ncf, cf) + fmt.Fprintf(c.b, " %%%s = zext i1 %%%s to i32\n", cin, ncf) + } else { + fmt.Fprintf(c.b, " %%%s = zext i1 %%%s to i32\n", cin, cf) + } + t0 := c.newTmp() + if op == "SBC" { + fmt.Fprintf(c.b, " %%%s = sub i32 %s, %s\n", t0, lhs, src) + } else { + fmt.Fprintf(c.b, " %%%s = add i32 %s, %s\n", t0, lhs, src) + } + res := c.newTmp() + if op == "SBC" { + fmt.Fprintf(c.b, " %%%s = sub i32 %%%s, %%%s\n", res, t0, cin) + } else { + fmt.Fprintf(c.b, " %%%s = add i32 %%%s, %%%s\n", res, t0, cin) + } + if err := c.selectRegWrite(dst.Reg, cond, "%"+res); err != nil { + return err + } + if setFlags { + if op == "ADC" { + l64 := c.newTmp() + s64 := c.newTmp() + c64 := c.newTmp() + total1 := c.newTmp() + total2 := c.newTmp() + carry := c.newTmp() + fmt.Fprintf(c.b, " %%%s = zext i32 %s to i64\n", l64, lhs) + fmt.Fprintf(c.b, " %%%s = zext i32 %s to i64\n", s64, src) + fmt.Fprintf(c.b, " %%%s = zext i32 %%%s to i64\n", c64, cin) + fmt.Fprintf(c.b, " %%%s = add i64 %%%s, %%%s\n", total1, l64, s64) + fmt.Fprintf(c.b, " %%%s = add i64 %%%s, %%%s\n", total2, total1, c64) + fmt.Fprintf(c.b, " %%%s = icmp ugt i64 %%%s, 4294967295\n", carry, total2) + if err := c.setFlagsAdd(cond, lhs, src, "%"+res); err != nil { + return err + } + if err := c.storeFlagCond(cond, c.flagsCSlot, "%"+carry); err != nil { + return err + } + } else { + l64 := c.newTmp() + s64 := c.newTmp() + b64 := c.newTmp() + subtr := c.newTmp() + borrow := c.newTmp() + fmt.Fprintf(c.b, " %%%s = zext i32 %s to i64\n", l64, lhs) + fmt.Fprintf(c.b, " %%%s = zext i32 %s to i64\n", s64, src) + fmt.Fprintf(c.b, " %%%s = zext i32 %%%s to i64\n", b64, cin) + fmt.Fprintf(c.b, " %%%s = add i64 %%%s, %%%s\n", subtr, s64, b64) + fmt.Fprintf(c.b, " %%%s = icmp ult i64 %%%s, %%%s\n", borrow, l64, subtr) + if err := c.setFlagsSub(cond, lhs, src, "%"+res); err != nil { + return err + } + nb := c.newTmp() + fmt.Fprintf(c.b, " %%%s = xor i1 %%%s, true\n", nb, borrow) + if err := c.storeFlagCond(cond, c.flagsCSlot, "%"+nb); err != nil { + return err + } + } + } + return nil +} + +func (c *armCtx) selectRegPairWrite(hi, lo Reg, cond, newHi, newLo string) error { + if cond == "" { + if err := c.storeReg(hi, newHi); err != nil { + return err + } + return c.storeReg(lo, newLo) + } + cv, err := c.condValue(cond) + if err != nil { + return err + } + oldHi, err := c.loadReg(hi) + if err != nil { + return err + } + oldLo, err := c.loadReg(lo) + if err != nil { + return err + } + selHi := c.newTmp() + selLo := c.newTmp() + fmt.Fprintf(c.b, " %%%s = select i1 %s, i32 %s, i32 %s\n", selHi, cv, newHi, oldHi) + fmt.Fprintf(c.b, " %%%s = select i1 %s, i32 %s, i32 %s\n", selLo, cv, newLo, oldLo) + if err := c.storeReg(hi, "%"+selHi); err != nil { + return err + } + return c.storeReg(lo, "%"+selLo) +} + +func (c *armCtx) lowerARMMUL(cond string, ins Instr) error { + if len(ins.Args) != 2 && len(ins.Args) != 3 { + return fmt.Errorf("arm MUL expects 2 or 3 operands: %q", ins.Raw) + } + var a, b string + var dst Operand + var err error + if len(ins.Args) == 2 { + dst = ins.Args[1] + if dst.Kind != OpReg { + return fmt.Errorf("arm MUL dst must be reg: %q", ins.Raw) + } + a, err = c.eval32(ins.Args[0], false) + if err != nil { + return err + } + b, err = c.loadReg(dst.Reg) + if err != nil { + return err + } + } else { + dst = ins.Args[2] + if dst.Kind != OpReg { + return fmt.Errorf("arm MUL dst must be reg: %q", ins.Raw) + } + a, err = c.eval32(ins.Args[0], false) + if err != nil { + return err + } + b, err = c.eval32(ins.Args[1], false) + if err != nil { + return err + } + } + t := c.newTmp() + fmt.Fprintf(c.b, " %%%s = mul i32 %s, %s\n", t, a, b) + return c.selectRegWrite(dst.Reg, cond, "%"+t) +} + +func (c *armCtx) lowerARMMULLU(cond string, ins Instr) error { + if len(ins.Args) != 3 || ins.Args[2].Kind != OpRegList || len(ins.Args[2].RegList) != 2 { + return fmt.Errorf("arm MULLU expects src, lhs, (hi,lo): %q", ins.Raw) + } + a, err := c.eval32(ins.Args[0], false) + if err != nil { + return err + } + b, err := c.eval32(ins.Args[1], false) + if err != nil { + return err + } + a64 := c.zextI32ToI64(a) + b64 := c.zextI32ToI64(b) + prod := c.newTmp() + fmt.Fprintf(c.b, " %%%s = mul i64 %s, %s\n", prod, a64, b64) + lo := c.newTmp() + hiShift := c.newTmp() + hi := c.newTmp() + fmt.Fprintf(c.b, " %%%s = trunc i64 %%%s to i32\n", lo, prod) + fmt.Fprintf(c.b, " %%%s = lshr i64 %%%s, 32\n", hiShift, prod) + fmt.Fprintf(c.b, " %%%s = trunc i64 %%%s to i32\n", hi, hiShift) + return c.selectRegPairWrite(ins.Args[2].RegList[0], ins.Args[2].RegList[1], cond, "%"+hi, "%"+lo) +} + +func (c *armCtx) lowerARMMULA(cond string, ins Instr) error { + if len(ins.Args) != 4 || ins.Args[3].Kind != OpReg { + return fmt.Errorf("arm MULA expects a, b, acc, dst: %q", ins.Raw) + } + a, err := c.eval32(ins.Args[0], false) + if err != nil { + return err + } + b, err := c.eval32(ins.Args[1], false) + if err != nil { + return err + } + acc, err := c.eval32(ins.Args[2], false) + if err != nil { + return err + } + mul := c.newTmp() + res := c.newTmp() + fmt.Fprintf(c.b, " %%%s = mul i32 %s, %s\n", mul, a, b) + fmt.Fprintf(c.b, " %%%s = add i32 %%%s, %s\n", res, mul, acc) + return c.selectRegWrite(ins.Args[3].Reg, cond, "%"+res) +} + +func (c *armCtx) lowerARMMULAL(cond string, ins Instr) error { + if len(ins.Args) != 3 || ins.Args[2].Kind != OpRegList || len(ins.Args[2].RegList) != 2 { + return fmt.Errorf("arm MULAL expects a, b, (hi,lo): %q", ins.Raw) + } + hiReg := ins.Args[2].RegList[0] + loReg := ins.Args[2].RegList[1] + a, err := c.eval32(ins.Args[0], false) + if err != nil { + return err + } + b, err := c.eval32(ins.Args[1], false) + if err != nil { + return err + } + oldHi, err := c.loadReg(hiReg) + if err != nil { + return err + } + oldLo, err := c.loadReg(loReg) + if err != nil { + return err + } + oldHi64 := c.zextI32ToI64(oldHi) + oldLo64 := c.zextI32ToI64(oldLo) + hiSh := c.newTmp() + old64 := c.newTmp() + fmt.Fprintf(c.b, " %%%s = shl i64 %s, 32\n", hiSh, oldHi64) + fmt.Fprintf(c.b, " %%%s = or i64 %%%s, %s\n", old64, hiSh, oldLo64) + prod := c.newTmp() + fmt.Fprintf(c.b, " %%%s = mul i64 %s, %s\n", prod, c.zextI32ToI64(a), c.zextI32ToI64(b)) + sum := c.newTmp() + fmt.Fprintf(c.b, " %%%s = add i64 %%%s, %%%s\n", sum, old64, prod) + lo := c.newTmp() + hiShift := c.newTmp() + hi := c.newTmp() + fmt.Fprintf(c.b, " %%%s = trunc i64 %%%s to i32\n", lo, sum) + fmt.Fprintf(c.b, " %%%s = lshr i64 %%%s, 32\n", hiShift, sum) + fmt.Fprintf(c.b, " %%%s = trunc i64 %%%s to i32\n", hi, hiShift) + return c.selectRegPairWrite(hiReg, loReg, cond, "%"+hi, "%"+lo) +} + +func (c *armCtx) lowerARMMULAWT(cond string, ins Instr) error { + if len(ins.Args) != 4 || ins.Args[3].Kind != OpReg { + return fmt.Errorf("arm MULAWT expects a, b, acc, dst: %q", ins.Raw) + } + a, err := c.eval32(ins.Args[0], false) + if err != nil { + return err + } + b, err := c.eval32(ins.Args[1], false) + if err != nil { + return err + } + acc, err := c.eval32(ins.Args[2], false) + if err != nil { + return err + } + a64 := c.newTmp() + b64 := c.newTmp() + prod := c.newTmp() + hiShift := c.newTmp() + hi := c.newTmp() + res := c.newTmp() + fmt.Fprintf(c.b, " %%%s = sext i32 %s to i64\n", a64, a) + fmt.Fprintf(c.b, " %%%s = sext i32 %s to i64\n", b64, b) + fmt.Fprintf(c.b, " %%%s = mul i64 %%%s, %%%s\n", prod, a64, b64) + fmt.Fprintf(c.b, " %%%s = ashr i64 %%%s, 32\n", hiShift, prod) + fmt.Fprintf(c.b, " %%%s = trunc i64 %%%s to i32\n", hi, hiShift) + fmt.Fprintf(c.b, " %%%s = add i32 %%%s, %s\n", res, hi, acc) + return c.selectRegWrite(ins.Args[3].Reg, cond, "%"+res) +} + +func (c *armCtx) lowerARMDIVUHW(cond string, ins Instr) error { + if len(ins.Args) != 3 || ins.Args[2].Kind != OpReg { + return fmt.Errorf("arm DIVUHW expects divisor, numerator, dst: %q", ins.Raw) + } + divisor, err := c.eval32(ins.Args[0], false) + if err != nil { + return err + } + numer, err := c.eval32(ins.Args[1], false) + if err != nil { + return err + } + zero := c.newTmp() + fmt.Fprintf(c.b, " %%%s = icmp eq i32 %s, 0\n", zero, divisor) + div := c.newTmp() + fmt.Fprintf(c.b, " %%%s = udiv i32 %s, %s\n", div, numer, divisor) + sel := c.newTmp() + fmt.Fprintf(c.b, " %%%s = select i1 %%%s, i32 0, i32 %%%s\n", sel, zero, div) + return c.selectRegWrite(ins.Args[2].Reg, cond, "%"+sel) +} + +func (c *armCtx) lowerARMCLZ(cond string, ins Instr) error { + if len(ins.Args) != 2 || ins.Args[1].Kind != OpReg { + return fmt.Errorf("arm CLZ expects src, dst: %q", ins.Raw) + } + src, err := c.eval32(ins.Args[0], false) + if err != nil { + return err + } + t := c.newTmp() + fmt.Fprintf(c.b, " %%%s = call i32 @llvm.ctlz.i32(i32 %s, i1 false)\n", t, src) + return c.selectRegWrite(ins.Args[1].Reg, cond, "%"+t) +} + +func (c *armCtx) lowerARMMRC(ins Instr) error { + if len(ins.Args) != 6 || ins.Args[2].Kind != OpReg { + return fmt.Errorf("arm MRC expects coproc, opc1, dst, CRn, CRm, opc2: %q", ins.Raw) + } + part := func(op Operand) string { + s := strings.ToLower(strings.TrimSpace(op.String())) + return strings.TrimPrefix(s, "$") + } + // LLVM inline asm refers to the single output register via $0. + asm := fmt.Sprintf("mrc p%s, %s, $0, %s, %s, %s", part(ins.Args[0]), part(ins.Args[1]), part(ins.Args[3]), part(ins.Args[4]), part(ins.Args[5])) + t := c.newTmp() + fmt.Fprintf(c.b, " %%%s = call i32 asm sideeffect %q, %q()\n", t, asm, "=r,~{memory}") + return c.storeReg(ins.Args[2].Reg, "%"+t) +} diff --git a/arm_lower_atomic.go b/arm_lower_atomic.go new file mode 100644 index 0000000..245a3eb --- /dev/null +++ b/arm_lower_atomic.go @@ -0,0 +1,247 @@ +package plan9asm + +import ( + "fmt" + "strconv" + "strings" +) + +func armNextReg(r Reg) (Reg, error) { + s := strings.ToUpper(string(r)) + if !strings.HasPrefix(s, "R") { + return "", fmt.Errorf("arm: expected integer reg, got %s", r) + } + n, err := strconv.Atoi(strings.TrimPrefix(s, "R")) + if err != nil { + return "", err + } + if n < 0 || n >= 15 { + return "", fmt.Errorf("arm: cannot compute next reg for %s", r) + } + return Reg(fmt.Sprintf("R%d", n+1)), nil +} + +func (c *armCtx) atomicMemPtr(mem MemRef) (string, error) { + addr, _, _, err := c.addrI32(mem, false) + if err != nil { + return "", err + } + pt := c.newTmp() + fmt.Fprintf(c.b, " %%%s = inttoptr i32 %s to ptr\n", pt, addr) + return "%" + pt, nil +} + +func armAtomicType(op string) (LLVMType, int, error) { + switch op { + case "LDREXB", "STREXB": + return I8, 1, nil + case "LDREX", "STREX": + return I32, 4, nil + case "LDREXD", "STREXD": + return I64, 8, nil + default: + return "", 0, fmt.Errorf("arm: unsupported atomic op %s", op) + } +} + +func (c *armCtx) atomicExtendToI32(v string, ty LLVMType) (string, error) { + switch ty { + case I8: + t := c.newTmp() + fmt.Fprintf(c.b, " %%%s = zext i8 %s to i32\n", t, v) + return "%" + t, nil + case I32: + return v, nil + default: + return "", fmt.Errorf("arm: unsupported atomic extend type %s", ty) + } +} + +func (c *armCtx) atomicTruncFromI32(v string, ty LLVMType) (string, error) { + switch ty { + case I8: + t := c.newTmp() + fmt.Fprintf(c.b, " %%%s = trunc i32 %s to i8\n", t, v) + return "%" + t, nil + case I32: + return v, nil + default: + return "", fmt.Errorf("arm: unsupported atomic trunc type %s", ty) + } +} + +func (c *armCtx) lowerAtomic(op string, ins Instr) (ok bool, terminated bool, err error) { + switch op { + case "LDREX", "LDREXB": + if len(ins.Args) != 2 || ins.Args[0].Kind != OpMem || ins.Args[1].Kind != OpReg { + return true, false, fmt.Errorf("arm %s expects mem, reg: %q", op, ins.Raw) + } + ty, align, err := armAtomicType(op) + if err != nil { + return true, false, err + } + ptr, err := c.atomicMemPtr(ins.Args[0].Mem) + if err != nil { + return true, false, err + } + ld := c.newTmp() + fmt.Fprintf(c.b, " %%%s = load atomic %s, ptr %s seq_cst, align %d\n", ld, ty, ptr, align) + v, err := c.atomicExtendToI32("%"+ld, ty) + if err != nil { + return true, false, err + } + fmt.Fprintf(c.b, " store i1 true, ptr %s\n", c.exclusiveValidSlot) + fmt.Fprintf(c.b, " store ptr %s, ptr %s\n", ptr, c.exclusivePtrSlot) + fmt.Fprintf(c.b, " store i8 %d, ptr %s\n", align, c.exclusiveSizeSlot) + fmt.Fprintf(c.b, " store i64 %s, ptr %s\n", c.zextI32ToI64(v), c.exclusiveValueSlot) + return true, false, c.storeReg(ins.Args[1].Reg, v) + + case "LDREXD": + if len(ins.Args) != 2 || ins.Args[0].Kind != OpMem || ins.Args[1].Kind != OpReg { + return true, false, fmt.Errorf("arm LDREXD expects mem, regLo: %q", ins.Raw) + } + hiReg, err := armNextReg(ins.Args[1].Reg) + if err != nil { + return true, false, err + } + ptr, err := c.atomicMemPtr(ins.Args[0].Mem) + if err != nil { + return true, false, err + } + ld := c.newTmp() + fmt.Fprintf(c.b, " %%%s = load atomic i64, ptr %s seq_cst, align 8\n", ld, ptr) + lo := c.newTmp() + hiShift := c.newTmp() + hi := c.newTmp() + fmt.Fprintf(c.b, " %%%s = trunc i64 %%%s to i32\n", lo, ld) + fmt.Fprintf(c.b, " %%%s = lshr i64 %%%s, 32\n", hiShift, ld) + fmt.Fprintf(c.b, " %%%s = trunc i64 %%%s to i32\n", hi, hiShift) + fmt.Fprintf(c.b, " store i1 true, ptr %s\n", c.exclusiveValidSlot) + fmt.Fprintf(c.b, " store ptr %s, ptr %s\n", ptr, c.exclusivePtrSlot) + fmt.Fprintf(c.b, " store i8 8, ptr %s\n", c.exclusiveSizeSlot) + fmt.Fprintf(c.b, " store i64 %%%s, ptr %s\n", ld, c.exclusiveValueSlot) + if err := c.storeReg(ins.Args[1].Reg, "%"+lo); err != nil { + return true, false, err + } + return true, false, c.storeReg(hiReg, "%"+hi) + + case "STREX", "STREXB": + if len(ins.Args) != 3 || ins.Args[0].Kind != OpReg || ins.Args[1].Kind != OpMem || ins.Args[2].Kind != OpReg { + return true, false, fmt.Errorf("arm %s expects srcReg, mem, statusReg: %q", op, ins.Raw) + } + ty, align, err := armAtomicType(op) + if err != nil { + return true, false, err + } + src, err := c.loadReg(ins.Args[0].Reg) + if err != nil { + return true, false, err + } + newv, err := c.atomicTruncFromI32(src, ty) + if err != nil { + return true, false, err + } + ptr, err := c.atomicMemPtr(ins.Args[1].Mem) + if err != nil { + return true, false, err + } + status, err := c.lowerAtomicExclusiveStore(ty, align, ptr, newv) + if err != nil { + return true, false, err + } + return true, false, c.storeReg(ins.Args[2].Reg, status) + + case "STREXD": + if len(ins.Args) != 3 || ins.Args[0].Kind != OpReg || ins.Args[1].Kind != OpMem || ins.Args[2].Kind != OpReg { + return true, false, fmt.Errorf("arm STREXD expects srcLoReg, mem, statusReg: %q", ins.Raw) + } + hiReg, err := armNextReg(ins.Args[0].Reg) + if err != nil { + return true, false, err + } + lo, err := c.loadReg(ins.Args[0].Reg) + if err != nil { + return true, false, err + } + hi, err := c.loadReg(hiReg) + if err != nil { + return true, false, err + } + lo64 := c.zextI32ToI64(lo) + hi64 := c.zextI32ToI64(hi) + hiShift := c.newTmp() + new64 := c.newTmp() + fmt.Fprintf(c.b, " %%%s = shl i64 %s, 32\n", hiShift, hi64) + fmt.Fprintf(c.b, " %%%s = or i64 %%%s, %s\n", new64, hiShift, lo64) + ptr, err := c.atomicMemPtr(ins.Args[1].Mem) + if err != nil { + return true, false, err + } + status, err := c.lowerAtomicExclusiveStore(I64, 8, ptr, "%"+new64) + if err != nil { + return true, false, err + } + return true, false, c.storeReg(ins.Args[2].Reg, status) + } + return false, false, nil +} + +func (c *armCtx) lowerAtomicExclusiveStore(ty LLVMType, align int, ptr string, newv string) (string, error) { + loadedValid := c.newTmp() + loadedPtr := c.newTmp() + ptrMatch := c.newTmp() + loadedSize := c.newTmp() + sizeMatch := c.newTmp() + precond := c.newTmp() + canTry := c.newTmp() + fmt.Fprintf(c.b, " %%%s = load i1, ptr %s\n", loadedValid, c.exclusiveValidSlot) + fmt.Fprintf(c.b, " %%%s = load ptr, ptr %s\n", loadedPtr, c.exclusivePtrSlot) + fmt.Fprintf(c.b, " %%%s = icmp eq ptr %%%s, %s\n", ptrMatch, loadedPtr, ptr) + fmt.Fprintf(c.b, " %%%s = load i8, ptr %s\n", loadedSize, c.exclusiveSizeSlot) + fmt.Fprintf(c.b, " %%%s = icmp eq i8 %%%s, %d\n", sizeMatch, loadedSize, align) + fmt.Fprintf(c.b, " %%%s = and i1 %%%s, %%%s\n", precond, loadedValid, ptrMatch) + fmt.Fprintf(c.b, " %%%s = and i1 %%%s, %%%s\n", canTry, precond, sizeMatch) + + id := c.newTmp() + tryLabel := armLLVMBlockName("strex_try_" + id) + failLabel := armLLVMBlockName("strex_fail_" + id) + mergeLabel := armLLVMBlockName("strex_merge_" + id) + fmt.Fprintf(c.b, " br i1 %%%s, label %%%s, label %%%s\n", canTry, tryLabel, failLabel) + + fmt.Fprintf(c.b, "\n%s:\n", tryLabel) + expected64 := c.newTmp() + fmt.Fprintf(c.b, " %%%s = load i64, ptr %s\n", expected64, c.exclusiveValueSlot) + expected := "" + switch ty { + case I8: + t := c.newTmp() + fmt.Fprintf(c.b, " %%%s = trunc i64 %%%s to i8\n", t, expected64) + expected = "%" + t + case I32: + t := c.newTmp() + fmt.Fprintf(c.b, " %%%s = trunc i64 %%%s to i32\n", t, expected64) + expected = "%" + t + case I64: + expected = "%" + expected64 + default: + return "", fmt.Errorf("arm: unsupported atomic expected type %s", ty) + } + cx := c.newTmp() + fmt.Fprintf(c.b, " %%%s = cmpxchg ptr %s, %s %s, %s %s seq_cst seq_cst, align %d\n", cx, ptr, ty, expected, ty, newv, align) + ok := c.newTmp() + failI1 := c.newTmp() + tryStatus := c.newTmp() + fmt.Fprintf(c.b, " %%%s = extractvalue {%s, i1} %%%s, 1\n", ok, ty, cx) + fmt.Fprintf(c.b, " %%%s = xor i1 %%%s, true\n", failI1, ok) + fmt.Fprintf(c.b, " %%%s = zext i1 %%%s to i32\n", tryStatus, failI1) + fmt.Fprintf(c.b, " br label %%%s\n", mergeLabel) + + fmt.Fprintf(c.b, "\n%s:\n", failLabel) + fmt.Fprintf(c.b, " br label %%%s\n", mergeLabel) + + fmt.Fprintf(c.b, "\n%s:\n", mergeLabel) + status := c.newTmp() + fmt.Fprintf(c.b, " %%%s = phi i32 [ %%%s, %%%s ], [ 1, %%%s ]\n", status, tryStatus, tryLabel, failLabel) + fmt.Fprintf(c.b, " store i1 false, ptr %s\n", c.exclusiveValidSlot) + return "%" + status, nil +} diff --git a/arm_lower_branch.go b/arm_lower_branch.go new file mode 100644 index 0000000..d596a6e --- /dev/null +++ b/arm_lower_branch.go @@ -0,0 +1,223 @@ +package plan9asm + +import ( + "fmt" + "strings" +) + +type armEmitBr func(target string) +type armEmitCondBr func(cond string, target string, fall string) error + +func (c *armCtx) lowerBranch(bi int, op, cond string, ins Instr, emitBr armEmitBr, emitCondBr armEmitCondBr) (ok bool, terminated bool, err error) { + switch op { + case "JMP": + op = "B" + } + switch op { + case "BL", "CALL": + if cond != "" { + return true, false, fmt.Errorf("arm %s.%s unsupported: %q", op, cond, ins.Raw) + } + if len(ins.Args) != 1 { + return true, false, fmt.Errorf("arm %s expects 1 operand: %q", op, ins.Raw) + } + switch ins.Args[0].Kind { + case OpReg: + addr, err := c.loadReg(ins.Args[0].Reg) + if err != nil { + return true, false, err + } + fmt.Fprintf(c.b, " call void asm sideeffect %q, %q(i32 %s)\n", "blx $0", "r,~{memory}", addr) + return true, false, nil + case OpMem: + addr, _, _, err := c.addrI32(ins.Args[0].Mem, false) + if err != nil { + return true, false, err + } + fmt.Fprintf(c.b, " call void asm sideeffect %q, %q(i32 %s)\n", "blx $0", "r,~{memory}", addr) + return true, false, nil + case OpSym: + return true, false, c.callSym(ins.Args[0]) + default: + return true, false, fmt.Errorf("arm %s invalid target: %q", op, ins.Raw) + } + case "B": + if len(ins.Args) != 1 { + return true, false, fmt.Errorf("arm B expects 1 operand: %q", ins.Raw) + } + if cond != "" { + tgt, ok := armBranchTarget(ins.Args[0]) + if !ok { + return true, false, fmt.Errorf("arm B.%s invalid target: %q", cond, ins.Raw) + } + if bi+1 >= len(c.blocks) { + return true, false, fmt.Errorf("arm B.%s needs fallthrough block: %q", cond, ins.Raw) + } + if err := emitCondBr(cond, tgt, c.blocks[bi+1].name); err != nil { + return true, false, err + } + return true, true, nil + } + if ins.Args[0].Kind == OpSym && strings.HasSuffix(ins.Args[0].Sym, "(SB)") { + return true, true, c.tailCallAndRet(ins.Args[0]) + } + tgt, ok := armBranchTarget(ins.Args[0]) + if !ok { + return true, false, fmt.Errorf("arm B invalid target: %q", ins.Raw) + } + emitBr(tgt) + return true, true, nil + case "BEQ", "BNE", "BLT", "BGE", "BGT", "BLE", "BHS", "BHI", "BLS", "BLO", "BCC", "BCS", "BMI": + if len(ins.Args) != 1 { + return true, false, fmt.Errorf("arm %s expects label: %q", op, ins.Raw) + } + tgt, ok := armBranchTarget(ins.Args[0]) + if !ok { + return true, false, fmt.Errorf("arm %s invalid target: %q", op, ins.Raw) + } + if bi+1 >= len(c.blocks) { + return true, false, fmt.Errorf("arm %s needs fallthrough block: %q", op, ins.Raw) + } + branchCond := op[1:] + if branchCond == "CS" { + branchCond = "HS" + } + if branchCond == "CC" { + branchCond = "LO" + } + if err := emitCondBr(branchCond, tgt, c.blocks[bi+1].name); err != nil { + return true, false, err + } + return true, true, nil + } + return false, false, nil +} + +func (c *armCtx) castI32RegToArg(v string, to LLVMType) (string, error) { + switch to { + case I32: + return v, nil + case I16, I8, I1: + t := c.newTmp() + fmt.Fprintf(c.b, " %%%s = trunc i32 %s to %s\n", t, v, to) + return "%" + t, nil + case Ptr: + t := c.newTmp() + fmt.Fprintf(c.b, " %%%s = inttoptr i32 %s to ptr\n", t, v) + return "%" + t, nil + case I64: + t := c.newTmp() + fmt.Fprintf(c.b, " %%%s = zext i32 %s to i64\n", t, v) + return "%" + t, nil + default: + return "", fmt.Errorf("unsupported arg type %s", to) + } +} + +func (c *armCtx) tailCallAndRet(symOp Operand) error { + if symOp.Kind != OpSym { + return fmt.Errorf("arm tailcall expects sym operand, got %s", symOp.String()) + } + s := strings.TrimSpace(symOp.Sym) + if !strings.HasSuffix(s, "(SB)") { + return fmt.Errorf("arm tailcall expects (SB) symbol, got %q", s) + } + callee := c.resolve(strings.TrimSuffix(s, "(SB)")) + csig, ok := c.sigs[callee] + if !ok { + return fmt.Errorf("arm tailcall missing signature for %q", callee) + } + args := make([]string, 0, len(csig.Args)) + for i := 0; i < len(csig.Args); i++ { + r := Reg(fmt.Sprintf("R%d", i)) + if i < len(csig.ArgRegs) { + r = csig.ArgRegs[i] + } + v, err := c.loadReg(r) + if err != nil { + return err + } + val, err := c.castI32RegToArg(v, csig.Args[i]) + if err != nil { + return fmt.Errorf("arm tailcall %q unsupported arg type %s", callee, csig.Args[i]) + } + args = append(args, fmt.Sprintf("%s %s", csig.Args[i], val)) + } + if csig.Ret == Void { + fmt.Fprintf(c.b, " call void %s(%s)\n", llvmGlobal(callee), strings.Join(args, ", ")) + if len(c.fpResults) > 0 { + return c.lowerRET() + } + if c.sig.Ret == Void { + c.b.WriteString(" ret void\n") + return nil + } + fmt.Fprintf(c.b, " ret %s %s\n", c.sig.Ret, llvmZeroValue(c.sig.Ret)) + return nil + } + t := c.newTmp() + fmt.Fprintf(c.b, " %%%s = call %s %s(%s)\n", t, csig.Ret, llvmGlobal(callee), strings.Join(args, ", ")) + if c.sig.Ret != csig.Ret { + return fmt.Errorf("arm tailcall return mismatch: caller %s callee %s", c.sig.Ret, csig.Ret) + } + fmt.Fprintf(c.b, " ret %s %%%s\n", c.sig.Ret, t) + return nil +} + +func (c *armCtx) callSym(symOp Operand) error { + if symOp.Kind != OpSym { + return fmt.Errorf("arm call expects sym operand, got %s", symOp.String()) + } + s := strings.TrimSpace(symOp.Sym) + if !strings.HasSuffix(s, "(SB)") { + return fmt.Errorf("arm call expects (SB) symbol, got %q", s) + } + callee := c.resolve(strings.TrimSuffix(s, "(SB)")) + if callee == "runtime.entersyscall" || callee == "runtime.exitsyscall" { + return nil + } + csig, ok := c.sigs[callee] + if !ok { + csig = FuncSig{Name: callee, Ret: Void} + } + args := make([]string, 0, len(csig.Args)) + for i := 0; i < len(csig.Args); i++ { + r := Reg(fmt.Sprintf("R%d", i)) + if i < len(csig.ArgRegs) { + r = csig.ArgRegs[i] + } + v, err := c.loadReg(r) + if err != nil { + return err + } + val, err := c.castI32RegToArg(v, csig.Args[i]) + if err != nil { + return fmt.Errorf("arm call %q unsupported arg type %s", callee, csig.Args[i]) + } + args = append(args, fmt.Sprintf("%s %s", csig.Args[i], val)) + } + if csig.Ret == Void { + fmt.Fprintf(c.b, " call void %s(%s)\n", llvmGlobal(callee), strings.Join(args, ", ")) + return nil + } + t := c.newTmp() + fmt.Fprintf(c.b, " %%%s = call %s %s(%s)\n", t, csig.Ret, llvmGlobal(callee), strings.Join(args, ", ")) + switch csig.Ret { + case I32: + return c.storeReg(Reg("R0"), "%"+t) + case I16, I8, I1: + z := c.newTmp() + fmt.Fprintf(c.b, " %%%s = zext %s %%%s to i32\n", z, csig.Ret, t) + return c.storeReg(Reg("R0"), "%"+z) + case Ptr: + p := c.newTmp() + fmt.Fprintf(c.b, " %%%s = ptrtoint ptr %%%s to i32\n", p, t) + return c.storeReg(Reg("R0"), "%"+p) + case I64: + tr := c.newTmp() + fmt.Fprintf(c.b, " %%%s = trunc i64 %%%s to i32\n", tr, t) + return c.storeReg(Reg("R0"), "%"+tr) + default: + return fmt.Errorf("arm call %q unsupported return type %s", callee, csig.Ret) + } +} diff --git a/arm_lower_data.go b/arm_lower_data.go new file mode 100644 index 0000000..c29b259 --- /dev/null +++ b/arm_lower_data.go @@ -0,0 +1,108 @@ +package plan9asm + +import ( + "fmt" + "strings" +) + +func (c *armCtx) lowerData(op, cond string, postInc bool, ins Instr) (ok bool, terminated bool, err error) { + switch op { + case "MOVD": + if len(ins.Args) != 2 { + return true, false, fmt.Errorf("arm MOVD expects 2 operands: %q", ins.Raw) + } + src, dst := ins.Args[0], ins.Args[1] + switch { + case src.Kind == OpReg && strings.HasPrefix(string(src.Reg), "F") && dst.Kind == OpMem: + v, err := c.loadFReg(src.Reg) + if err != nil { + return true, false, err + } + return true, false, c.storeMem(dst.Mem, 64, postInc, v) + case src.Kind == OpMem && dst.Kind == OpReg && strings.HasPrefix(string(dst.Reg), "F"): + v, err := c.loadMem(src.Mem, 64, postInc, false) + if err != nil { + return true, false, err + } + return true, false, c.storeFReg(dst.Reg, v) + case src.Kind == OpReg && strings.HasPrefix(string(src.Reg), "F") && dst.Kind == OpReg && strings.HasPrefix(string(dst.Reg), "F"): + v, err := c.loadFReg(src.Reg) + if err != nil { + return true, false, err + } + return true, false, c.storeFReg(dst.Reg, v) + default: + return true, false, fmt.Errorf("arm MOVD unsupported operands: %q", ins.Raw) + } + case "MOVW": + if len(ins.Args) != 2 { + return true, false, fmt.Errorf("arm MOVW expects 2 operands: %q", ins.Raw) + } + src, dst := ins.Args[0], ins.Args[1] + v := "" + if src.Kind == OpMem { + v, err = c.loadMem(src.Mem, 32, postInc, false) + } else { + v, err = c.eval32(src, false) + } + if err != nil { + return true, false, err + } + return true, false, c.storeARMValue(dst, v, 32, cond, postInc, ins.Raw) + case "MOVB", "MOVBU": + if len(ins.Args) != 2 { + return true, false, fmt.Errorf("arm %s expects 2 operands: %q", op, ins.Raw) + } + src, dst := ins.Args[0], ins.Args[1] + v := "" + if src.Kind == OpMem { + v, err = c.loadMem(src.Mem, 8, postInc, op == "MOVB") + } else { + v, err = c.eval32(src, false) + } + if err != nil { + return true, false, err + } + return true, false, c.storeARMValue(dst, v, 8, cond, postInc, ins.Raw) + } + return false, false, nil +} + +func (c *armCtx) selectRegWrite(dst Reg, cond string, newV string) error { + if cond == "" || strings.EqualFold(cond, "AL") { + return c.storeReg(dst, newV) + } + cv, err := c.condValue(cond) + if err != nil { + return err + } + oldV, err := c.loadReg(dst) + if err != nil { + return err + } + t := c.newTmp() + fmt.Fprintf(c.b, " %%%s = select i1 %s, i32 %s, i32 %s\n", t, cv, newV, oldV) + return c.storeReg(dst, "%"+t) +} + +func (c *armCtx) storeARMValue(dst Operand, v string, bits int, cond string, postInc bool, raw string) error { + switch dst.Kind { + case OpReg: + // ARM register file holds full words; loads already extended to i32. + return c.selectRegWrite(dst.Reg, cond, v) + case OpMem: + if cond != "" { + return fmt.Errorf("arm conditional store to memory unsupported: %q", raw) + } + return c.storeMem(dst.Mem, bits, postInc, v) + case OpFP: + if cond != "" { + return fmt.Errorf("arm conditional store to FP slot unsupported: %q", raw) + } + return c.storeFPResult32(dst.FPOffset, v) + case OpSym: + return nil + default: + return fmt.Errorf("arm unsupported dst operand: %q", raw) + } +} diff --git a/arm_lower_movm.go b/arm_lower_movm.go new file mode 100644 index 0000000..a32b0b8 --- /dev/null +++ b/arm_lower_movm.go @@ -0,0 +1,131 @@ +package plan9asm + +import ( + "fmt" + "strings" +) + +func armDecodeMOVM(raw string) (mode string, writeback bool) { + s := strings.ToUpper(strings.TrimSpace(raw)) + switch { + case strings.Contains(s, "IAW") || strings.Contains(s, "IA.W"): + return "IA", true + case strings.Contains(s, "DBW") || strings.Contains(s, "DB.W"): + return "DB", true + case strings.Contains(s, "WP"): + return "DB", true + case strings.Contains(s, ".IA"): + return "IA", false + case strings.Contains(s, ".IB"): + return "IB", false + case strings.Contains(s, ".DB"): + return "DB", false + case strings.Contains(s, ".DA"): + return "DA", false + default: + return "IA", false + } +} + +func armRegListAllGPR(regs []Reg) bool { + for _, r := range regs { + p, _, ok := regRangeParts(r) + if !ok || p != "R" { + return false + } + } + return true +} + +func (c *armCtx) lowerMOVM(rawOp string, ins Instr) (ok bool, terminated bool, err error) { + if !strings.HasPrefix(rawOp, "MOVM") { + return false, false, nil + } + if len(ins.Args) != 2 { + return true, false, fmt.Errorf("arm MOVM expects 2 operands: %q", ins.Raw) + } + mode, writeback := armDecodeMOVM(rawOp) + var mem MemRef + var regs []Reg + load := false + switch { + case ins.Args[0].Kind == OpMem && ins.Args[1].Kind == OpRegList: + mem = ins.Args[0].Mem + regs = ins.Args[1].RegList + load = true + case ins.Args[0].Kind == OpRegList && ins.Args[1].Kind == OpMem: + mem = ins.Args[1].Mem + regs = ins.Args[0].RegList + default: + return true, false, fmt.Errorf("arm MOVM expects mem/reglist operands: %q", ins.Raw) + } + if !armRegListAllGPR(regs) { + return true, false, fmt.Errorf("arm MOVM currently supports only general-purpose reg lists: %q", ins.Raw) + } + baseAddr, baseReg, _, err := c.addrI32(mem, false) + if err != nil { + return true, false, err + } + count := int64(len(regs)) + start := baseAddr + switch mode { + case "IA": + case "IB": + t := c.newTmp() + fmt.Fprintf(c.b, " %%%s = add i32 %s, 4\n", t, start) + start = "%" + t + case "DB": + t := c.newTmp() + fmt.Fprintf(c.b, " %%%s = add i32 %s, %d\n", t, start, -4*count) + start = "%" + t + case "DA": + t := c.newTmp() + fmt.Fprintf(c.b, " %%%s = add i32 %s, %d\n", t, start, -4*(count-1)) + start = "%" + t + default: + return true, false, fmt.Errorf("arm MOVM unsupported addressing mode %q: %q", mode, ins.Raw) + } + + addr := start + for i, r := range regs { + pt := c.newTmp() + fmt.Fprintf(c.b, " %%%s = inttoptr i32 %s to ptr\n", pt, addr) + ptr := "%" + pt + if load { + t := c.newTmp() + fmt.Fprintf(c.b, " %%%s = load i32, ptr %s\n", t, ptr) + if err := c.storeReg(r, "%"+t); err != nil { + return true, false, err + } + } else { + v, err := c.loadReg(r) + if err != nil { + return true, false, err + } + fmt.Fprintf(c.b, " store i32 %s, ptr %s\n", v, ptr) + } + if i+1 < len(regs) { + t := c.newTmp() + fmt.Fprintf(c.b, " %%%s = add i32 %s, 4\n", t, addr) + addr = "%" + t + } + } + + if writeback { + final := baseAddr + switch mode { + case "IA", "IB": + t := c.newTmp() + fmt.Fprintf(c.b, " %%%s = add i32 %s, %d\n", t, baseAddr, 4*count) + final = "%" + t + case "DB", "DA": + t := c.newTmp() + fmt.Fprintf(c.b, " %%%s = add i32 %s, %d\n", t, baseAddr, -4*count) + final = "%" + t + } + if err := c.storeReg(baseReg, final); err != nil { + return true, false, err + } + } + return true, false, nil +} diff --git a/arm_lower_syscall.go b/arm_lower_syscall.go new file mode 100644 index 0000000..dd47741 --- /dev/null +++ b/arm_lower_syscall.go @@ -0,0 +1,84 @@ +package plan9asm + +import "fmt" + +func (c *armCtx) zextI32ToI64(v32 string) string { + t := c.newTmp() + fmt.Fprintf(c.b, " %%%s = zext i32 %s to i64\n", t, v32) + return "%" + t +} + +func (c *armCtx) truncI64ToI32(v64 string) string { + t := c.newTmp() + fmt.Fprintf(c.b, " %%%s = trunc i64 %s to i32\n", t, v64) + return "%" + t +} + +func (c *armCtx) lowerSyscall(op string, ins Instr) (ok bool, terminated bool, err error) { + switch op { + case "SWI": + if len(ins.Args) > 1 || (len(ins.Args) == 1 && ins.Args[0].Kind != OpImm) { + return true, false, fmt.Errorf("arm SWI expects optional immediate operand: %q", ins.Raw) + } + num32 := "" + if len(ins.Args) == 1 && ins.Args[0].Imm != 0 { + num32 = c.imm32(ins.Args[0].Imm) + } else { + num32, err = c.loadReg(Reg("R7")) + if err != nil { + return true, false, err + } + } + loadArg := func(r Reg) (string, error) { + if _, ok := c.regSlot[r]; !ok { + return "0", nil + } + return c.loadReg(r) + } + a1, err := loadArg(Reg("R0")) + if err != nil { + return true, false, err + } + a2, err := loadArg(Reg("R1")) + if err != nil { + return true, false, err + } + a3, err := loadArg(Reg("R2")) + if err != nil { + return true, false, err + } + a4, err := loadArg(Reg("R3")) + if err != nil { + return true, false, err + } + a5, err := loadArg(Reg("R4")) + if err != nil { + return true, false, err + } + a6, err := loadArg(Reg("R5")) + if err != nil { + return true, false, err + } + r := c.newTmp() + fmt.Fprintf(c.b, " %%%s = call i64 @syscall(i64 %s, i64 %s, i64 %s, i64 %s, i64 %s, i64 %s, i64 %s)\n", + r, + c.zextI32ToI64(num32), + c.zextI32ToI64(a1), + c.zextI32ToI64(a2), + c.zextI32ToI64(a3), + c.zextI32ToI64(a4), + c.zextI32ToI64(a5), + c.zextI32ToI64(a6), + ) + if err := c.storeReg(Reg("R0"), c.truncI64ToI32("%"+r)); err != nil { + return true, false, err + } + if _, ok := c.regSlot[Reg("R1")]; ok { + if err := c.storeReg(Reg("R1"), "0"); err != nil { + return true, false, err + } + } + return true, false, nil + } + return false, false, nil +} diff --git a/arm_needed.go b/arm_needed.go new file mode 100644 index 0000000..491c432 --- /dev/null +++ b/arm_needed.go @@ -0,0 +1,31 @@ +package plan9asm + +import "strings" + +// funcNeedsARMCFG decides whether ARM lowering needs the CFG-based path. +// The linear prototype only handles straight-line arithmetic/data movement. +func funcNeedsARMCFG(fn Func) bool { + for _, ins := range fn.Instrs { + if ins.Op == OpLABEL { + return true + } + rawOp := strings.ToUpper(string(ins.Op)) + op := rawOp + if dot := strings.IndexByte(op, '.'); dot >= 0 { + op = op[:dot] + } + switch Op(op) { + case OpTEXT, OpRET, OpBYTE: + continue + case "MOVW", "MOVB", "MOVBU", "ADD", "SUB", "AND", "ORR", "EOR", "RSB": + // The linear ARM path cannot handle conditional execution or post-inc. + if strings.Contains(rawOp, ".") { + return true + } + continue + default: + return true + } + } + return false +} diff --git a/arm_parser_immediate_test.go b/arm_parser_immediate_test.go new file mode 100644 index 0000000..3cc32db --- /dev/null +++ b/arm_parser_immediate_test.go @@ -0,0 +1,18 @@ +package plan9asm + +import "testing" + +func TestParseARMSymbolicImmediateExpr(t *testing.T) { + file, err := Parse(ArchARM, `TEXT ·f(SB),NOSPLIT,$0-0 + MOVW $(16 + callbackArgs__size), R0 + RET +`) + if err != nil { + t.Fatalf("Parse() error = %v", err) + } + if got := file.Funcs[0].Instrs[1].Args[0]; got.Kind != OpImm { + t.Fatalf("unexpected operand kind: %#v", got) + } else if got.ImmRaw == "" { + t.Fatalf("symbolic immediate should be marked unresolved: %#v", got) + } +} diff --git a/arm_parser_test.go b/arm_parser_test.go new file mode 100644 index 0000000..50702b3 --- /dev/null +++ b/arm_parser_test.go @@ -0,0 +1,39 @@ +package plan9asm + +import "testing" + +func TestParseARMRegRangeList(t *testing.T) { + file, err := Parse(ArchARM, `TEXT runtime·memclrNoHeapPointers(SB),NOSPLIT,$0-8 + MOVM.IA.W [R0-R7], (R8) + RET +`) + if err != nil { + t.Fatalf("Parse() error = %v", err) + } + got := file.Funcs[0].Instrs[1].Args[0] + if got.Kind != OpRegList || len(got.RegList) != 8 || got.RegList[0] != Reg("R0") || got.RegList[7] != Reg("R7") { + t.Fatalf("unexpected reg list: %#v", got) + } +} + +func TestParseARMShiftOperands(t *testing.T) { + file, err := Parse(ArchARM, `TEXT ·block(SB),NOSPLIT,$0-0 + ADD R2@>(32-7), R3, R2 + MOVW R4>>R5, R6 + TEQ R7->1, R7 + RET +`) + if err != nil { + t.Fatalf("Parse() error = %v", err) + } + ins := file.Funcs[0].Instrs + if ins[1].Args[0].Kind != OpRegShift || ins[1].Args[0].ShiftOp != ShiftRotate || ins[1].Args[0].ShiftAmount != 25 { + t.Fatalf("unexpected rotate operand: %#v", ins[1].Args[0]) + } + if ins[2].Args[0].Kind != OpRegShift || ins[2].Args[0].ShiftOp != ShiftRight || ins[2].Args[0].ShiftReg != Reg("R5") { + t.Fatalf("unexpected register shift operand: %#v", ins[2].Args[0]) + } + if ins[3].Args[0].Kind != OpRegShift || ins[3].Args[0].ShiftOp != ShiftArith || ins[3].Args[0].ShiftAmount != 1 { + t.Fatalf("unexpected arithmetic shift operand: %#v", ins[3].Args[0]) + } +} diff --git a/arm_translate.go b/arm_translate.go new file mode 100644 index 0000000..28571a3 --- /dev/null +++ b/arm_translate.go @@ -0,0 +1,14 @@ +package plan9asm + +import "strings" + +func emitARMPrelude(b *strings.Builder) { + b.WriteString("declare i64 @syscall(i64, i64, i64, i64, i64, i64, i64)\n") + b.WriteString("declare i32 @llvm.fshr.i32(i32, i32, i32)\n") + b.WriteString("declare i32 @llvm.ctlz.i32(i32, i1)\n") + b.WriteString("\n") +} + +func armLLVMBlockName(src string) string { + return arm64LLVMBlockName(src) +} diff --git a/arm_translate_cfg.go b/arm_translate_cfg.go new file mode 100644 index 0000000..65aa86b --- /dev/null +++ b/arm_translate_cfg.go @@ -0,0 +1,186 @@ +package plan9asm + +import ( + "fmt" + "strings" +) + +func translateFuncARM(b *strings.Builder, fn Func, sig FuncSig, resolve func(string) string, sigs map[string]FuncSig, annotateSource bool) error { + fmt.Fprintf(b, "define %s %s(", sig.Ret, llvmGlobal(sig.Name)) + for i, t := range sig.Args { + if i > 0 { + b.WriteString(", ") + } + fmt.Fprintf(b, "%s %%arg%d", t, i) + } + b.WriteString(")") + if sig.Attrs != "" { + b.WriteString(" " + sig.Attrs) + } + b.WriteString(" {\n") + + c := newARMCtx(b, fn, sig, resolve, sigs, annotateSource) + if err := c.emitEntryAllocasAndArgInit(); err != nil { + return err + } + if err := c.lowerBlocks(); err != nil { + return err + } + + b.WriteString("}\n") + return nil +} + +func (c *armCtx) lowerBlocks() error { + emitBr := func(target string) { + fmt.Fprintf(c.b, " br label %%%s\n", armLLVMBlockName(target)) + } + emitCondBr := func(cond string, target string, fall string) error { + cv, err := c.condValue(cond) + if err != nil { + return err + } + fmt.Fprintf(c.b, " br i1 %s, label %%%s, label %%%s\n", cv, armLLVMBlockName(target), armLLVMBlockName(fall)) + return nil + } + + for bi := 0; bi < len(c.blocks); bi++ { + blk := c.blocks[bi] + if bi != 0 { + fmt.Fprintf(c.b, "\n%s:\n", armLLVMBlockName(blk.name)) + } + terminated := false + for _, ins := range blk.instrs { + c.emitSourceComment(ins) + term, err := c.lowerInstr(bi, ins, emitBr, emitCondBr) + if err != nil { + return err + } + if term { + terminated = true + break + } + } + if terminated { + continue + } + if bi+1 < len(c.blocks) { + emitBr(c.blocks[bi+1].name) + continue + } + c.lowerRetZero() + } + return nil +} + +func (c *armCtx) lowerInstr(bi int, ins Instr, emitBr armEmitBr, emitCondBr armEmitCondBr) (bool, error) { + rawOp := strings.ToUpper(string(ins.Op)) + baseOp, cond, postInc, setFlags := armDecodeOp(rawOp) + switch baseOp { + case string(OpTEXT), string(OpBYTE): + return false, nil + case string(OpRET): + return true, c.lowerRET() + case "UNDEF": + c.b.WriteString(" call void asm sideeffect \"udf #0\", \"~{memory}\"()\n") + c.b.WriteString(" unreachable\n") + return true, nil + case "MOVM": + if ok, term, err := c.lowerMOVM(rawOp, ins); ok { + return term, err + } + return false, fmt.Errorf("arm: unsupported instruction %s", ins.Op) + case "PCDATA", "FUNCDATA", "NO_LOCAL_POINTERS", "WORD", "NOP", "DMB", "#IFDEF", "#ELSE", "#ENDIF": + return false, nil + } + if ok, term, err := c.lowerData(baseOp, cond, postInc, ins); ok { + return term, err + } + if ok, term, err := c.lowerArith(baseOp, cond, setFlags, ins); ok { + return term, err + } + if ok, term, err := c.lowerAtomic(baseOp, ins); ok { + return term, err + } + if ok, term, err := c.lowerSyscall(baseOp, ins); ok { + return term, err + } + if ok, term, err := c.lowerBranch(bi, baseOp, cond, ins, emitBr, emitCondBr); ok { + return term, err + } + return false, fmt.Errorf("arm: unsupported instruction %s", ins.Op) +} + +func (c *armCtx) lowerRET() error { + if len(c.fpResults) == 0 { + r0, err := c.loadReg(Reg("R0")) + if err != nil { + return err + } + switch c.sig.Ret { + case Void: + c.b.WriteString(" ret void\n") + case I1, I8, I16: + t := c.newTmp() + fmt.Fprintf(c.b, " %%%s = trunc i32 %s to %s\n", t, r0, c.sig.Ret) + fmt.Fprintf(c.b, " ret %s %%%s\n", c.sig.Ret, t) + case I32: + fmt.Fprintf(c.b, " ret i32 %s\n", r0) + case Ptr: + t := c.newTmp() + fmt.Fprintf(c.b, " %%%s = inttoptr i32 %s to ptr\n", t, r0) + fmt.Fprintf(c.b, " ret ptr %%%s\n", t) + default: + return fmt.Errorf("arm: unsupported return type %s", c.sig.Ret) + } + return nil + } + if len(c.fpResults) == 1 { + slot := c.fpResults[0] + var v string + var err error + if c.fpResWritten[slot.Index] || c.fpResAddrTaken[slot.Index] { + v, err = c.loadFPResult(slot) + } else { + v, err = c.loadRetSlotFallback(slot) + } + if err != nil { + return err + } + fmt.Fprintf(c.b, " ret %s %s\n", c.sig.Ret, v) + return nil + } + cur := "undef" + last := "" + for _, slot := range c.fpResults { + var v string + var err error + if c.fpResWritten[slot.Index] || c.fpResAddrTaken[slot.Index] { + v, err = c.loadFPResult(slot) + } else { + v, err = c.loadRetSlotFallback(slot) + } + if err != nil { + return err + } + name := c.newTmp() + fmt.Fprintf(c.b, " %%%s = insertvalue %s %s, %s %s, %d\n", name, c.sig.Ret, cur, slot.Type, v, slot.Index) + cur = "%" + name + last = cur + } + fmt.Fprintf(c.b, " ret %s %s\n", c.sig.Ret, last) + return nil +} + +func (c *armCtx) lowerRetZero() { + switch c.sig.Ret { + case Void: + c.b.WriteString(" ret void\n") + case I1: + c.b.WriteString(" ret i1 false\n") + case Ptr: + c.b.WriteString(" ret ptr null\n") + default: + fmt.Fprintf(c.b, " ret %s 0\n", c.sig.Ret) + } +} diff --git a/arm_translate_test.go b/arm_translate_test.go new file mode 100644 index 0000000..0dd92a1 --- /dev/null +++ b/arm_translate_test.go @@ -0,0 +1,52 @@ +package plan9asm + +import ( + "strings" + "testing" +) + +func TestTranslateARMLinearAdd(t *testing.T) { + file, err := Parse(ArchARM, `TEXT ·Add(SB),NOSPLIT,$0-12 + MOVW a+0(FP), R0 + ADD b+4(FP), R0 + MOVW R0, ret+8(FP) + RET +`) + if err != nil { + t.Fatalf("Parse() error = %v", err) + } + ll, err := Translate(file, Options{ + TargetTriple: "armv7-unknown-linux-gnueabihf", + Goarch: "arm", + ResolveSym: func(sym string) string { return "example." + strings.TrimPrefix(sym, "·") }, + Sigs: map[string]FuncSig{ + "example.Add": { + Name: "example.Add", + Args: []LLVMType{I32, I32}, + Ret: I32, + Frame: FrameLayout{ + Params: []FrameSlot{ + {Offset: 0, Type: I32, Index: 0, Field: -1}, + {Offset: 4, Type: I32, Index: 1, Field: -1}, + }, + Results: []FrameSlot{ + {Offset: 8, Type: I32, Index: 0, Field: -1}, + }, + }, + }, + }, + }) + if err != nil { + t.Fatalf("Translate() error = %v", err) + } + for _, want := range []string{ + "target triple = \"armv7-unknown-linux-gnueabihf\"", + "define i32 @example.Add(i32 %arg0, i32 %arg1)", + "add i32", + "ret i32", + } { + if !strings.Contains(ll, want) { + t.Fatalf("missing %q in output:\n%s", want, ll) + } + } +} diff --git a/cmd/plan9asm/main.go b/cmd/plan9asm/main.go index 06e4543..ba8bc65 100644 --- a/cmd/plan9asm/main.go +++ b/cmd/plan9asm/main.go @@ -82,7 +82,7 @@ func runList(args []string) error { goarch string ) fs.StringVar(&goos, "goos", runtime.GOOS, "target GOOS") - fs.StringVar(&goarch, "goarch", runtime.GOARCH, "target GOARCH (amd64/arm64/386)") + fs.StringVar(&goarch, "goarch", runtime.GOARCH, "target GOARCH (amd64/arm64/arm/386)") if err := fs.Parse(args); err != nil { return err } @@ -150,7 +150,7 @@ func runTranspile(args []string) error { fs.BoolVar(&annotate, "annotate", true, "emit source asm lines as IR comments") fs.StringVar(&inFile, "i", "", "Plan9 asm .s file path") fs.StringVar(&outFile, "o", "", "output .ll file path") - fs.StringVar(&goarch, "goarch", runtime.GOARCH, "target GOARCH (amd64/arm64/386)") + fs.StringVar(&goarch, "goarch", runtime.GOARCH, "target GOARCH (amd64/arm64/arm/386)") fs.StringVar(&goos, "goos", runtime.GOOS, "target GOOS") fs.StringVar(&metaFile, "meta", "", "optional output metadata json path") fs.StringVar(&patterns, "patterns", "", "deprecated comma-separated package patterns") @@ -413,10 +413,12 @@ func toPlan9Arch(goarch string) (plan9asm.Arch, error) { switch goarch { case "amd64", "386": return plan9asm.ArchAMD64, nil + case "arm": + return plan9asm.ArchARM, nil case "arm64": return plan9asm.ArchARM64, nil default: - return "", fmt.Errorf("unsupported -goarch %q (expect amd64/arm64/386)", goarch) + return "", fmt.Errorf("unsupported -goarch %q (expect amd64/arm64/arm/386)", goarch) } } diff --git a/cmd/plan9asmll/go.mod b/cmd/plan9asmll/go.mod index e0a4db9..c0127ee 100644 --- a/cmd/plan9asmll/go.mod +++ b/cmd/plan9asmll/go.mod @@ -1,16 +1,16 @@ module github.com/goplus/plan9asm/cmd/plan9asmll -go 1.21 +go 1.24.0 require ( - github.com/goplus/llvm v0.8.5 + github.com/goplus/llvm v0.8.6 github.com/goplus/plan9asm v0.0.0 - golang.org/x/tools v0.24.0 + golang.org/x/tools v0.42.0 ) require ( - golang.org/x/mod v0.20.0 // indirect - golang.org/x/sync v0.8.0 // indirect + golang.org/x/mod v0.33.0 // indirect + golang.org/x/sync v0.19.0 // indirect ) replace github.com/goplus/plan9asm => ../.. diff --git a/cmd/plan9asmll/go.sum b/cmd/plan9asmll/go.sum index 531a664..c5e647d 100644 --- a/cmd/plan9asmll/go.sum +++ b/cmd/plan9asmll/go.sum @@ -1,8 +1,10 @@ -github.com/goplus/llvm v0.8.5 h1:DUnFeYC3Rco622tBEKGg8xkigRAV2fh5ZIfBCt7gOSs= -github.com/goplus/llvm v0.8.5/go.mod h1:PeVK8GgzxwAYCiMiUAJb5wJR6xbhj989tu9oulKLLT4= -golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0= -golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= -golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24= -golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/goplus/llvm v0.8.6 h1:hqt9pM5q5zCbx87MqErStMZRglg+irarcZalinu8z7g= +github.com/goplus/llvm v0.8.6/go.mod h1:PeVK8GgzxwAYCiMiUAJb5wJR6xbhj989tu9oulKLLT4= +golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8= +golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w= +golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= +golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k= +golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0= diff --git a/cmd/plan9asmll/main.go b/cmd/plan9asmll/main.go index e56e89f..e29b284 100644 --- a/cmd/plan9asmll/main.go +++ b/cmd/plan9asmll/main.go @@ -86,7 +86,7 @@ type compileConfig struct { func main() { var ( goos = flag.String("goos", runtime.GOOS, "target GOOS") - goarch = flag.String("goarch", runtime.GOARCH, "target GOARCH (amd64/arm64/386)") + goarch = flag.String("goarch", runtime.GOARCH, "target GOARCH (amd64/arm64/arm/386)") targets = flag.String("targets", "", "comma-separated GOOS/GOARCH list (e.g. linux/amd64,windows/arm64)") allTargets = flag.Bool("all-targets", false, "run matrix: darwin/{amd64,arm64} linux/{amd64,arm64,386} windows/{amd64,arm64,386}") patterns = flag.String("patterns", "std", "comma-separated package patterns") @@ -527,6 +527,8 @@ func toPlan9Arch(goarch string) (plan9asm.Arch, error) { switch goarch { case "amd64", "386": return plan9asm.ArchAMD64, nil + case "arm": + return plan9asm.ArchARM, nil case "arm64": return plan9asm.ArchARM64, nil default: diff --git a/cmd/plan9asmscan/main.go b/cmd/plan9asmscan/main.go index 294815d..a46fdf6 100644 --- a/cmd/plan9asmscan/main.go +++ b/cmd/plan9asmscan/main.go @@ -77,15 +77,15 @@ var ( func main() { var ( goos = flag.String("goos", runtime.GOOS, "target GOOS") - goarch = flag.String("goarch", runtime.GOARCH, "target GOARCH (amd64/arm64)") + goarch = flag.String("goarch", runtime.GOARCH, "target GOARCH (amd64/arm64/arm)") out = flag.String("out", "", "write report to file (default stdout)") format = flag.String("format", "md", "output format: md|json") repoRoot = flag.String("repo-root", ".", "llgo repository root for extracting supported ops") ) flag.Parse() - if *goarch != "amd64" && *goarch != "arm64" { - fatalf("unsupported -goarch %q (expect amd64/arm64)", *goarch) + if *goarch != "amd64" && *goarch != "arm64" && *goarch != "arm" { + fatalf("unsupported -goarch %q (expect amd64/arm64/arm)", *goarch) } arch, err := toPlan9Arch(*goarch) if err != nil { @@ -136,6 +136,8 @@ func toPlan9Arch(goarch string) (plan9asm.Arch, error) { switch goarch { case "amd64": return plan9asm.ArchAMD64, nil + case "arm": + return plan9asm.ArchARM, nil case "arm64": return plan9asm.ArchARM64, nil default: @@ -146,11 +148,16 @@ func toPlan9Arch(goarch string) (plan9asm.Arch, error) { func listStdPackages(goos, goarch string) ([]pkgJSON, error) { cmd := exec.Command("go", "list", "-json", "std") cmd.Env = append(os.Environ(), + "CGO_ENABLED=0", "GOOS="+goos, "GOARCH="+goarch, ) - out, err := cmd.Output() + out, err := cmd.CombinedOutput() if err != nil { + msg := strings.TrimSpace(string(out)) + if msg != "" { + return nil, fmt.Errorf("go list -json std: %w: %s", err, msg) + } return nil, fmt.Errorf("go list -json std: %w", err) } diff --git a/cmd/plan9asmscan/main_test.go b/cmd/plan9asmscan/main_test.go index d24dd5f..6dbf836 100644 --- a/cmd/plan9asmscan/main_test.go +++ b/cmd/plan9asmscan/main_test.go @@ -14,6 +14,9 @@ func TestToPlan9Arch(t *testing.T) { if got, err := toPlan9Arch("amd64"); err != nil || got != plan9asm.ArchAMD64 { t.Fatalf("toPlan9Arch(amd64) = (%q, %v)", got, err) } + if got, err := toPlan9Arch("arm"); err != nil || got != plan9asm.ArchARM { + t.Fatalf("toPlan9Arch(arm) = (%q, %v)", got, err) + } if got, err := toPlan9Arch("arm64"); err != nil || got != plan9asm.ArchARM64 { t.Fatalf("toPlan9Arch(arm64) = (%q, %v)", got, err) } diff --git a/go_translate.go b/go_translate.go index df334ad..ac38e5d 100644 --- a/go_translate.go +++ b/go_translate.go @@ -142,6 +142,8 @@ func goArchFor(goarch string) (Arch, error) { switch goarch { case "amd64", "386": return ArchAMD64, nil + case "arm": + return ArchARM, nil case "arm64": return ArchARM64, nil default: diff --git a/go_translate_helpers_test.go b/go_translate_helpers_test.go index 75713a2..4fc7f58 100644 --- a/go_translate_helpers_test.go +++ b/go_translate_helpers_test.go @@ -44,6 +44,9 @@ func TestGoHelperArchTupleAndSymParsing(t *testing.T) { if got, err := goArchFor("amd64"); err != nil || got != ArchAMD64 { t.Fatalf("goArchFor amd64 = (%q, %v), want %q", got, err, ArchAMD64) } + if got, err := goArchFor("arm"); err != nil || got != ArchARM { + t.Fatalf("goArchFor arm = (%q, %v), want %q", got, err, ArchARM) + } if got, err := goArchFor("arm64"); err != nil || got != ArchARM64 { t.Fatalf("goArchFor arm64 = (%q, %v), want %q", got, err, ArchARM64) } diff --git a/stdlib_bytealg_arm_compile_test.go b/stdlib_bytealg_arm_compile_test.go new file mode 100644 index 0000000..1c2793a --- /dev/null +++ b/stdlib_bytealg_arm_compile_test.go @@ -0,0 +1,205 @@ +//go:build !llgo +// +build !llgo + +package plan9asm + +import ( + "os" + "os/exec" + "path/filepath" + "runtime" + "strings" + "testing" +) + +func TestStdlibInternalBytealg_ARM_Compile(t *testing.T) { + llc, _, ok := findLlcAndClang(t) + if !ok { + t.Skip("llc not found") + } + goroot := runtime.GOROOT() + if goroot == "" { + t.Skip("GOROOT not available") + } + sfiles := stdlibBytealgARMSigs(goroot) + if len(sfiles) == 0 { + t.Skip("internal/bytealg arm asm files not present in this GOROOT") + } + resolve := func(sym string) string { + sym = goStripABISuffix(sym) + if strings.HasPrefix(sym, "runtime·") { + sym = strings.ReplaceAll(sym, "∕", "/") + return strings.ReplaceAll(sym, "·", ".") + } + if strings.HasPrefix(sym, "·") { + return "internal/bytealg." + strings.TrimPrefix(sym, "·") + } + if !strings.Contains(sym, "·") && !strings.Contains(sym, ".") && !strings.Contains(sym, "/") { + return "internal/bytealg." + sym + } + sym = strings.ReplaceAll(sym, "∕", "/") + return strings.ReplaceAll(sym, "·", ".") + } + + triple := "armv7-unknown-linux-gnueabihf" + compiled := 0 + for path, sigs := range sfiles { + src, err := os.ReadFile(path) + if err != nil { + t.Fatalf("read %s: %v", path, err) + } + file, err := Parse(ArchARM, string(src)) + if err != nil { + t.Fatalf("parse %s: %v", path, err) + } + ll, err := Translate(file, Options{ + TargetTriple: triple, + ResolveSym: resolve, + Sigs: sigs, + Goarch: "arm", + }) + if err != nil { + t.Fatalf("translate %s: %v", path, err) + } + tmp := t.TempDir() + llPath := filepath.Join(tmp, filepath.Base(path)+".ll") + objPath := filepath.Join(tmp, filepath.Base(path)+".o") + if err := os.WriteFile(llPath, []byte(ll), 0644); err != nil { + t.Fatal(err) + } + cmd := exec.Command(llc, "-mtriple="+triple, "-filetype=obj", llPath, "-o", objPath) + out, err := cmd.CombinedOutput() + if err != nil { + s := string(out) + if strings.Contains(s, "No available targets") || + strings.Contains(s, "no targets are registered") || + strings.Contains(s, "unknown target triple") || + strings.Contains(s, "unknown target") || + strings.Contains(s, "is not a registered target") { + t.Skipf("llc does not support triple %q: %s", triple, strings.TrimSpace(s)) + } + t.Fatalf("llc failed for %s: %v\n%s", path, err, s) + } + compiled++ + } + if compiled == 0 { + t.Fatalf("expected at least one successful llc compilation") + } +} + +func stdlibBytealgARMSigs(goroot string) map[string]map[string]FuncSig { + sfiles := map[string]map[string]FuncSig{ + filepath.Join(goroot, "src", "internal", "bytealg", "compare_arm.s"): { + "internal/bytealg.Compare": sigWithClassicFrame32("internal/bytealg.Compare", []LLVMType{"{ ptr, i32, i32 }", "{ ptr, i32, i32 }"}, I32), + "runtime.cmpstring": sigWithClassicFrame32("runtime.cmpstring", []LLVMType{"{ ptr, i32 }", "{ ptr, i32 }"}, I32), + "internal/bytealg.cmpbody": withArgRegs(sigWithClassicFrame32("internal/bytealg.cmpbody", []LLVMType{Ptr, I32, Ptr, I32, Ptr}, Void), []Reg{"R2", "R0", "R3", "R1", "R7"}), + }, + filepath.Join(goroot, "src", "internal", "bytealg", "equal_arm.s"): { + "runtime.memequal": sigWithClassicFrame32("runtime.memequal", []LLVMType{Ptr, Ptr, I32}, I1), + "runtime.memequal_varlen": sigWithClassicFrame32("runtime.memequal_varlen", []LLVMType{Ptr, Ptr}, I1), + "internal/bytealg.memeqbody": withArgRegs(sigWithClassicFrame32("internal/bytealg.memeqbody", []LLVMType{Ptr, Ptr, I32, Ptr}, Void), []Reg{"R0", "R2", "R1", "R7"}), + }, + filepath.Join(goroot, "src", "internal", "bytealg", "count_arm.s"): { + "internal/bytealg.Count": sigWithClassicFrame32("internal/bytealg.Count", []LLVMType{"{ ptr, i32, i32 }", I8}, I32), + "internal/bytealg.CountString": sigWithClassicFrame32("internal/bytealg.CountString", []LLVMType{"{ ptr, i32 }", I8}, I32), + "internal/bytealg.countbytebody": withArgRegs(sigWithClassicFrame32("internal/bytealg.countbytebody", []LLVMType{Ptr, I32, I8, Ptr}, Void), []Reg{"R0", "R1", "R2", "R7"}), + }, + filepath.Join(goroot, "src", "internal", "bytealg", "indexbyte_arm.s"): { + "internal/bytealg.IndexByte": sigWithClassicFrame32("internal/bytealg.IndexByte", []LLVMType{"{ ptr, i32, i32 }", I8}, I32), + "internal/bytealg.IndexByteString": sigWithClassicFrame32("internal/bytealg.IndexByteString", []LLVMType{"{ ptr, i32 }", I8}, I32), + "internal/bytealg.indexbytebody": withArgRegs(sigWithClassicFrame32("internal/bytealg.indexbytebody", []LLVMType{Ptr, I32, I8, Ptr}, Void), []Reg{"R0", "R1", "R2", "R5"}), + }, + } + for path := range sfiles { + if _, err := os.Stat(path); err != nil { + delete(sfiles, path) + } + } + return sfiles +} + +func sigWithClassicFrame32(name string, args []LLVMType, ret LLVMType) FuncSig { + sig := FuncSig{Name: name, Args: args, Ret: ret} + var off int64 + for i, arg := range args { + off = alignOff32(off, typeAlign32(arg)) + for _, slot := range frameSlotsForType32(arg, off, i, false) { + sig.Frame.Params = append(sig.Frame.Params, slot) + } + off += typeSize32(arg) + } + if ret != Void { + off = alignOff32(off, typeAlign32(ret)) + for _, slot := range frameSlotsForType32(ret, off, 0, true) { + sig.Frame.Results = append(sig.Frame.Results, slot) + } + } + return sig +} + +func frameSlotsForType32(ty LLVMType, off int64, index int, result bool) []FrameSlot { + fieldIndex := func(v int) int { + if result { + return -1 + } + return v + } + switch ty { + case "{ ptr, i32 }": + return []FrameSlot{ + {Offset: off + 0, Type: Ptr, Index: index, Field: fieldIndex(0)}, + {Offset: off + 4, Type: I32, Index: index, Field: fieldIndex(1)}, + } + case "{ ptr, i32, i32 }": + return []FrameSlot{ + {Offset: off + 0, Type: Ptr, Index: index, Field: fieldIndex(0)}, + {Offset: off + 4, Type: I32, Index: index, Field: fieldIndex(1)}, + {Offset: off + 8, Type: I32, Index: index, Field: fieldIndex(2)}, + } + default: + return []FrameSlot{{Offset: off, Type: ty, Index: index, Field: -1}} + } +} + +func typeSize32(ty LLVMType) int64 { + switch ty { + case I1, I8: + return 1 + case I16: + return 2 + case I32, Ptr: + return 4 + case I64: + return 8 + case "{ ptr, i32 }": + return 8 + case "{ ptr, i32, i32 }": + return 12 + default: + return 4 + } +} + +func typeAlign32(ty LLVMType) int64 { + switch ty { + case I64: + return 4 + case "{ ptr, i32 }", "{ ptr, i32, i32 }", I32, Ptr: + return 4 + case I16: + return 2 + default: + return 1 + } +} + +func alignOff32(off, align int64) int64 { + if align <= 1 { + return off + } + m := off % align + if m == 0 { + return off + } + return off + (align - m) +} diff --git a/translate.go b/translate.go index c19c5bf..ad3ceef 100644 --- a/translate.go +++ b/translate.go @@ -142,9 +142,19 @@ func translateIRText(file *File, opt Options) (string, error) { if sig.Ret == "" { return "", fmt.Errorf("missing return type for %q", name) } + if err := validateResolvedImmediates(file.Arch, *fn); err != nil { + return "", fmt.Errorf("%s: %v", name, err) + } if sig.Attrs == "" { sig.Attrs = attrRegistry.ref(inferFuncTargetFeatures(file.Arch, *fn)) } + if file.Arch == ArchARM && funcNeedsARMCFG(*fn) { + if err := translateFuncARM(&b, *fn, sig, resolve, opt.Sigs, opt.AnnotateSource); err != nil { + return "", fmt.Errorf("%s: %v", name, err) + } + b.WriteString("\n") + continue + } if file.Arch == ArchARM64 && funcNeedsARM64CFG(*fn) { if err := translateFuncARM64(&b, *fn, sig, resolve, opt.Sigs, opt.AnnotateSource); err != nil { return "", fmt.Errorf("%s: %v", name, err) @@ -168,6 +178,20 @@ func translateIRText(file *File, opt Options) (string, error) { return b.String(), nil } +func validateResolvedImmediates(arch Arch, fn Func) error { + if arch != ArchARM { + return nil + } + for _, ins := range fn.Instrs { + for _, arg := range ins.Args { + if arg.Kind == OpImm && arg.ImmRaw != "" { + return fmt.Errorf("unresolved symbolic immediate %q", arg.ImmRaw) + } + } + } + return nil +} + func emitExternFuncDecls(b *strings.Builder, file *File, resolve func(string) string, sigs map[string]FuncSig) { defined := map[string]bool{} for i := range file.Funcs { @@ -433,6 +457,16 @@ func translateFuncLinear(b *strings.Builder, arch Arch, fn Func, sig FuncSig, an // This is currently best-effort; the prototype primarily targets stdlib // asm that uses FP slots. switch arch { + case ArchARM: + if len(sig.ArgRegs) > 0 { + for i := 0; i < len(sig.Args) && i < len(sig.ArgRegs); i++ { + reg[sig.ArgRegs[i]] = ssaVal{typ: sig.Args[i], val: fmt.Sprintf("%%arg%d", i)} + } + } else { + for i := 0; i < len(sig.Args) && i < 4; i++ { + reg[Reg(fmt.Sprintf("R%d", i))] = ssaVal{typ: sig.Args[i], val: fmt.Sprintf("%%arg%d", i)} + } + } case ArchARM64: if len(sig.ArgRegs) > 0 { for i := 0; i < len(sig.Args) && i < len(sig.ArgRegs); i++ { @@ -633,9 +667,13 @@ func translateFuncLinear(b *strings.Builder, arch Arch, fn Func, sig FuncSig, an return cur, nil } - valueOf := func(op Operand) (ssaVal, error) { + var valueOf func(op Operand) (ssaVal, error) + valueOf = func(op Operand) (ssaVal, error) { switch op.Kind { case OpImm: + if op.ImmRaw != "" { + return ssaVal{}, fmt.Errorf("unresolved symbolic immediate %q", op.ImmRaw) + } // Default immediates to i64; MOVL will cast to i32 as needed. return ssaVal{typ: I64, val: fmt.Sprintf("%d", op.Imm)}, nil case OpReg: @@ -645,6 +683,46 @@ func translateFuncLinear(b *strings.Builder, arch Arch, fn Func, sig FuncSig, an return ssaVal{typ: I64, val: "0"}, nil } return v, nil + case OpRegShift: + v, err := valueOf(Operand{Kind: OpReg, Reg: op.Reg}) + if err != nil { + return ssaVal{}, err + } + if arch != ArchARM { + return ssaVal{}, fmt.Errorf("shift operand only modeled for arm in linear lowering: %s", op) + } + v, err = emitCast(v, I32) + if err != nil { + return ssaVal{}, err + } + var shiftVal string + if op.ShiftReg != "" { + sv, err := valueOf(Operand{Kind: OpReg, Reg: op.ShiftReg}) + if err != nil { + return ssaVal{}, err + } + sv, err = emitCast(sv, I32) + if err != nil { + return ssaVal{}, err + } + shiftVal = sv.val + } else { + shiftVal = fmt.Sprintf("%d", op.ShiftAmount) + } + name := newTmp() + switch op.ShiftOp { + case ShiftLeft: + fmt.Fprintf(b, " %%%s = shl i32 %s, %s\n", name, v.val, shiftVal) + case ShiftRight: + fmt.Fprintf(b, " %%%s = lshr i32 %s, %s\n", name, v.val, shiftVal) + case ShiftArith: + fmt.Fprintf(b, " %%%s = ashr i32 %s, %s\n", name, v.val, shiftVal) + case ShiftRotate: + fmt.Fprintf(b, " %%%s = call i32 @llvm.fshr.i32(i32 %s, i32 %s, i32 %s)\n", name, v.val, v.val, shiftVal) + default: + return ssaVal{}, fmt.Errorf("unsupported shift op %q", op.ShiftOp) + } + return ssaVal{typ: I32, val: "%" + name}, nil case OpFP: slot, ok := fpParamSlot(op.FPOffset) if ok { @@ -774,6 +852,58 @@ func translateFuncLinear(b *strings.Builder, arch Arch, fn Func, sig FuncSig, an continue } continue + case "MOVW": + if arch != ArchARM { + continue + } + src, dst := ins.Args[0], ins.Args[1] + v, err := valueOf(src) + if err != nil { + return err + } + v, err = emitCast(v, I32) + if err != nil { + return err + } + switch dst.Kind { + case OpReg: + if err := setReg(dst.Reg, v); err != nil { + return err + } + case OpFP: + if err := setResult(dst.FPOffset, v); err != nil { + return err + } + default: + continue + } + continue + case "MOVB", "MOVBU": + if arch != ArchARM { + continue + } + src, dst := ins.Args[0], ins.Args[1] + v, err := valueOf(src) + if err != nil { + return err + } + v, err = emitCast(v, I8) + if err != nil { + return err + } + switch dst.Kind { + case OpReg: + if err := setReg(dst.Reg, v); err != nil { + return err + } + case OpFP: + if err := setResult(dst.FPOffset, v); err != nil { + return err + } + default: + continue + } + continue case OpMOVQ: src, dst := ins.Args[0], ins.Args[1] v, err := valueOf(src) @@ -828,6 +958,70 @@ func translateFuncLinear(b *strings.Builder, arch Arch, fn Func, sig FuncSig, an fmt.Fprintf(b, " %%%s = xor i64 %s, %s\n", name, lhs.val, rhs.val) } reg[dst.Reg] = ssaVal{typ: I64, val: "%" + name} + case "ADD", "SUB", "AND", "ORR", "EOR", "RSB": + if arch != ArchARM { + continue + } + var lhs, rhs ssaVal + var dst Operand + switch len(ins.Args) { + case 2: + dst = ins.Args[1] + if dst.Kind != OpReg { + return fmt.Errorf("%s dst must be register: %s", ins.Op, ins.Raw) + } + var err error + lhs, err = valueOf(dst) + if err != nil { + return err + } + rhs, err = valueOf(ins.Args[0]) + if err != nil { + return err + } + case 3: + dst = ins.Args[2] + if dst.Kind != OpReg { + return fmt.Errorf("%s dst must be register: %s", ins.Op, ins.Raw) + } + var err error + lhs, err = valueOf(ins.Args[1]) + if err != nil { + return err + } + rhs, err = valueOf(ins.Args[0]) + if err != nil { + return err + } + default: + return fmt.Errorf("%s expects 2 or 3 operands: %s", ins.Op, ins.Raw) + } + var err error + lhs, err = emitCast(lhs, I32) + if err != nil { + return err + } + rhs, err = emitCast(rhs, I32) + if err != nil { + return err + } + name := newTmp() + switch strings.ToUpper(string(ins.Op)) { + case "ADD": + fmt.Fprintf(b, " %%%s = add i32 %s, %s\n", name, lhs.val, rhs.val) + case "SUB": + fmt.Fprintf(b, " %%%s = sub i32 %s, %s\n", name, lhs.val, rhs.val) + case "AND": + fmt.Fprintf(b, " %%%s = and i32 %s, %s\n", name, lhs.val, rhs.val) + case "ORR": + fmt.Fprintf(b, " %%%s = or i32 %s, %s\n", name, lhs.val, rhs.val) + case "EOR": + fmt.Fprintf(b, " %%%s = xor i32 %s, %s\n", name, lhs.val, rhs.val) + case "RSB": + fmt.Fprintf(b, " %%%s = sub i32 %s, %s\n", name, rhs.val, lhs.val) + } + reg[dst.Reg] = ssaVal{typ: I32, val: "%" + name} + continue case OpMOVL: src, dst := ins.Args[0], ins.Args[1] @@ -1027,7 +1221,7 @@ func translateFuncLinear(b *strings.Builder, arch Arch, fn Func, sig FuncSig, an } func archReturnReg(arch Arch) Reg { - if arch == ArchARM64 { + if arch == ArchARM || arch == ArchARM64 { return Reg("R0") } return AX diff --git a/translate_module_direct.go b/translate_module_direct.go index bfd2a6d..c8a09a5 100644 --- a/translate_module_direct.go +++ b/translate_module_direct.go @@ -67,6 +67,14 @@ func translateModuleDirect(file *File, opt Options) (llvm.Module, error) { mod.Dispose() return llvm.Module{}, fmt.Errorf("missing return type for %q", name) } + if err := validateResolvedImmediates(file.Arch, *fn); err != nil { + mod.Dispose() + return llvm.Module{}, directUnsupportedf("%s: %v", name, err) + } + if file.Arch == ArchARM && funcNeedsARMCFG(*fn) { + mod.Dispose() + return llvm.Module{}, directUnsupportedf("arm CFG lowering required for %s", name) + } if file.Arch == ArchARM64 && funcNeedsARM64CFG(*fn) { mod.Dispose() return llvm.Module{}, directUnsupportedf("arm64 CFG lowering required for %s", name) @@ -135,6 +143,16 @@ func translateFuncLinearModule(mod llvm.Module, arch Arch, fn Func, sig FuncSig) } switch arch { + case ArchARM: + if len(sig.ArgRegs) > 0 { + for i := 0; i < len(sig.Args) && i < len(sig.ArgRegs); i++ { + setArgReg(sig.ArgRegs[i], i) + } + } else { + for i := 0; i < len(sig.Args) && i < 4; i++ { + setArgReg(Reg(fmt.Sprintf("R%d", i)), i) + } + } case ArchARM64: if len(sig.ArgRegs) > 0 { for i := 0; i < len(sig.Args) && i < len(sig.ArgRegs); i++ { @@ -237,6 +255,9 @@ func translateFuncLinearModule(mod llvm.Module, arch Arch, fn Func, sig FuncSig) valueOf := func(op Operand) (directValue, error) { switch op.Kind { case OpImm: + if op.ImmRaw != "" { + return directValue{}, directUnsupportedf("unresolved symbolic immediate %q", op.ImmRaw) + } llty, _ := llvmTypeFromLLVMType(ctx, I64) return directValue{typ: I64, val: llvm.ConstInt(llty, uint64(op.Imm), true)}, nil case OpReg: diff --git a/translate_prelude.go b/translate_prelude.go index d1cef28..6a4e4d3 100644 --- a/translate_prelude.go +++ b/translate_prelude.go @@ -4,6 +4,8 @@ import "strings" func emitArchPrelude(b *strings.Builder, arch Arch, goarch string) { switch arch { + case ArchARM: + emitARMPrelude(b) case ArchARM64: emitARM64Prelude(b) case ArchAMD64: diff --git a/types.go b/types.go index 4aedf24..f4bfeb8 100644 --- a/types.go +++ b/types.go @@ -14,6 +14,7 @@ type Arch string const ( ArchAMD64 Arch = "amd64" + ArchARM Arch = "arm" ArchARM64 Arch = "arm64" ) @@ -149,6 +150,15 @@ const ( OpRegList ) +type ShiftOp string + +const ( + ShiftLeft ShiftOp = "<<" + ShiftRight ShiftOp = ">>" + ShiftArith ShiftOp = "->" + ShiftRotate ShiftOp = "@>" +) + // Operand models a minimal subset of Plan 9 asm operands. // // Supported: @@ -158,11 +168,13 @@ const ( type Operand struct { Kind OperandKind - Imm int64 // OpImm - Reg Reg // OpReg + Imm int64 // OpImm + ImmRaw string // OpImm unresolved symbolic placeholder, including leading '$' + Reg Reg // OpReg // OpRegShift + ShiftOp ShiftOp ShiftAmount int64 - ShiftRight bool + ShiftReg Reg FPName string // OpFP (e.g. "a", "ret") FPOffset int64 // OpFP @@ -192,14 +204,18 @@ type MemRef struct { func (o Operand) String() string { switch o.Kind { case OpImm: + if o.ImmRaw != "" { + return o.ImmRaw + } return fmt.Sprintf("$%d", o.Imm) case OpReg: return string(o.Reg) case OpRegShift: - if o.ShiftRight { - return fmt.Sprintf("%s>>%d", o.Reg, o.ShiftAmount) + suffix := fmt.Sprintf("%d", o.ShiftAmount) + if o.ShiftReg != "" { + suffix = string(o.ShiftReg) } - return fmt.Sprintf("%s<<%d", o.Reg, o.ShiftAmount) + return fmt.Sprintf("%s%s%s", o.Reg, o.ShiftOp, suffix) case OpFP: return fmt.Sprintf("%s+%d(FP)", o.FPName, o.FPOffset) case OpFPAddr: @@ -260,6 +276,13 @@ func parseImm(s string) (int64, bool) { } u, ok := parseImmExpr(v) if !ok { + // Be permissive with symbolic immediates such as: + // $(16 + callbackArgs__size) + // Parser/scan should accept them, but lowering must reject them + // explicitly via Operand.ImmRaw instead of silently materializing 0. + if isSymbolicImmPlaceholder(s) { + return 0, true + } return 0, false } return int64(u), true @@ -267,6 +290,15 @@ func parseImm(s string) (int64, bool) { return int64(u), true } +func isSymbolicImmPlaceholder(s string) bool { + expr := strings.TrimSpace(strings.TrimPrefix(strings.TrimSpace(s), "$")) + if !strings.HasPrefix(expr, "(") || !strings.HasSuffix(expr, ")") { + return false + } + inner := strings.TrimSpace(strings.TrimSuffix(strings.TrimPrefix(expr, "("), ")")) + return strings.ContainsAny(inner, "+-*/%<>&|^ \t") +} + func parseImmExpr(v string) (uint64, bool) { // Plan9 asm frequently uses C-style unary "~" for bitwise-not in immediates. // Go expressions use "^", so normalize before parsing. @@ -475,13 +507,17 @@ func parseOperand(s string) (Operand, error) { return Operand{}, fmt.Errorf("empty operand") } if imm, ok := parseImm(s); ok { - return Operand{Kind: OpImm, Imm: imm}, nil + op := Operand{Kind: OpImm, Imm: imm} + if isSymbolicImmPlaceholder(s) { + op.ImmRaw = s + } + return op, nil } if name, off, ok := parseFPAddr(s); ok { return Operand{Kind: OpFPAddr, FPName: name, FPOffset: off}, nil } - if base, amt, right, ok := parseRegShift(s); ok { - return Operand{Kind: OpRegShift, Reg: base, ShiftAmount: amt, ShiftRight: right}, nil + if base, sop, amt, shiftReg, ok := parseRegShift(s); ok { + return Operand{Kind: OpRegShift, Reg: base, ShiftOp: sop, ShiftAmount: amt, ShiftReg: shiftReg}, nil } if r, ok := parseReg(s); ok { return Operand{Kind: OpReg, Reg: r}, nil @@ -501,11 +537,11 @@ func parseOperand(s string) (Operand, error) { regs := make([]Reg, 0, len(parts)) for _, p := range parts { p = strings.TrimSpace(p) - r, ok := parseReg(p) + rs, ok := expandRegRange(p) if !ok { return Operand{}, fmt.Errorf("invalid reg in reg list %q: %q", s, p) } - regs = append(regs, r) + regs = append(regs, rs...) } return Operand{Kind: OpRegList, RegList: regs}, nil } @@ -519,11 +555,11 @@ func parseOperand(s string) (Operand, error) { regs := make([]Reg, 0, len(parts)) for _, p := range parts { p = strings.TrimSpace(p) - r, ok := parseReg(p) + rs, ok := expandRegRange(p) if !ok { return Operand{}, fmt.Errorf("invalid reg in reg list %q: %q", s, p) } - regs = append(regs, r) + regs = append(regs, rs...) } return Operand{Kind: OpRegList, RegList: regs}, nil } @@ -542,38 +578,102 @@ func parseOperand(s string) (Operand, error) { return Operand{}, fmt.Errorf("unsupported operand: %q", s) } -func parseRegShift(s string) (base Reg, amt int64, right bool, ok bool) { +func parseRegShift(s string) (base Reg, sop ShiftOp, amt int64, shiftReg Reg, ok bool) { s = strings.TrimSpace(s) if s == "" { - return "", 0, false, false + return "", "", 0, "", false } - op := "" - i := strings.Index(s, "<<") - if i >= 0 { - op = "<<" - } else { - i = strings.Index(s, ">>") - if i >= 0 { - op = ">>" + for _, candidate := range []ShiftOp{ShiftRotate, ShiftArith, ShiftLeft, ShiftRight} { + i := strings.Index(s, string(candidate)) + if i < 0 { + continue + } + l := strings.TrimSpace(s[:i]) + r := strings.TrimSpace(s[i+len(candidate):]) + if l == "" || r == "" { + return "", "", 0, "", false + } + br, ok := parseReg(l) + if !ok { + return "", "", 0, "", false } + if rr, ok := parseReg(r); ok { + return br, candidate, 0, rr, true + } + if n, err := strconv.ParseInt(r, 0, 64); err == nil { + return br, candidate, n, "", true + } + if u, ok := parseImmExpr(r); ok { + return br, candidate, int64(u), "", true + } + return "", "", 0, "", false } - if op == "" { - return "", 0, false, false + return "", "", 0, "", false +} + +func expandRegRange(part string) ([]Reg, bool) { + part = strings.TrimSpace(part) + dash := strings.IndexByte(part, '-') + if dash < 0 { + r, ok := parseReg(part) + if !ok { + return nil, false + } + return []Reg{r}, true } - l := strings.TrimSpace(s[:i]) - r := strings.TrimSpace(s[i+2:]) - if l == "" || r == "" { - return "", 0, false, false + left := strings.TrimSpace(part[:dash]) + right := strings.TrimSpace(part[dash+1:]) + lr, ok := parseReg(left) + if !ok { + return nil, false + } + rr, ok := parseReg(right) + if !ok { + return nil, false } - br, ok := parseReg(l) + lp, li, ok := regRangeParts(lr) if !ok { - return "", 0, false, false + return nil, false + } + rp, ri, ok := regRangeParts(rr) + if !ok || lp != rp { + return nil, false + } + step := 1 + if li > ri { + step = -1 + } + out := make([]Reg, 0, absInt(li-ri)+1) + for i := li; ; i += step { + out = append(out, Reg(fmt.Sprintf("%s%d", lp, i))) + if i == ri { + break + } } - n, err := strconv.ParseInt(r, 0, 64) + return out, true +} + +func regRangeParts(r Reg) (prefix string, idx int, ok bool) { + s := string(r) + i := len(s) + for i > 0 && s[i-1] >= '0' && s[i-1] <= '9' { + i-- + } + if i == len(s) || i == 0 { + return "", 0, false + } + n, err := strconv.Atoi(s[i:]) if err != nil { - return "", 0, false, false + return "", 0, false + } + return s[:i], n, true +} + +func absInt(v int) int { + if v < 0 { + return -v } - return br, n, op == ">>", true + return v } type Op string