compiler: mark string parameters as readonly

Strings are readonly, but the compiler doesn't always know this. Marking
them as readonly in the frontend allows the compiler to optimize based
on this knowledge.

This provides some small code size benefits. I didn't measure running
speed.
This commit is contained in:
Ayke van Laethem
2025-10-10 14:17:34 +02:00
committed by Ron Evans
parent 3be7100e89
commit 7460734522
5 changed files with 25 additions and 12 deletions
+8
View File
@@ -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
+5
View File
@@ -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&paramIsReadonly != 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
+1 -1
View File
@@ -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
+3 -3
View File
@@ -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
+8 -8
View File
@@ -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