diff --git a/compiler/calls.go b/compiler/calls.go index a44ac38a87..08952c4db6 100644 --- a/compiler/calls.go +++ b/compiler/calls.go @@ -32,6 +32,9 @@ const ( // Whether this is a full or partial Go parameter (int, slice, etc). // The extra context parameter is not a Go parameter. paramIsGoParam = 1 << iota + + // Whether this is a readonly parameter (for example, a string pointer). + paramIsReadonly ) // createRuntimeCallCommon creates a runtime call. Use createRuntimeCall or @@ -167,6 +170,7 @@ func (c *compilerContext) flattenAggregateType(t llvm.Type, name string, goType continue } suffix := strconv.Itoa(i) + isString := false if goType != nil { // Try to come up with a good suffix for this struct field, // depending on which Go type it's based on. @@ -183,12 +187,16 @@ func (c *compilerContext) flattenAggregateType(t llvm.Type, name string, goType suffix = []string{"r", "i"}[i] case types.String: suffix = []string{"data", "len"}[i] + isString = true } case *types.Signature: suffix = []string{"context", "funcptr"}[i] } } subInfos := c.flattenAggregateType(subfield, name+"."+suffix, extractSubfield(goType, i)) + if isString { + subInfos[0].flags |= paramIsReadonly + } paramInfos = append(paramInfos, subInfos...) } return paramInfos diff --git a/compiler/symbol.go b/compiler/symbol.go index 749ad8f1b5..47bc0f4844 100644 --- a/compiler/symbol.go +++ b/compiler/symbol.go @@ -142,6 +142,11 @@ func (c *compilerContext) getFunction(fn *ssa.Function) (llvm.Type, llvm.Value) nocapture := c.ctx.CreateEnumAttribute(llvm.AttributeKindID("nocapture"), 0) llvmFn.AddAttributeAtIndex(i+1, nocapture) } + if paramInfo.flags¶mIsReadonly != 0 && paramInfo.llvmType.TypeKind() == llvm.PointerTypeKind { + // Readonly pointer parameters (like strings) benefit from being marked as readonly. + readonly := c.ctx.CreateEnumAttribute(llvm.AttributeKindID("readonly"), 0) + llvmFn.AddAttributeAtIndex(i+1, readonly) + } } // Set a number of function or parameter attributes, depending on the diff --git a/compiler/testdata/go1.20.ll b/compiler/testdata/go1.20.ll index b1c5bd48f2..dff746667d 100644 --- a/compiler/testdata/go1.20.ll +++ b/compiler/testdata/go1.20.ll @@ -50,7 +50,7 @@ unsafe.String.throw: ; preds = %entry declare void @runtime.unsafeSlicePanic(ptr) #1 ; Function Attrs: nounwind -define hidden ptr @main.unsafeStringData(ptr %s.data, i32 %s.len, ptr %context) unnamed_addr #2 { +define hidden ptr @main.unsafeStringData(ptr readonly %s.data, i32 %s.len, ptr %context) unnamed_addr #2 { entry: %stackalloc = alloca i8, align 1 call void @runtime.trackPointer(ptr %s.data, ptr nonnull %stackalloc, ptr undef) #3 diff --git a/compiler/testdata/go1.21.ll b/compiler/testdata/go1.21.ll index 0e8182538c..6af9776bc3 100644 --- a/compiler/testdata/go1.21.ll +++ b/compiler/testdata/go1.21.ll @@ -77,7 +77,7 @@ entry: } ; Function Attrs: nounwind -define hidden %runtime._string @main.minString(ptr %a.data, i32 %a.len, ptr %b.data, i32 %b.len, ptr %context) unnamed_addr #2 { +define hidden %runtime._string @main.minString(ptr readonly %a.data, i32 %a.len, ptr readonly %b.data, i32 %b.len, ptr %context) unnamed_addr #2 { entry: %0 = insertvalue %runtime._string zeroinitializer, ptr %a.data, 0 %1 = insertvalue %runtime._string %0, i32 %a.len, 1 @@ -91,7 +91,7 @@ entry: ret %runtime._string %5 } -declare i1 @runtime.stringLess(ptr, i32, ptr, i32, ptr) #1 +declare i1 @runtime.stringLess(ptr readonly, i32, ptr readonly, i32, ptr) #1 ; Function Attrs: nounwind define hidden i32 @main.maxInt(i32 %a, i32 %b, ptr %context) unnamed_addr #2 { @@ -116,7 +116,7 @@ entry: } ; Function Attrs: nounwind -define hidden %runtime._string @main.maxString(ptr %a.data, i32 %a.len, ptr %b.data, i32 %b.len, ptr %context) unnamed_addr #2 { +define hidden %runtime._string @main.maxString(ptr readonly %a.data, i32 %a.len, ptr readonly %b.data, i32 %b.len, ptr %context) unnamed_addr #2 { entry: %0 = insertvalue %runtime._string zeroinitializer, ptr %a.data, 0 %1 = insertvalue %runtime._string %0, i32 %a.len, 1 diff --git a/compiler/testdata/string.ll b/compiler/testdata/string.ll index ce2bf35066..8c95323ccf 100644 --- a/compiler/testdata/string.ll +++ b/compiler/testdata/string.ll @@ -31,13 +31,13 @@ entry: } ; Function Attrs: nounwind -define hidden i32 @main.stringLen(ptr %s.data, i32 %s.len, ptr %context) unnamed_addr #2 { +define hidden i32 @main.stringLen(ptr readonly %s.data, i32 %s.len, ptr %context) unnamed_addr #2 { entry: ret i32 %s.len } ; Function Attrs: nounwind -define hidden i8 @main.stringIndex(ptr %s.data, i32 %s.len, i32 %index, ptr %context) unnamed_addr #2 { +define hidden i8 @main.stringIndex(ptr readonly %s.data, i32 %s.len, i32 %index, ptr %context) unnamed_addr #2 { entry: %.not = icmp ult i32 %index, %s.len br i1 %.not, label %lookup.next, label %lookup.throw @@ -55,16 +55,16 @@ lookup.throw: ; preds = %entry declare void @runtime.lookupPanic(ptr) #1 ; Function Attrs: nounwind -define hidden i1 @main.stringCompareEqual(ptr %s1.data, i32 %s1.len, ptr %s2.data, i32 %s2.len, ptr %context) unnamed_addr #2 { +define hidden i1 @main.stringCompareEqual(ptr readonly %s1.data, i32 %s1.len, ptr readonly %s2.data, i32 %s2.len, ptr %context) unnamed_addr #2 { entry: %0 = call i1 @runtime.stringEqual(ptr %s1.data, i32 %s1.len, ptr %s2.data, i32 %s2.len, ptr undef) #3 ret i1 %0 } -declare i1 @runtime.stringEqual(ptr, i32, ptr, i32, ptr) #1 +declare i1 @runtime.stringEqual(ptr readonly, i32, ptr readonly, i32, ptr) #1 ; Function Attrs: nounwind -define hidden i1 @main.stringCompareUnequal(ptr %s1.data, i32 %s1.len, ptr %s2.data, i32 %s2.len, ptr %context) unnamed_addr #2 { +define hidden i1 @main.stringCompareUnequal(ptr readonly %s1.data, i32 %s1.len, ptr readonly %s2.data, i32 %s2.len, ptr %context) unnamed_addr #2 { entry: %0 = call i1 @runtime.stringEqual(ptr %s1.data, i32 %s1.len, ptr %s2.data, i32 %s2.len, ptr undef) #3 %1 = xor i1 %0, true @@ -72,16 +72,16 @@ entry: } ; Function Attrs: nounwind -define hidden i1 @main.stringCompareLarger(ptr %s1.data, i32 %s1.len, ptr %s2.data, i32 %s2.len, ptr %context) unnamed_addr #2 { +define hidden i1 @main.stringCompareLarger(ptr readonly %s1.data, i32 %s1.len, ptr readonly %s2.data, i32 %s2.len, ptr %context) unnamed_addr #2 { entry: %0 = call i1 @runtime.stringLess(ptr %s2.data, i32 %s2.len, ptr %s1.data, i32 %s1.len, ptr undef) #3 ret i1 %0 } -declare i1 @runtime.stringLess(ptr, i32, ptr, i32, ptr) #1 +declare i1 @runtime.stringLess(ptr readonly, i32, ptr readonly, i32, ptr) #1 ; Function Attrs: nounwind -define hidden i8 @main.stringLookup(ptr %s.data, i32 %s.len, i8 %x, ptr %context) unnamed_addr #2 { +define hidden i8 @main.stringLookup(ptr readonly %s.data, i32 %s.len, i8 %x, ptr %context) unnamed_addr #2 { entry: %0 = zext i8 %x to i32 %.not = icmp ugt i32 %s.len, %0