diff --git a/_demo/catomic/atomic.go b/_demo/catomic/atomic.go new file mode 100644 index 00000000..9125f42d --- /dev/null +++ b/_demo/catomic/atomic.go @@ -0,0 +1,20 @@ +package main + +import ( + "github.com/goplus/llgo/c/sync" +) + +func main() { + var v int64 = 100 + ret := sync.FetchAndAdd(&v, 1) + println("ret:", ret, "v:", v) + + ret = sync.CompareAndXchg(&v, 100, 102) + println("ret:", ret, "vs 100, v:", v) + + ret = sync.CompareAndXchg(&v, 101, 102) + println("ret:", ret, "vs 101, v:", v) + + ret = sync.FetchAndSub(&v, 1) + println("ret:", ret, "v:", v) +} diff --git a/c/sync/sync.go b/c/sync/sync.go new file mode 100644 index 00000000..e79859bd --- /dev/null +++ b/c/sync/sync.go @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2024 The GoPlus Authors (goplus.org). All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package sync + +import ( + _ "unsafe" +) + +const ( + LLGoPackage = "decl" +) + +type integer interface { + ~int | ~uint | ~uintptr | ~int32 | ~uint32 | ~int64 | ~uint64 +} + +// llgo:link FetchAndXchg llgo.atomicXchg +func FetchAndXchg[T integer](ptr *T, v T) T { return v } + +// llgo:link FetchAndAdd llgo.atomicAdd +func FetchAndAdd[T integer](ptr *T, v T) T { return v } + +// llgo:link FetchAndSub llgo.atomicSub +func FetchAndSub[T integer](ptr *T, v T) T { return v } + +// llgo:link FetchAndAnd llgo.atomicAnd +func FetchAndAnd[T integer](ptr *T, v T) T { return v } + +// llgo:link FetchAndNand llgo.atomicNand +func FetchAndNand[T integer](ptr *T, v T) T { return v } + +// llgo:link FetchAndOr llgo.atomicOr +func FetchAndOr[T integer](ptr *T, v T) T { return v } + +// llgo:link FetchAndXor llgo.atomicXor +func FetchAndXor[T integer](ptr *T, v T) T { return v } + +// llgo:link FetchAndMax llgo.atomicMax +func FetchAndMax[T integer](ptr *T, v T) T { return v } + +// llgo:link FetchAndMin llgo.atomicMin +func FetchAndMin[T integer](ptr *T, v T) T { return v } + +// llgo:link FetchAndUMax llgo.atomicUMax +func FetchAndUMax[T integer](ptr *T, v T) T { return v } + +// llgo:link FetchAndUMin llgo.atomicUMin +func FetchAndUMin[T integer](ptr *T, v T) T { return v } + +// llgo:link CompareAndXchg llgo.atomicCmpXchg +func CompareAndXchg[T integer](ptr *T, old, new T) T { return old } diff --git a/cl/_testlibc/atomic/in.go b/cl/_testlibc/atomic/in.go new file mode 100644 index 00000000..8c05f87a --- /dev/null +++ b/cl/_testlibc/atomic/in.go @@ -0,0 +1,21 @@ +package main + +import ( + "github.com/goplus/llgo/c" + "github.com/goplus/llgo/c/sync" +) + +func main() { + var v int64 = 100 + sync.FetchAndAdd(&v, 1) + c.Printf(c.Str("%ld\n"), v) + + sync.CompareAndXchg(&v, 100, 102) + c.Printf(c.Str("%ld\n"), v) + + sync.CompareAndXchg(&v, 101, 102) + c.Printf(c.Str("%ld\n"), v) + + sync.FetchAndSub(&v, 1) + c.Printf(c.Str("%ld\n"), v) +} diff --git a/cl/_testlibc/atomic/out.ll b/cl/_testlibc/atomic/out.ll new file mode 100644 index 00000000..d0680e88 --- /dev/null +++ b/cl/_testlibc/atomic/out.ll @@ -0,0 +1,52 @@ +; ModuleID = 'main' +source_filename = "main" + +@"main.init$guard" = global i1 false, align 1 +@__llgo_argc = global i32 0, align 4 +@__llgo_argv = global ptr null, align 8 +@0 = private unnamed_addr constant [5 x i8] c"%ld\0A\00", align 1 +@1 = private unnamed_addr constant [5 x i8] c"%ld\0A\00", align 1 +@2 = private unnamed_addr constant [5 x i8] c"%ld\0A\00", align 1 +@3 = private unnamed_addr constant [5 x i8] c"%ld\0A\00", align 1 + +define void @main.init() { +_llgo_0: + %0 = load i1, ptr @"main.init$guard", align 1 + br i1 %0, label %_llgo_2, label %_llgo_1 + +_llgo_1: ; preds = %_llgo_0 + store i1 true, ptr @"main.init$guard", align 1 + br label %_llgo_2 + +_llgo_2: ; preds = %_llgo_1, %_llgo_0 + ret void +} + +define i32 @main(i32 %0, ptr %1) { +_llgo_0: + store i32 %0, ptr @__llgo_argc, align 4 + store ptr %1, ptr @__llgo_argv, align 8 + call void @"github.com/goplus/llgo/internal/runtime.init"() + call void @main.init() + %2 = call ptr @"github.com/goplus/llgo/internal/runtime.AllocZ"(i64 8) + store i64 100, ptr %2, align 4 + %3 = atomicrmw add ptr %2, i64 1 seq_cst, align 8 + %4 = load i64, ptr %2, align 4 + %5 = call i32 (ptr, ...) @printf(ptr @0, i64 %4) + %6 = cmpxchg ptr %2, i64 100, i64 102 seq_cst seq_cst, align 8 + %7 = load i64, ptr %2, align 4 + %8 = call i32 (ptr, ...) @printf(ptr @1, i64 %7) + %9 = cmpxchg ptr %2, i64 101, i64 102 seq_cst seq_cst, align 8 + %10 = load i64, ptr %2, align 4 + %11 = call i32 (ptr, ...) @printf(ptr @2, i64 %10) + %12 = atomicrmw sub ptr %2, i64 1 seq_cst, align 8 + %13 = load i64, ptr %2, align 4 + %14 = call i32 (ptr, ...) @printf(ptr @3, i64 %13) + ret i32 0 +} + +declare void @"github.com/goplus/llgo/internal/runtime.init"() + +declare ptr @"github.com/goplus/llgo/internal/runtime.AllocZ"(i64) + +declare i32 @printf(ptr, ...) diff --git a/cl/_testlibc/setjmp/in.go b/cl/_testlibc/setjmp/in.go index 90367ce8..7e8a6bf4 100644 --- a/cl/_testlibc/setjmp/in.go +++ b/cl/_testlibc/setjmp/in.go @@ -8,8 +8,8 @@ func main() { jb := c.AllocaSigjmpBuf() switch ret := c.Sigsetjmp(jb, 0); ret { case 0: - cstr := c.Str("?Hello, setjmp!\n") - c.Fprintf(c.Stderr, c.Advance(cstr, 1)) + cstr := c.Str("??Hello, setjmp!\n") + c.Fprintf(c.Stderr, c.Str("%s"), c.Advance(c.Pointer(c.Advance(cstr, 1)), 1)) c.Siglongjmp(jb, 1) default: println("exception:", ret) diff --git a/cl/_testlibgo/_atomic/in.go b/cl/_testlibgo/_atomic/in.go new file mode 100644 index 00000000..be7974ac --- /dev/null +++ b/cl/_testlibgo/_atomic/in.go @@ -0,0 +1,11 @@ +package main + +import ( + "sync/atomic" +) + +func main() { + var v int64 = 100 + atomic.AddInt64(&v, 1) + println(v) +} diff --git a/cl/compile.go b/cl/compile.go index 02e7c9ec..b3d83aa8 100644 --- a/cl/compile.go +++ b/cl/compile.go @@ -304,6 +304,34 @@ func (p *context) compileFuncDecl(pkg llssa.Package, f *ssa.Function) (llssa.Fun return fn, nil, goFunc } +var llgoInstrs = map[string]int{ + "cstr": llgoCstr, + "advance": llgoAdvance, + "index": llgoIndex, + "alloca": llgoAlloca, + "allocaCStr": llgoAllocaCStr, + "stringData": llgoStringData, + "pyList": llgoPyList, + "sigjmpbuf": llgoSigjmpbuf, + "sigsetjmp": llgoSigsetjmp, + "siglongjmp": llgoSiglongjmp, + "deferData": llgoDeferData, + "unreachable": llgoUnreachable, + + "atomicCmpXchg": llgoAtomicCmpXchg, + "atomicXchg": int(llgoAtomicXchg), + "atomicAdd": int(llgoAtomicAdd), + "atomicSub": int(llgoAtomicSub), + "atomicAnd": int(llgoAtomicAnd), + "atomicNand": int(llgoAtomicNand), + "atomicOr": int(llgoAtomicOr), + "atomicXor": int(llgoAtomicXor), + "atomicMax": int(llgoAtomicMax), + "atomicMin": int(llgoAtomicMin), + "atomicUMax": int(llgoAtomicUMax), + "atomicUMin": int(llgoAtomicUMin), +} + // funcOf returns a function by name and set ftype = goFunc, cFunc, etc. // or returns nil and set ftype = llgoCstr, llgoAlloca, llgoUnreachable, etc. func (p *context) funcOf(fn *ssa.Function) (aFn llssa.Function, pyFn llssa.PyObjRef, ftype int) { @@ -320,32 +348,7 @@ func (p *context) funcOf(fn *ssa.Function) (aFn llssa.Function, pyFn llssa.PyObj } ftype = ignoredFunc case llgoInstr: - switch name { - case "cstr": - ftype = llgoCstr - case "advance": - ftype = llgoAdvance - case "index": - ftype = llgoIndex - case "alloca": - ftype = llgoAlloca - case "allocaCStr": - ftype = llgoAllocaCStr - case "stringData": - ftype = llgoStringData - case "pyList": - ftype = llgoPyList - case "sigjmpbuf": - ftype = llgoSigjmpbuf - case "sigsetjmp": - ftype = llgoSigsetjmp - case "siglongjmp": - ftype = llgoSiglongjmp - case "deferData": - ftype = llgoDeferData - case "unreachable": - ftype = llgoUnreachable - default: + if ftype = llgoInstrs[name]; ftype == 0 { panic("unknown llgo instruction: " + name) } default: @@ -552,6 +555,25 @@ func (p *context) siglongjmp(b llssa.Builder, args []ssa.Value) { panic("siglongjmp(jb c.SigjmpBuf, retval c.Int): invalid arguments") } +func (p *context) atomic(b llssa.Builder, op llssa.AtomicOp, args []ssa.Value) (ret llssa.Expr) { + if len(args) == 2 { + addr := p.compileValue(b, args[0]) + val := p.compileValue(b, args[1]) + return b.Atomic(op, addr, val) + } + panic("atomicOp(addr *T, val T) T: invalid arguments") +} + +func (p *context) atomicCmpXchg(b llssa.Builder, args []ssa.Value) llssa.Expr { + if len(args) == 3 { + addr := p.compileValue(b, args[0]) + old := p.compileValue(b, args[1]) + new := p.compileValue(b, args[2]) + return b.AtomicCmpXchg(addr, old, new) + } + panic("atomicCmpXchg(addr *T, old, new T) T: invalid arguments") +} + func isPhi(i ssa.Instruction) bool { _, ok := i.(*ssa.Phi) return ok @@ -655,6 +677,8 @@ func (p *context) call(b llssa.Builder, act llssa.DoAction, call *ssa.CallCommon ret = p.allocaCStr(b, args) case llgoStringData: ret = p.stringData(b, args) + case llgoAtomicCmpXchg: + ret = p.atomicCmpXchg(b, args) case llgoSigsetjmp: ret = p.sigsetjmp(b, args) case llgoSiglongjmp: @@ -666,7 +690,11 @@ func (p *context) call(b llssa.Builder, act llssa.DoAction, call *ssa.CallCommon case llgoUnreachable: // func unreachable() b.Unreachable() default: - log.Panicln("unknown ftype:", ftype) + if ftype >= llgoAtomicOpBase && ftype <= llgoAtomicOpLast { + ret = p.atomic(b, llssa.AtomicOp(ftype-llgoAtomicOpBase), args) + } else { + log.Panicln("unknown ftype:", ftype) + } } default: fn := p.compileValue(b, cv) diff --git a/cl/import.go b/cl/import.go index 171ea4d9..8f2a6f98 100644 --- a/cl/import.go +++ b/cl/import.go @@ -344,10 +344,27 @@ const ( llgoIndex = llgoInstrBase + 5 llgoStringData = llgoInstrBase + 6 llgoPyList = llgoInstrBase + 7 - llgoSigjmpbuf = llgoInstrBase + 10 - llgoSigsetjmp = llgoInstrBase + 11 - llgoSiglongjmp = llgoInstrBase + 12 - llgoDeferData = llgoInstrBase + 13 + llgoSigjmpbuf = llgoInstrBase + 0xa + llgoSigsetjmp = llgoInstrBase + 0xb + llgoSiglongjmp = llgoInstrBase + 0xc + llgoDeferData = llgoInstrBase + 0xd + + llgoAtomicCmpXchg = llgoInstrBase + 0xf + llgoAtomicOpBase = llgoInstrBase + 0x10 + + llgoAtomicXchg = llgoAtomicOpBase + llssa.OpXchg + llgoAtomicAdd = llgoAtomicOpBase + llssa.OpAdd + llgoAtomicSub = llgoAtomicOpBase + llssa.OpSub + llgoAtomicAnd = llgoAtomicOpBase + llssa.OpAnd + llgoAtomicNand = llgoAtomicOpBase + llssa.OpNand + llgoAtomicOr = llgoAtomicOpBase + llssa.OpOr + llgoAtomicXor = llgoAtomicOpBase + llssa.OpXor + llgoAtomicMax = llgoAtomicOpBase + llssa.OpMax + llgoAtomicMin = llgoAtomicOpBase + llssa.OpMin + llgoAtomicUMax = llgoAtomicOpBase + llssa.OpUMax + llgoAtomicUMin = llgoAtomicOpBase + llssa.OpUMin + + llgoAtomicOpLast = llgoAtomicOpBase + int(llssa.OpUMin) ) func (p *context) funcName(fn *ssa.Function, ignore bool) (*types.Package, string, int) { diff --git a/ssa/memory.go b/ssa/memory.go index d63df457..1baaf303 100644 --- a/ssa/memory.go +++ b/ssa/memory.go @@ -252,3 +252,47 @@ func (b Builder) ArrayAlloc(telem Type, n Expr) (ret Expr) { } // ----------------------------------------------------------------------------- + +// AtomicOp is an atomic operation. +type AtomicOp = llvm.AtomicRMWBinOp + +const ( + OpXchg = llvm.AtomicRMWBinOpXchg + OpAdd = llvm.AtomicRMWBinOpAdd + OpSub = llvm.AtomicRMWBinOpSub + OpAnd = llvm.AtomicRMWBinOpAnd + OpNand = llvm.AtomicRMWBinOpNand + OpOr = llvm.AtomicRMWBinOpOr + OpXor = llvm.AtomicRMWBinOpXor + OpMax = llvm.AtomicRMWBinOpMax + OpMin = llvm.AtomicRMWBinOpMin + OpUMax = llvm.AtomicRMWBinOpUMax + OpUMin = llvm.AtomicRMWBinOpUMin +) + +// Atomic performs an atomic operation on the memory location pointed to by ptr. +func (b Builder) Atomic(op AtomicOp, ptr, val Expr) Expr { + if debugInstr { + log.Printf("Atomic %v, %v, %v\n", op, ptr.impl, val.impl) + } + t := b.Prog.Elem(ptr.Type) + val = b.ChangeType(t, val) + ret := b.impl.CreateAtomicRMW(op, ptr.impl, val.impl, llvm.AtomicOrderingSequentiallyConsistent, false) + return Expr{ret, t} +} + +// AtomicCmpXchg performs an atomic compare-and-swap operation on the memory location pointed to by ptr. +func (b Builder) AtomicCmpXchg(ptr, old, new Expr) Expr { + if debugInstr { + log.Printf("AtomicCmpXchg %v, %v, %v\n", ptr.impl, old.impl, new.impl) + } + t := b.Prog.Elem(ptr.Type) + old = b.ChangeType(t, old) + new = b.ChangeType(t, new) + ret := b.impl.CreateAtomicCmpXchg( + ptr.impl, old.impl, new.impl, + llvm.AtomicOrderingSequentiallyConsistent, llvm.AtomicOrderingSequentiallyConsistent, false) + return Expr{ret, t} +} + +// -----------------------------------------------------------------------------