Due: before class on Wednesday 05 November
These problems are from the second half of Unit 3 and from class 4.1.
-
Consider the following code in Rust:
fn eat_num(val: i32) { /* ... */ } fn eat_str(val: String) { /* ... */ } fn main() { let my_num = 100000; eat_num(my_num); println!("{}", my_num); // Line A let my_str = String::from("snickers"); eat_str(my_str); println!("{}", my_str); // Line B }Why does
Line Acompile, butLine Bcauses a “use of moved value” error? (Select all that apply)i32is a “Plain Old Data” (POD) type that lives entirely on the stack.i32implements theCopytrait, somy_numis copied intoeat_numinstead of being moved.Stringmanages heap-allocated data and does not implement theCopytrait, so ownership ofmy_stris moved intoeat_str.- Passing
my_stras&my_str(a borrow) would fix the error atLine Bby passing an immutable reference instead of moving ownership. Stringvalues are always allocated on the stack, just likei32values.
-
The following Rust code is intended to take a
String, append"&Ms", and then print the modified original string. It fails to compile for several reasons related to ownership and mutability.fn add_m(msg: String) { msg.push_str("&Ms"); } fn main() { let tasty = String::from("M"); add_m(tasty); println!("Yum: {}", tasty); }Task: Fix the code so it compiles and correctly prints “Yum: M&Ms”.
Use the rust playground to test your work.
-
In class, we wrote a function like this to create a new, pluralized version of a string without altering the original one:
define ptr @pluralize(ptr %str) { ; we need n, n+1, and n+2 %n = call i64 @strlen(ptr %str) %n1 = add i64 %n, 1 %n2 = add i64 %n, 2 ; allocate new heap array with length n+2 %result = call ptr @malloc(i64 %n2) ; copy original string into new one call void @memcpy(ptr %result, ptr %str, i64 %n) ; assign the s (same as before!) %offn = getelementptr i8, ptr %result, i64 %n store i8 115, ptr %offn %offn1 = getelementptr i8, ptr %result, i64 %n1 store i8 0, ptr %offn1 ret ptr %result }Question: Why is heap allocation with
@mallocnecessary in this version? (Select all that apply)- If the new string were allocated on the stack with
alloca, the memory would be automatically deallocated when thepluralizefunction returns. - Returning a pointer to memory allocated with
allocafrom within a function results in a dangling pointer. @mallocis the only instruction that can allocate memory for an array ofi8characters.- The
loadandstoreinstructions only work on pointers returned from@malloc, notalloca. - The new string’s memory must “live” longer than the function call that created it, which is the purpose of heap allocation.
- If the new string were allocated on the stack with
-
Why does the introduction of branching (like
if/elseor loops) make stack memory (alloca/load/store) necessary, even for simple variables? (Select all that apply)- LLVM is an SSA (Static Single Assignment) language, meaning a register (e.g.,
%x) cannot be assigned more than once. - A loop, by definition, needs to update a variable (like a counter
i). In SSA, this changing value must be stored in and re-loaded from memory. - In an
if/elsestructure, both branches might assign a different value to the same variable. - To resolve the
if/elseproblem, both theifblockandelseblockmust store their computed value to a single memory address (%zaddr) allocated in thetopblock. - The
brinstruction requires all registers to be cleared, so values must be saved to the stack first.
- LLVM is an SSA (Static Single Assignment) language, meaning a register (e.g.,
-
Which of the following statements about LLVM basic blocks and branching are correct? (Select all that apply)
- A basic block is a sequence of instructions that ends with a terminator instruction, such as
brorret. - The conditional
brinstruction must specify both an “if” destination (label %iflabel) and an “else” destination (label %elselabel). - If a conditional
brinstruction’s condition is false, execution “falls through” to the next instruction after thebr. - If there is no label at the beginning of a function, then that function does not have any basic blocks.
- A single basic block can contain multiple
brinstructions to create complex internal logic before the terminator.
- A basic block is a sequence of instructions that ends with a terminator instruction, such as
-
Translate the following C function into LLVM IR. Your translation must use the
alloca/load/storepattern to manage the variablesiandsum, as their values change.int sum_up_to(int n) { int i = 1; int sum = 0; while (i <= n) { sum = sum + i; i = i + 1; } return sum; } -
Draw the control flow graph (CFG) for the LLVM function you wrote for the previous problem.
-
Which of the following statements accurately describe a closure? (Select all that apply)
- A closure is a “bundle” containing a function’s code plus its captured referencing environment.
- The captured variables in a closure are resolved based on where the function is called (dynamic scope).
- The captured variables in a closure are resolved based on where the function was defined (lexical scope).
- In Java, a lambda that captures a local variable is conceptually similar to creating an object instance that stores the captured data and has one method.
- A Rust lambda that captures a local variable can lead to a dangling reference if it is called after the captured variable’s stack frame is deallocated