8. Dolphin – Phase 5#
Attention
This is a group assignment. The workload is calibrated for a group of 3. Please also see the recommended workflow section in the Appendix below.
In case of questions regarding ambiguity of what you should do, ask questions on the forum. If you are in doubt and there is no enough time, use your best judgment and explain your reasoning in your report.
8.1. Assignment overview#
This assignment extends the language with three language features: records, arrays, and strings. For examples of programs that use these features, we refer to the Exercises for week 7, and the example programs provided with this assignment.
There are 6 tasks and no questions in this assignment. There is one glory task. Do it only if you have the time and feel ambitious.
8.1.1. What you need to get started#
This assignment is a continuation of the previous assignment. You will need to edit the code from the previous assignment.
The following features of LLVM that we have not previously used are relevant for this assignment
LLVM global identifiers and string literals.
LLVM cast operations such as
bitcast
andptrtoint
.LLVM’s
getelemntptr
instruction. See our reference material on GEP and Exercises for week 11.
You need to understand OCamllex lexer generator. Refer to OCamllex documentation for details.
You need to understand Menhir parser generator. Refer to Menhir documentation for details.
8.1.2. What you need to hand in#
Please hand in a .zip
file containing the following.
A brief report documenting your solution. Acceptable report formats are
.pdf
,.rtf
, and.md
. For each task and question, briefly (1 – 4 sentences) describe your implementation and answer. Write concisely.All the source files needed to reproduce your solution. This also includes the
C
code provided. Please explain in your report how the solution could be reproduced, e.g., callingmake
(if you have created aMakefile
), the command line to callclang
, etc.All the tests that you create (see Task 6) should be placed into a directory
assignment-08-tests
as individual.dlp
files.
Important
Make sure to understand all the code you hand in.
8.2. Records in Dolphin#
Records in Dolphin are similar to structs in C (or Java classes without methods). They are the only form of user-defined data types,
allow the grouping of data of different types under a single entity.
Record types are declared using record
keyword, which has the following syntax:
record <record-type-name> {
<field-name-1>:<type-1>;
<field-name-2>:<type-2>;
...
<field-name-n>:<type-n>;
}
For example,
record Tuple { x: int; y : int; }
. A record type is referred to in the program using its name, for example via var x:MyRec
.
New records are declared using the syntax
new <record-type-name> {
<field-name-1> = <field-init-expression-1>;
<field-name-2> = <field-init-expression-2>;
...
<field-name-n> = <field-init-expression-n>;
}
where
each <field-init-expressions>
must have the type corresponding to the field it initializes.
Given an expression e
of record type, its field f
is accessed using the dot notation: e.f
.
The keyword nil
denotes an invalid record of any record type, like null
in Java; attempting to access one of its fields should be reported as an error at runtime.
Below is an example of a simple program with records:
record Tuple { x: int; y : int ; }
int main () {
var a:Tuple = new Tuple { x = 0; y = 1; };
var b:Tuple = nil;
return a.x; /* dot notation to refer to the field `x` of variable `a` */
}
The following applies to records and their use in Dolphin programs.
All records are declared at the top-level. A Dolphin program is a collection of function and record declarations.
All records are mutually recursive.
Within a program, all record names must be unique.
The following record names are reserved for the standard library. They may not be used in the declarations of the user-defined records:
stream
,socket
,socket_address
,ip_version
,accepted_connection
,udp_recvfrom_result
, andconnection_type
.The only way to obtain non-nil values for the reserved records is through standard library. In particular, reserved records cannot be created in the program using the
new
keyword.All fields within the record must be unique.
The number of fields may be zero.
At record creation, the fields may appear in any order. For example,
var a:Tuple = new Tuple { y = 1; x = 0; };
is a valid record creation.All fields must be initialized. If a field initialization is missing, an error must be reported.
The only binary operations allowed on the records are equalty and inequality. Records are compared by reference. The following example program illustrates record equality.
/* valid program; returns 1 */
record Tuple { x: int; y : int ; }
int main () {
var a:Tuple = new Tuple { x = 0; y = 1;};
var b:Tuple = new Tuple { x = 0; y = 1;};
var c = a;
if (b == c) {
return 0;
}
if (a == c) {
return 1;
}
return 2;
}
If one of the operands of equality (or inequality) is a record, the other operand must be either (a) another record of the same type, or (b)
nil
.
Note
This particular design of record comparison is compatible with mainstream languages such as Java and C. This also means that other ways of definging equality, i.e., structurally, have to be implemented in code, e.g., by writing a function bool tuple_eq (Tuple t1, Tuple t2)
.
8.3. Arrays in Dolphin#
Arrays in Dolphin are similar to arrays in languages such as Java or C. They hold a fixed number of values of a single type. The type of the array is denoted using the syntax [<element-type>]
, e.g., [int]
is the type of array of integers. Arrays are initialized using the syntax new <element-type> [<length-expression>]
, where <length-expression>
must evaluate to an integer. For example, var x = new int[1+3]
initializes x
to be an array of 4 elements. The length of the arrays is, in general, not known at compile time. Arrays are indexed using the bracket notation <variable-name> [ <index-expression> ]
.
The following applies to arrays:
All array accesses – reading and writing – are bounds-checked.
Similar to records, the only allowed binary operations on arrays are equality and inequality. Similar to records, the equality checks are done by reference.
The number of array elements must be non-negative.
After array creation, the length of the array cannot change at runtime.
Array length is accessed using
length_of (<expression>)
, where<expression>
must evaluate to an array, andlength_of
is a keyword.
8.4. Strings in Dolphin#
In Dolphin, strings are a built-in type. They can be created in one of the following ways:
using string literals in the program source, e.g,.
"Hello"
using Dolphin standard library functions, e.g.,
string_concat ("Hello", "World")
concatenates two strings.
The following applies to strings:
The binary operations on strings are equality, inequality, and string comparison. Unlike arrays and records, strings are compared by value.
String literals may include escape characters: for example ‘\n’ stands for a newline. Dolphin follows OCaml’s lexical convention for representing escape sequences.
String literals may span over several lines.
The length of a string can be obtained using
length_of
keyword.
The following example program illustrates some string operations:
int main() {
var x = "hello
world";
var y = "hello\nworld";
if (x == y) {
return 0;
}
return length_of(x);
}
8.5. Abstract Syntax Tree#
Task 1: Design and implement an AST and Typed AST for Phase 5.
As you work on this task, take the following aspects into account.
8.5.1. Type representation#
We suggest the following OCaml data structures for representing strings, arrays, and records.
(* suggested code snippet to incorporate into your AST *)
type recordname = RecordName of {name : string; loc : Loc.location}
type fieldname = FieldName of {name : string; loc : Loc.location}
type ident = Ident of {name : string; loc : Loc.location}
type typ =
| ... (* placeholder for previous constructors *)
| Str of {loc : Loc.location}
| Array of {typ : typ; loc : Loc.location}
| Record of {recordname : recordname}
8.5.2. Program structure#
The structure of the new AST should follow the idea that a program is a list of functions or record declarations. You should create a way of representing record declarations in the AST.
8.5.3. Expressions#
To accommodate the new features, we suggest to extend your expr
type by
adding constructors for the following:
record creation
array creation
nil
expressionstring values
the
length_of
keyword
8.5.4. Extending Lvals#
In programming languages, lvals, correspond to
the program entities that may appear to the left of the assignment statement, hence the use of the letter l in the name. In previous phases, lvals were only identifiers. With the addition of records and arrays, the space of
lvals is now richer. It includes indexing into array or a record field, as well as their combination, e.g., f().x[1+g()].y[2].z
.
With regards to extending the AST to support lvals, we suggest the following.
type expr =
...
and lval =
| Var of ident
| Idx of { arr : expr
; index : expr
; loc : Loc.location
}
| Fld of { rcrd : expr
; field : fieldname (* see declaration of fieldname earlier *)
; loc : Loc.location
}
8.6. Lexer#
Task 2: Extend the lexer to support the new language features
As you work on this task, recognize what new tokens need to be added to the language.
For full support of strings, you may need to add a new lexer rule, in order to properly treat escape characters and strings spanning multiple lines. Note that because Dolphin follows OCaml’s specification of escape characters, we can use the OCaml standard library function Scanf.unescape
to recognize escape characters.
8.7. Parser#
Task 3: Extend the parser to support the new language features
To score full points on this task, your parser must not have any shift/reduce or reduce/reduce conflicts. As you work on this task, pay attention to proper parsing of lvals in your parser. Consult the examples and the specification earlier in this section regarding the correct syntax, i.e., the use of semicolons when delimiting fields in records - and write your own tests based on the specification.
8.8. Semantic analysis#
Task 4: Extend the semantic analysis to support the new language features
8.8.1. Record declarations#
In the implementation of semantic analysis, pay special attention to the mutual recursion of records. Similarly to the mutual recursion of functions, use two passes over the record declarations.
Identify all record names, and collect them into a data-structure (a list or a set).
Check each record individually, ensuring that the user-defined fields correspond to valid types.
8.8.2. Checking functions#
Once the record declarations are checked, the information about all the records can be collected into an environment where their names map to their types. This environment should also include the reserved records. Use this resulting environment when type checking function signatures and bodies.
8.9. Code generation#
Task 5: Extend the code generation to support the new language features
We start off by going over the runtime and standard library integration.
8.9.1. Runtime and standard library integration#
Before you proceed with code generation, it is necessary to port your project to the new runtime. The runtime and the standard library are split across several modules, because there is a qualitative distinction between the runtime and the standard library (unlike in the earlier phases), which we explain below. We have the following files.
runtime.c
contains the extended core runtime functionality, for operations such as record and array allocation, string equality, etc. What makes these functions part of the runtime (as opposed to the standand library) is that none of these C functions are exposed to the programmer. It is the compiler’s code generator that embeds calls to them, based on the (typed) AST.stdlib.c
contains Dolphin standard library that includes functions that are user-visible. These include a number of “everyday” functionality, such as functions for string concatenation, printing a string, etc. This is a relatively large module.runtime.h
is a header file that is included fromstdlib.c
.
These files are to be downloaded from Brightpsace.
8.9.1.1. Representation of reserved records and strings#
We map reserved records, e.g., stream
to empty LLVM records. This is sufficient because When passing these arguments back and forth to the runtime, we will
only use pointers to these records. This also means that we could have
represented the reserved records as generic pointers, i.e., i8*
; but the
empty record approach has an added benefit of tagging the intended use of the
signature functions (though the typing guarantees are weak because they can be overcome via casts).
Because strings are represented as LLVM structs, we also need to include a definition of the string type.
8.9.1.2. Declaring external functions and types#
Because your generated LLVM file uses the external functionality provided by
the runtime and standard library, it needs to include @declare
instructions
to let LLVM know of the the function signatures and that they are implemented elsewhere.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; The following declarations should be included in the generated LLVM file ;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; LLVM struct corresponding to the reserved stream type
%dolphin_record_stream = type { }
; LLVM struct correpsonding to the string representation
%string_type = type {i64, i8* }
@dolphin_rc_empty_string = external global %string_type
; signatures for the runtime functions
; -- not user visible --
declare i64 @compare_strings(%string_type*, %string_type*)
declare i8* @allocate_record(i32)
declare i8* @raw_allocate_on_heap(i32)
declare i8* @allocate_array(i32, i64, i8*)
declare void @report_error_array_index_out_of_bounds()
declare void @report_error_nil_access()
declare void @report_error_division_by_zero()
declare i64 @string_length(%string_type*)
; signatures for the core standard library
; -- these are user visible --
declare %string_type* @bytes_array_to_string(i8*)
declare i8* @string_to_bytes_array(%string_type*)
declare i64 @byte_to_int_unsigned(i8)
declare i64 @byte_to_int_signed(i8)
declare i8 @int_to_byte_unsigned(i64)
declare i8 @int_to_byte_signed(i64)
declare i64 @ascii_ord(%string_type*)
declare %string_type* @ascii_chr(i64)
declare %string_type* @string_concat(%string_type*, %string_type*)
declare %string_type* @substring(%string_type*, i64, i64)
declare %string_type* @int_to_string(i64)
declare i64 @string_to_int(%string_type*)
declare i64 @input_byte(%dolphin_record_stream*)
declare i1 @output_byte(i8, %dolphin_record_stream*)
declare i8* @input_bytes_array(i64, %dolphin_record_stream*)
declare void @output_bytes_array(i8*, %dolphin_record_stream*)
declare void @output_string(%string_type*, %dolphin_record_stream*)
declare i1 @seek_in_file(i64, i1, %dolphin_record_stream*)
declare i64 @pos_in_file(%dolphin_record_stream*)
declare i1 @close_file(%dolphin_record_stream*)
declare i1 @flush_file(%dolphin_record_stream*)
declare i1 @error_in_file(%dolphin_record_stream*)
declare i1 @end_of_file(%dolphin_record_stream*)
declare i64 @get_eof()
declare %dolphin_record_stream* @open_file(%string_type*, %string_type*)
declare %dolphin_record_stream* @get_stdin()
declare %dolphin_record_stream* @get_stderr()
declare %dolphin_record_stream* @get_stdout()
declare %string_type** @get_cmd_args()
declare void @exit(i64)
; array length utility functions
define i32 @dolphin_rc_compute_array_length_size () {
%size_ptr = getelementptr i64, i64* null, i64 1
%size = ptrtoint i64* %size_ptr to i32
ret i32 %size
}
define void @dolphin_rc_set_array_length (i64* %array, i64 %size) {
%len_ptr = getelementptr i64, i64* %array, i64 -1
store i64 %size, i64* %len_ptr
ret void
}
define i64 @dolphin_rc_get_array_length (i64* %array) {
%len_ptr = getelementptr i64, i64* %array, i64 -1
%size= load i64, i64* %len_ptr
ret i64 %size
}
8.9.2. Records#
8.9.2.1. Record representation#
We represent records using LLVM structs. The translation is quite straightforward and is one-to-one. A Dolphin record with n
fields is translated to an LLVM struct with n
fields. The types of the LLVM fields should correspond to the Dolphin types. For example, the Dolphin declaration of two types.
record T1 { x: int; t2 : T2;}
record T2 { y: int; t1 : T1;}
is translated to LLVM as follows
%dlp_rec_T2 = type { i64, %dlp_rec_T1* }
%dlp_rec_T1 = type { i64, %dlp_rec_T2* }
Note that the choice of the naming %dlp_rec_T2
here is compiler-chosen; you may
chose a different naming convention. What is important is that the code generation phase
of the compiler is aware of the mapping between the source and the target types, and
that of course the generated LLVM code is valid.
8.9.2.2. Nil representation#
Nil record values can be represented as null
in LLVM.
8.9.2.3. Record initialization#
Allocation of a record is done through the runtime function allocate_record
.
This function takes an argument corresponding to the size of the record. Because the
size depends on LLVM, we need to implement an architecture-independent way of
obtaining the size information. This is accomplished using the GEP
size hack[Lat05] as illustrated below.
To actually save the payload of the record, we further need to use a combination of GEP
and store instructions.
For example, consider the following record creation
var t2 = new T2{y = 10; t1 = t1; } /* for some previously computed value `t1` */
The LLVM code for it can look as follows (we abbreviate getelementptr
as <GEP>
for brevity in the listing).
In this example, we need to use bitcast
to cast the generic pointer i8*
returned
by the runtime allocation function into the LLVM type.
Note that casts typically are erased in the LLVM to x86 translation; in other words, they have no performance overhead.
; Suppose %var_t1 and %var_t2 are the identifiers
; that we alloca-ed for the source level t1, t2
; GEP size hack
%size_ptr = getelementptr %dlp_rec_T2, %dlp_rec_T2* null, i32 1
; Cast ptr to integer
%size = ptrtoint %dlp_rec_T2* %size_ptr to i32
; Call into the runtime for allocation
%ptr_rt = call i8* @allocate_record (i32 %size)
; Turn generic pointer into a more specific one, so we can use GEP
%t2_ptr = bitcast i8* %ptr_rt to %dlp_record_T2*
; Access field y of the struct
%ptr_field_y_of_var_t2 = getelementptr %dlp_rec_T2, %dlp_rec_T2* %t2_ptr, i32 0, i32 0
; Save 10 in the field y
store i64 10, i64* %ptr_field_y_of_var_t2
; Read from %var_t1
%ptr_t1 = load %dlp_rec_T1*, %dlp_rec_T1** %var_t1
; Access field t1 of the struct
%ptr_field_t1_of_var_t2 = getelementptr %dlp_rec_T2, %dlp_rec_T2* %t2_ptr, i32 0, i32 1
; Save in the field t1
store %dlp_rec_T1* %ptr_t1, %dlp_rec_T1** %ptr_field_t1_of_var_t2
; Save in %var_t2
store %dlp_rec_T2* %t2_ptr %dlp_rec_T2** %var_t2
8.9.2.4. Record access#
To access the record, we need to use the GEP instruction similarly to how it is used in the initialization above.
8.9.3. Array translation#
8.9.3.1. Array representation#
An array is represented as a contiguous block of memory, with the metadata information about the length stored in memory before the content.
┌─────────┬─────────────────────────────────────────────────┐
│ Length │ Array content │
└─────────┴─────────────────────────────────────────────────┘
▲ ▲
│ │
│ The pointer to the start of the array
│
│
Length is accessed by casting the pointer to the start of the
array to i64* and using GEP with index -1
8.9.3.2. Array initialization#
Array initialization takes place via function allocate_array
in the runtime. Note that the last
argument to that function needs to include the pointer to the initialization value.
To correctly access the array length, our runtime depends on three functions to be generated by our compiler. These functions
are declared in the header file dolphin_rc.h
.
/* dolphin_rc.h - include this file in your project */
#include <stdint.h>
extern int32_t dolphin_rc_compute_array_length_size();
extern void dolphin_rc_set_array_length(void *, int64_t);
extern int64_t dolphin_rc_get_array_length(void *);
8.9.3.3. Array access#
To access the i
-th element of the array (counting from zero), we can use GEP instruction with i
as the first index.
8.9.4. String translation#
8.9.4.1. Runtime representation#
Strings are represented at runtime as C structs of the type.
struct string { int64_t len; char * contents; };
8.9.4.2. String literals in LLVM code.#
String literals are represented in the source as global identifiers.
A string literal is represented as an LLVM global of LLVM array type. Recall that LLVM arrays have static length (and are only used here to represent string literals). Consider the following source program and the associated translation.
int main () {
var x = "Hello World\n";
output_string (x, get_stdout());
return 0;
}
This corresponding simplified LLVM code can look as follows
;; external functions and reserved type declarations omitted
@str_lit_struct = global { i64, [12 x i8]* } {i64 12, [12 x i8]* @str_lit}
@str_lit = global [12 x i8] c"Hello World\0A"
define i64 @dolphin_fun_main () {
%var_x = alloca %string_type*
%str_cast = bitcast { i64, [12 x i8]* }* @str_lit_struct to %string_type*
store %string_type* %str_cast, %string_type** %var_x
%tmp_1 = load %string_type*, %string_type** %var_x
%tmp_2 = call %dolphin_record_stream* @get_stdout ()
call void @output_string (%string_type* %tmp_1, %dolphin_record_stream* %tmp_2)
ret i64 0
}
8.9.5. Comparing strings#
String comparison is to be translated to calls to the runtime function compare_strings
. This
function compares two strings lexicographically. It returns 0
if the strings are identical,
-1
if the first strings is less than the second one, and 1
if the first argument is greater than the second one.
8.10. Consolidation and testing#
Task 6: Put all the phases together and test your functionality
Add at least 10 new tests that check for the negative behavior in the semantic analysis and the frontend that you have implemented.
Add at least 10 new tests that check for the positive behavior of the frontend.
Run your compiler on the provided example programs and check their behavior.
Describe why your tests are useful.
8.10.1. Glory task#
Task 7 (glory): Add support for the full standard library
Full standard library includes networking API. This will allow you to run the HTTP server example. See full standard library signature in the Appendix.
8.11. Appendix#
8.11.1. Example programs#
We provide a handful of example programs together with their expected output. Download them from brightspace.
8.11.2. Recommended workflow#
As a general rule of thumb, work your way very slowly through the assignment, especially because as you add code generation, the size of the generated LLVM programs will grow substantially, and it is crucial that you understand it. Read and test the LLVM code you generate as you incrementally add new aspects of code generation. For example, test record creation immediately after you implement it, even before you complete other aspects of records, i.e., record lookups.
At a high level, we suggest the following order for the assignment:
Implement the most rudimentary support for strings in the frontend. In particular, for lexing, ignore escape characters and newlines.
Add runtime integration and code generation for strings. At this point, you should be able to compile and run something as simple as
int main () { output_string ("Hello World!", get_stdout()); return 0; }
Add support for records through all the compiler phases.
Add support for arrays through all the compiler phases. Get the core functionality of arrays (creation, lookup, update) working first, and add bounds-checking afterwards.
Add support for lexing of complex strings, including escape characters and newlines.
Consolidate.
8.11.3. Full standard library signature#
%dolphin_record_udp_recvfrom_result = type { i8*, %dolphin_record_socket_address* }
%dolphin_record_accepted_connection = type { %dolphin_record_socket*, %dolphin_record_socket_address* }
%dolphin_record_socket = type { }
%dolphin_record_socket_address = type { }
%dolphin_record_ip_address = type { }
%dolphin_record_ip_version = type { }
%dolphin_record_connection_type = type { }
%dolphin_record_stream = type { }
%string_type = type {i64, i8* }
@dolphin_rc_empty_string = external global %string_type
declare i64 @compare_strings(%string_type*, %string_type*)
declare i8* @allocate_record(i32)
declare i8* @raw_allocate_on_heap(i32)
declare i8* @allocate_array(i32, i64, i8*)
declare void @report_error_array_index_out_of_bounds()
declare void @report_error_nil_access()
declare void @report_error_division_by_zero()
declare %dolphin_record_udp_recvfrom_result* @socket_recvfrom_udp(%dolphin_record_socket*)
declare i64 @socket_sendto_udp(%dolphin_record_socket*, %dolphin_record_socket_address*, i8*)
declare i1 @socket_close(%dolphin_record_socket*)
declare i1 @socket_activate_udp(%dolphin_record_socket*)
declare i1 @socket_connect(%dolphin_record_socket*, %dolphin_record_socket_address*)
declare %dolphin_record_accepted_connection* @socket_accept(%dolphin_record_socket*)
declare i1 @socket_listen(%dolphin_record_socket*, i64)
declare i1 @socket_bind(%dolphin_record_socket*, %dolphin_record_socket_address*)
declare i64 @get_port_of_socket_address(%dolphin_record_socket_address*)
declare %dolphin_record_ip_address* @get_ip_address_of_socket_address(%dolphin_record_socket_address*)
declare %dolphin_record_socket_address* @create_socket_address(%dolphin_record_ip_address*, i64)
declare %string_type* @ip_address_to_string(%dolphin_record_ip_address*)
declare %dolphin_record_ip_address* @string_to_ip_address(%dolphin_record_ip_version*, %string_type*)
declare %dolphin_record_stream* @socket_get_output_stream(%dolphin_record_socket*)
declare %dolphin_record_stream* @socket_get_input_stream(%dolphin_record_socket*)
declare %dolphin_record_socket* @create_socket(%dolphin_record_ip_version*, %dolphin_record_connection_type*)
declare %dolphin_record_ip_address* @get_ipv6_address_any()
declare %dolphin_record_ip_address* @get_ipv4_address_any()
declare %dolphin_record_ip_version* @get_ipv6()
declare %dolphin_record_ip_version* @get_ipv4()
declare %dolphin_record_connection_type* @get_tcp_connection_type()
declare %dolphin_record_connection_type* @get_udp_connection_type()
declare i64 @string_length(%string_type*)
declare %string_type* @bytes_array_to_string(i8*)
declare i8* @string_to_bytes_array(%string_type*)
declare i64 @byte_to_int_unsigned(i8)
declare i64 @byte_to_int_signed(i8)
declare i8 @int_to_byte_unsigned(i64)
declare i8 @int_to_byte_signed(i64)
declare i64 @ascii_ord(%string_type*)
declare %string_type* @ascii_chr(i64)
declare %string_type* @string_concat(%string_type*, %string_type*)
declare %string_type* @substring(%string_type*, i64, i64)
declare %string_type* @int_to_string(i64)
declare i64 @string_to_int(%string_type*)
declare i64 @input_byte(%dolphin_record_stream*)
declare i1 @output_byte(i8, %dolphin_record_stream*)
declare i8* @input_bytes_array(i64, %dolphin_record_stream*)
declare void @output_bytes_array(i8*, %dolphin_record_stream*)
declare void @output_string(%string_type*, %dolphin_record_stream*)
declare i1 @seek_in_file(i64, i1, %dolphin_record_stream*)
declare i64 @pos_in_file(%dolphin_record_stream*)
declare i1 @close_file(%dolphin_record_stream*)
declare i1 @flush_file(%dolphin_record_stream*)
declare i1 @error_in_file(%dolphin_record_stream*)
declare i1 @end_of_file(%dolphin_record_stream*)
declare i64 @get_eof()
declare %dolphin_record_stream* @open_file(%string_type*, %string_type*)
declare %dolphin_record_stream* @get_stdin()
declare %dolphin_record_stream* @get_stderr()
declare %dolphin_record_stream* @get_stdout()
declare %string_type** @get_cmd_args()
declare void @exit(i64)
define i32 @dolphin_rc_compute_array_length_size () {
%size_of_length_addr$0 = getelementptr i64, i64* null, i64 1
%size_of_length$1 = ptrtoint i64* %size_of_length_addr$0 to i32
ret i32 %size_of_length$1
}
define void @dolphin_rc_set_array_length (i64* %array, i64 %size) {
%array_length$2 = getelementptr i64, i64* %array, i64 -1
store i64 %size, i64* %array_length$2
ret void
}
define i64 @dolphin_rc_get_array_length (i64* %array) {
%array_length$3 = getelementptr i64, i64* %array, i64 -1
%size$4 = load i64, i64* %array_length$3
ret i64 %size$4
}