Hone logo
Hone
Problems

Go Code Inlining Engine

This challenge asks you to build a Go program that can analyze and potentially inline simple functions within other Go functions. Inlining is a compiler optimization technique that replaces a function call with the actual code of the called function, which can improve performance by reducing function call overhead.

Problem Description

Your task is to create an "inlining engine" for a simplified subset of Go code. This engine will take a string representing Go source code, identify specific functions that are candidates for inlining, and then perform the inlining operation.

Key Requirements:

  1. Parse Go Code: The engine must be able to parse Go source code into an Abstract Syntax Tree (AST). The go/parser and go/ast packages are highly recommended for this.
  2. Identify Inlinable Functions: Define criteria for which functions are considered "inlinable." For this challenge, a function is inlinable if:
    • It is a top-level function (not a method or a function literal).
    • It has a single return statement.
    • It has no side effects (e.g., no printing to console, no file I/O, no complex control flow like defer or panic). For simplicity, we'll consider functions with only assignment statements, arithmetic operations, and simple return values as having no side effects.
    • It is called only once within the scope of other functions that could be modified by the inlining engine.
  3. Perform Inlining: For each identified inlinable function and its single call site:
    • Replace the function call with the body of the inlined function.
    • Ensure variable scopes are handled correctly (e.g., remapping parameters to local variables and adjusting variable names if conflicts arise).
    • Remove the original function definition if it's no longer called anywhere else (though for this challenge, we can simplify and just remove the call).
  4. Output Modified Code: The engine should output the modified Go source code as a string.

Expected Behavior:

The engine should process the input Go code, find eligible functions, and substitute their bodies into their call sites. Functions that do not meet the inlining criteria, or functions that are called multiple times, should remain as they are.

Edge Cases to Consider:

  • Functions with no parameters.
  • Functions with multiple parameters.
  • Functions returning different types.
  • Variable name collisions between the caller and the inlined function.
  • The case where an inlinable function is called within the same function definition.
  • Input code with syntax errors (though for this challenge, assume valid Go syntax for the functions being processed).

Examples

Example 1:

package main

import "fmt"

func add(a, b int) int {
	return a + b
}

func main() {
	x := 5
	y := 10
	result := add(x, y)
	fmt.Println(result)
}

Output:

package main

import "fmt"

func main() {
	x := 5
	y := 10
	result := x + y // Inlined 'add' function
	fmt.Println(result)
}

Explanation: The add function is simple, has one return, and is called only once in main. Its body a + b replaces the add(x, y) call, with a being x and b being y.

Example 2:

package main

import "fmt"

func multiply(a, b int) int {
	return a * b
}

func calculate(val int) int {
	temp := multiply(val, 2)
	return temp + 5
}

func main() {
	val := 3
	finalResult := calculate(val)
	fmt.Println(finalResult)
}

Output:

package main

import "fmt"

func calculate(val int) int {
	temp := val * 2 // Inlined 'multiply' function
	return temp + 5
}

func main() {
	val := 3
	finalResult := calculate(val)
	fmt.Println(finalResult)
}

Explanation: The multiply function is inlinable. It's called within calculate. The multiply(val, 2) call is replaced by val * 2. The original multiply function definition is removed from the output for clarity, although the core task is to modify the call site.

Example 3:

package main

import "fmt"

func complexOperation(a int) int {
	if a > 10 {
		return a * 2
	} else {
		return a + 5
	}
}

func anotherFunc(b int) int {
	return b * 3
}

func main() {
	val1 := 15
	res1 := complexOperation(val1)
	fmt.Println(res1)

	val2 := 7
	res2 := complexOperation(val2) // Second call to complexOperation
	fmt.Println(res2)

	val3 := 4
	res3 := anotherFunc(val3)
	fmt.Println(res3)
}

Output:

package main

import "fmt"

func complexOperation(a int) int {
	if a > 10 {
		return a * 2
	} else {
		return a + 5
	}
}

func anotherFunc(b int) int {
	return b * 3
}

func main() {
	val1 := 15
	res1 := complexOperation(val1)
	fmt.Println(res1)

	val2 := 7
	res2 := complexOperation(val2) // Second call to complexOperation
	fmt.Println(res2)

	val3 := 4
	res3 := anotherFunc(val3)
	fmt.Println(res3)
}

Explanation: complexOperation is not inlinable because it has conditional logic (multiple return paths). anotherFunc is inlinable, but it's only called once. The problem description prioritizes inlining functions that are called only once within the functions that the engine will modify. In this simplified scenario, neither function is inlined because complexOperation doesn't meet the criteria, and while anotherFunc does, the example focuses on showcasing why complexOperation isn't chosen. If anotherFunc were called only once in main, it would be a candidate.

Constraints

  • The input Go source code will be a single string representing a valid Go package.
  • Focus on inlining top-level functions with simple bodies (assignments, arithmetic, single return). Avoid complex control flow (if, for, switch, select), defer, panic, go statements, and external function calls within the inlinable function's body.
  • The solution should be written entirely in Go.
  • For this challenge, assume no external dependencies beyond the standard Go library (go/parser, go/ast, go/token, go/printer, etc.).
  • The performance of the parsing and AST manipulation is not a primary concern, but the resulting code generation should be reasonably efficient.

Notes

  • The go/ast package provides a powerful way to represent and manipulate Go code structure.
  • You'll likely need to traverse the AST to find function declarations and function calls.
  • Handling variable name conflicts is crucial. Consider prefixing or renaming variables from the inlined function's scope to avoid clashes with variables in the calling scope.
  • For simplification, you might want to implement the "single call site" check by first identifying all calls to a particular function and then proceeding only if there's exactly one.
  • The go/printer package can be used to convert the modified AST back into Go source code.
Loading editor...
go