We consider an Ada program compiled with its native GCC-based toolchain (GNAT). The Ada front end parses and type-checks the source, and GCC lowers the program to GIMPLE-IR as part of its normal optimization pipeline. Instead of continuing to RTL and generating code for a GCC-supported architecture, we intercept the compilation at the GIMPLE level and feed the GIMPLE dump to our model IRIS.
IRIS translates the GIMPLE-IR into semantically equivalent LLVM-IR, which we then pass to the LLVM toolchain and compile to WebAssembly, a target not currently supported by GCC. At the time of writing, either GNAT LLVM provide direct support for compiling Ada to WebAssembly.
This pipeline effectively enables Ada code to run in WebAssembly environments without modifying the Ada front end or implementing a dedicated Ada-to-WebAssembly backend. It illustrates how IR-to-IR translation can decouple language front ends from specific back ends and open new deployment targets with minimal changes to existing compiler infrastructure.
- We start from the following illustrative Ada snippet:
with Ada.Text_IO; use Ada.Text_IO;
procedure Hello is
begin
Put_Line ("Hello World. Welcome to GNAT");
end;
- GCC dumps the GIMPLE IR:
_GLOBAL.SZ0.ada_hello (positive___XDLU_1__2147483647 p0, positive___XDLU_1__2147483647 p1)
{
bitsizetype D.4598;
bitsizetype iftmp.0;
if (p1 <= p0) goto <D.4600>; else goto <D.4601>;
<D.4600>:
_1 = (sizetype) p0;
_2 = (sizetype) p1;
_3 = _1 - _2;
_4 = _3 + 1;
_5 = (bitsizetype) _4;
iftmp.0 = _5 * 8;
goto <D.4602>;
<D.4601>:
iftmp.0 = 0;
<D.4602>:
D.4598 = iftmp.0;
return D.4598;
}
_GLOBAL.SZ1.ada_hello (positive___XDLU_1__2147483647 p0, positive___XDLU_1__2147483647 p1)
{
sizetype D.4603;
sizetype iftmp.1;
if (p1 <= p0) goto <D.4605>; else goto <D.4606>;
<D.4605>:
_1 = (sizetype) p0;
_2 = (sizetype) p1;
_3 = _1 - _2;
iftmp.1 = _3 + 1;
goto <D.4607>;
<D.4606>:
iftmp.1 = 0;
<D.4607>:
D.4603 = iftmp.1;
return D.4603;
}
hello ()
{
struct D.4608;
{
typedef signed long struct signed long;
typedef character hello__T1b[1:28];
D.4608.P_ARRAY = "Hello World. Welcome to GNAT";
D.4608.P_BOUNDS = &*.LC0;
ada.text_io.put_line (D.4608);
}
return;
}
- The IRIS model translates this GIMPLE IR to LLVM IR:
; ModuleID = 'main.c'
source_filename = "main.c"
target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64-pc-linux-gnu"
%struct.anon = type { i8*, i8* }
@.str = private unnamed_addr constant [29 x i8] c"Hello World. Welcome to GNAT\00", align 1
@.str.1 = private unnamed_addr constant [2 x i8] c"\0A\00", align 1
; Function Attrs: noinline nounwind optnone uwtable
define dso_local i64 @SZ0(i32 noundef %0, i32 noundef %1) #0 {
%3 = alloca i32, align 4
%4 = alloca i32, align 4
store i32 %0, i32* %3, align 4
store i32 %1, i32* %4, align 4
%5 = load i32, i32* %4, align 4
%6 = load i32, i32* %3, align 4
%7 = icmp ugt i32 %5, %6
br i1 %7, label %8, label %9
8: ; preds = %2
br label %17
9: ; preds = %2
%10 = load i32, i32* %3, align 4
%11 = zext i32 %10 to i64
%12 = getelementptr inbounds i8, i8* null, i64 %11
%13 = load i32, i32* %4, align 4
%14 = zext i32 %13 to i64
%15 = sub i64 0, %14
%16 = getelementptr inbounds i8, i8* %12, i64 %15
br label %17
17: ; preds = %9, %8
%18 = phi i8* [ null, %8 ], [ %16, %9 ]
%19 = ptrtoint i8* %18 to i64
%20 = sub i64 %19, 0
ret i64 %20
}
; Function Attrs: noinline nounwind optnone uwtable
define dso_local i64 @SZ1(i32 noundef %0, i32 noundef %1) #0 {
%3 = alloca i32, align 4
%4 = alloca i32, align 4
store i32 %0, i32* %3, align 4
store i32 %1, i32* %4, align 4
%5 = load i32, i32* %4, align 4
%6 = load i32, i32* %3, align 4
%7 = icmp ugt i32 %5, %6
br i1 %7, label %8, label %9
8: ; preds = %2
br label %16
9: ; preds = %2
%10 = load i32, i32* %3, align 4
%11 = load i32, i32* %4, align 4
%12 = zext i32 %11 to i64
%13 = sub i64 0, %12
%14 = getelementptr inbounds i8, i8* null, i64 %13
%15 = zext i32 %10 to i64
br label %16
16: ; preds = %9, %8
%17 = phi i8* [ null, %8 ], [ %14, %9 ]
%18 = ptrtoint i8* %17 to i64
%19 = sub i64 %18, 0
ret i64 %19
}
; Function Attrs: noinline nounwind optnone uwtable
define dso_local void @hello() #0 {
%1 = alloca %struct.anon, align 8
%2 = getelementptr inbounds %struct.anon, %struct.anon* %1, i32 0, i32 0
store i8* getelementptr inbounds ([29 x i8], [29 x i8]* @.str, i64 0, i64 0), i8** %2, align 8
%3 = getelementptr inbounds %struct.anon, %struct.anon* %1, i32 0, i32 1
store i8* getelementptr inbounds ([2 x i8], [2 x i8]* @.str.1, i64 0, i64 0), i8** %3, align 8
call void @put_line(%struct.anon* noundef byval(%struct.anon) align 8 %1)
ret void
}
declare void @put_line(%struct.anon* noundef byval(%struct.anon) align 8) #1
attributes #0 = { noinline nounwind optnone uwtable "frame-pointer"="all" "min-legal-vector-width"="0" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" }
attributes #1 = { "frame-pointer"="all" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" }
!llvm.module.flags = !{!0, !1, !2, !3, !4}
!llvm.ident = !{!5}
!0 = !{i32 1, !"wchar_size", i32 4}
!1 = !{i32 7, !"PIC Level", i32 2}
!2 = !{i32 7, !"PIE Level", i32 2}
!3 = !{i32 7, !"uwtable", i32 1}
!4 = !{i32 7, !"frame-pointer", i32 2}
!5 = !{!"Ubuntu clang version 14.0.0-1ubuntu1.1"}
- When we try to compile the generated LLVM IR, we get the following error:
> clang hello.ll -o hello_binary -O0 -nostartfiles -Wl,-e,hello -lgnat-10
/usr/bin/ld: /scratch/tmp/hello-d7da56.o: in function `hello':
main.c:(.text+0xc3): undefined reference to `put_line'
clang: error: linker command failed with exit code 1 (use -v to see invocation)
- As a workaround, we define our own
put_line.c:
#include <stdio.h>
void put_line(char *s1, char *s2) {
(void)s2;
puts(s1);
}
- Change the
put_line()call in the LLVM IR so that the function signatures match:
79c79,81
< call void @put_line(%struct.anon* noundef byval(%struct.anon) align 8 %1)
---
> %4 = load i8*, i8** %2, align 8
> %5 = load i8*, i8** %3, align 8
> call void @put_line(i8* noundef %4, i8* noundef %5)
83c85
< declare void @put_line(%struct.anon* noundef byval(%struct.anon) align 8) #1
---
> declare dso_local void @put_line(i8* noundef, i8* noundef) #1
- And define a
driver.cto specify the entry-point:
void hello(void);
int main(void) {
hello();
return 0;
}
- Finally, compiling with
clang hello-modif.ll put_line.o driver.c -O0 -lgnat-10 -o hello_paperworks:
avalenz1@BSC-8488191241:~/ada/paper-sample/test/paper$ ./hello_paper
Hello World. Welcome to GNAT
We are not sure about:
-
Do we need the
driver.c? -
Can we link correctly agains
libgnat-10.soso that we do not need to define our ownput_line.cto get the missing symbols?