Weird behaviour with strings returned from functions in toy-language

Hope you can help me diagnose this error I’m getting with my String type, when returned from a function. Consider this program from my toy-language:

def test_fn_1() -> String {
    return "test_fn_1"
}

def test_fn_2() -> String {
    return "test_fn_2"
}

var str1 = test_fn_1()
var str2 = test_fn_2()

print(str1)
print(str2)

I would expect it to output:

test_fn_1
test_fn_2

But instead it outputs (including the blank line - my print function takes a String*, prints it and appends a newline)

test_fn_2

My builtins module defines the string type, string constructor and the print function:

; ModuleID = 'builtins'
source_filename = "builtins"

%String = type { i8*, i32 }

@newline = internal constant [2 x i8] c"\0A\00"

define void @String_constructor(%String* %this) {
String_constructor_body:
  %0 = getelementptr %String, %String* %this, i32 0, i32 1
  store i32 0, i32* %0, align 4
  %1 = getelementptr %String, %String* %this, i32 0, i32 0
  store i8* null, i8** %1, align 8
  ret void
}

declare i32 @printf(i8*, ...)

define void @print(%String* %0) {
print_body:
  %1 = getelementptr %String, %String* %0, i32 0, i32 0
  %2 = load i8*, i8** %1, align 8
  %3 = call i32 (i8*, ...) @printf(i8* %2)
  %4 = call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([2 x i8], [2 x i8]* @newline, i32 0, i32 0))
  ret void
}

The above program is translated into this IR:

; ModuleID = '../testFile'
source_filename = "../testFile"

%String = type { i8*, i32 }
%File = type { i32* }

@0 = private unnamed_addr constant [10 x i8] c"test_fn_1\00", align 1
@1 = private unnamed_addr constant [10 x i8] c"test_fn_2\00", align 1

define i32 @main() {
entrypoint:
  %0 = call %String* @test_fn_1()
  %1 = alloca %String*, align 8
  store %String* %0, %String** %1, align 8
  %2 = call %String* @test_fn_2()
  %3 = alloca %String*, align 8
  store %String* %2, %String** %3, align 8
  %4 = load %String*, %String** %1, align 8
  call void @print(%String* %4)
  %5 = load %String*, %String** %3, align 8
  call void @print(%String* %5)
  ret i32 0
}

declare void @print(%String*)

declare void @String_constructor(%String*)

define %String* @test_fn_1() {
test_fn_1_body:
  %0 = alloca %String, align 8
  call void @String_constructor(%String* %0)
  %1 = getelementptr %String, %String* %0, i32 0, i32 0
  store i8* getelementptr inbounds ([10 x i8], [10 x i8]* @0, i32 0, i32 0), i8** %1, align 8
  %2 = getelementptr %String, %String* %0, i32 0, i32 1
  store i32 9, i32* %2, align 4
  ret %String* %0
}

define %String* @test_fn_2() {
test_fn_2_body:
  %0 = alloca %String, align 8
  call void @String_constructor(%String* %0)
  %1 = getelementptr %String, %String* %0, i32 0, i32 0
  store i8* getelementptr inbounds ([10 x i8], [10 x i8]* @1, i32 0, i32 0), i8** %1, align 8
  %2 = getelementptr %String, %String* %0, i32 0, i32 1
  store i32 9, i32* %2, align 4
  ret %String* %0
}

It works fine if the functions return integers instead of strings, and it also works fine with strings not returned from function calls:

var str1 = "test_fn_1()"
var str2 = "test_fn_2()"

print(str1)
print(str2)
// prints 
// test_fn_1()
// test_fn_2()

Any ideas what could be wrong? Thank you!

The alloca instruction generally creates local variables, on the stack. The two test functions end up using the same memory for both variables. You’re doing the toy-language equivalent of C code that looks like

String *test_fn_1() {
  String s;
  return &s;
}

Ah of course, so I create a pointer to somewhere on the stack, and then the stackframe is popped and corrupted by the code following the function call. I suppose allocating on heap with llvm::CallInst::CreateMalloc is the solution? It at least works for the scenario above - will have to think about how my toy-language will deal with memory management of course.