mini iLisp "hello world" - graeme-lockley/ilisp GitHub Wiki
The hello world of mini-iLisp
is super simple.
(println "hello world)
A handful of constraints that need to be remembered:
-
When linking a binary the single public identifier
main
is called with arguments. The usual C declaration formain
captures this.int main(int argc, char *argv[], char *envp[]) { ... }
Compiling hello world as follows is sufficient:
declare i32 @printf(i8*, ...) #1 @.str = private unnamed_addr constant [14 x i8] c"hello worlds\0A\00", align 1 define i32 @main() #0 { %1 = call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([14 x i8], [14 x i8]* @.str, i64 0, i64 0)) ret i32 0 }
-
In the code above is string is being represented as a pointer to a chunk of memory without any tagging. By virtue of
mini-iLisp
being a dynamically typed language it is necessary for every value to be tagged. Rewriting this into C we end up with:#include <stdio.h> #define INT_VALUE 0 #define STR_VALUE 1 struct Value { char type; union { int number; char *str; }; }; void print_value(struct Value *value) { switch (value->type) { case INT_VALUE: printf("%d", value->number); break; case STR_VALUE: printf("%s", value->str); break; } } int main() { struct Value v; v.type = STR_VALUE; v.str = "hello world"; print_value(&v); printf("\n"); }
This is then translated into the following IR
declare i32 @printf(i8*, ...) #1 %struct.Value = type { i8, %union.anon } %union.anon = type { i8* } @.str = private unnamed_addr constant [3 x i8] c"%d\00", align 1 @.str.1 = private unnamed_addr constant [3 x i8] c"%s\00", align 1 @.str.2 = private unnamed_addr constant [12 x i8] c"hello world\00", align 1 @.str.3 = private unnamed_addr constant [2 x i8] c"\0A\00", align 1 define void @print_value(%struct.Value* %0) #0 { %2 = alloca %struct.Value*, align 8 store %struct.Value* %0, %struct.Value** %2, align 8 %3 = load %struct.Value*, %struct.Value** %2, align 8 %4 = getelementptr inbounds %struct.Value, %struct.Value* %3, i32 0, i32 0 %5 = load i8, i8* %4, align 8 %6 = sext i8 %5 to i32 switch i32 %6, label %19 [ i32 0, label %7 i32 1, label %13 ] 7: %8 = load %struct.Value*, %struct.Value** %2, align 8 %9 = getelementptr inbounds %struct.Value, %struct.Value* %8, i32 0, i32 1 %10 = bitcast %union.anon* %9 to i32* %11 = load i32, i32* %10, align 8 %12 = call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([3 x i8], [3 x i8]* @.str, i64 0, i64 0), i32 %11) br label %19 13: %14 = load %struct.Value*, %struct.Value** %2, align 8 %15 = getelementptr inbounds %struct.Value, %struct.Value* %14, i32 0, i32 1 %16 = bitcast %union.anon* %15 to i8** %17 = load i8*, i8** %16, align 8 %18 = call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([3 x i8], [3 x i8]* @.str.1, i64 0, i64 0), i8* %17) br label %19 19: ret void } define dso_local i32 @main() #0 { %1 = alloca %struct.Value, align 8 %2 = getelementptr inbounds %struct.Value, %struct.Value* %1, i32 0, i32 0 store i8 1, i8* %2, align 8 %3 = getelementptr inbounds %struct.Value, %struct.Value* %1, i32 0, i32 1 %4 = bitcast %union.anon* %3 to i32* store i32 ptrtoint ([12 x i8]* @.str.2 to i32), i32* %4, align 8 call void @print_value(%struct.Value* %1) %5 = call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([2 x i8], [2 x i8]* @.str.3, i64 0, i64 0)) ret i32 0 }
-
The union will be extended to accommodate the different types as they are introduced in later sections.
-
The function
print_value
is dropped into a library and linked in.