# Project export: Freak in the Sheets

This document was generated by HackStack to give an AI agent context about a hackathon project. Sections are labeled with their provenance; content marked as truncated was cut to keep this document small.

## Project metadata

- Hackathon: TreeHacks 2026
- Tagline: The hot new LLVM backend: compile your code to a spreadsheet.
- Devpost: https://devpost.com/software/freak-in-the-sheets-7jl542
- GitHub: https://github.com/kognise/freak-in-the-sheets/
- Demo: https://docs.google.com/spreadsheets/d/1Kvvak6EMyFYLsCufNyUjhO2Tyd9aIhJAbIxl6B9Mas8/edit?usp=sharing
- Video: https://www.youtube.com/embed/5Sb-QcEFHwg?enablejsapi=1&hl=en_US&rel=0&start=&version=3&wmode=transparent
- Result: winner (Most Technically Complex; [Human Capital] Human Capital Fellowship Prize ( $50k equity-free check per team member (up to $200k total)))
- Team: 3 GitHub contributor(s) — Thomas Breydo (13 commits), Lexi Mattick (12 commits), KevinL10 (1 commits)

## Devpost submission (written by the team)

### Overview

We wrote a compiler that can run any piece of software inside a spreadsheet. What???! LLVM is the world's most advanced compiler. It probably built the software you're using to read this. Under the hood, LLVM compiles a program using a "frontend" (converting C or Rust into an intermediate representation) and a "backend" (converting the intermediate representation into code that can be run on a specific computer). We invented a novel instruction set, 2D memory architecture, and encoding system that is able to execute arbitrary code within the limitations of a spreadsheet. We then wrote an LLVM backend that is able to output directly to that ISA, enabling arbitrary code in any programming language to be run in Google Sheets. The compiled source code is stored in memory alongside its data and executed in real time. How can you run arbitrary code in a spreadsheet? A spreadsheet formula is a pure function of some input cells to some output cells. An abstract CPU can also be thought of as, for each clock tick, a pure function from the previous state of a memory tape to the resulting new state. We abuse a Google Sheets feature called iterative calculation, designed to allow cells to contain self references to calculate higher precision values, to instead arbitrarily step forward our own virtual machine. Combining this with an unpatched backend validation exploit in Google Sheets, code can be run indefinitely without any user input (a key problem with some attempts at faking a CPU in a spreadsheet). Once you have a functioning bytecode interpreter in Google Sheets, all that's left is defining the bounds of your new ISA, writing a simple two-pass assembler, and building an LLVM backend from scratch to target that ISA. (Hint: this was actually 80% of the work.)

### Challenges we ran into

This whole thing was insanely hard and we didn't know if we could get it to work at any point until the morning of submission. Google Sheets formulas are hard to wrestle with, LLVM is an insanely complex codebase, and the scope of our ambition was big. One example of a fun challenge we faced was that of performance. Our programs were taking a very long time, and benchmarks revealed that the more Google Sheets cells the programs compiled to, the longer updates would take. So, we created a new method that can pack 16,666 32-bit integers into one horizontal cell instead of the previous 1, by taking advantage of Google Sheet's handling of Unicode (50,000 Unicode glyphs per cell, we can store 15 bits pet glyph, which gives us 3 glyphs per int and therefore 16,666 with exactly two chars left over for necessary surrounding brackets; we also have some extra room for pointer metadata and such if needed). This is why there are so many Chinese characters when you execute your code. That's the memory!

## README (from the GitHub repository)

*This repo contains about 10% of the work. For the other 90%, see our llvm & clang fork: https://github.com/KevinL10/llvm-project-treehacks/tree/fits*

# Freak in the Sheets

We wrote a compiler that can run any piece of software inside a spreadsheet.

```
clang --target=google-sheets main.c
```

## What???

LLVM is the world's most advanced compiler. It probably built the software you're using to read this. Under the hood, LLVM compiles a program using a "frontend" (converting C or Rust into an *intermediate representation*) and a "backend" (converting the intermediate representation into code that can be run on a specific computer).

We invented a novel instruction set, 2D memory architecture, and encoding system that is able to execute arbitrary code within the limitations of a spreadsheet. We then wrote an LLVM backend that is able to output directly to that ISA, enabling arbitrary code in **any programming language** to be run in Google Sheets.

The compiled source code is stored in memory alongside its data and executed in realtime.

![Pipeline](https://i.ibb.co/h1fHGYn8/Compilation-pipeline-1.png)

## How can you run arbitrary code in a spreadsheet?

A spreadsheet formula is a pure function of some input cells to some output cells. An abstract CPU can also be thought of as, for each clock tick, a pure function from the previous state of a memory tape to the resulting new state.

We abuse a Google Sheets feature called iterative calculation, designed to allow cells to contain self references to calculate higher precision values, to instead arbitrarily step forward our own virtual machine. Combining this with an unpatched backend validation exploit in Google Sheets, code can be run indefinitely without any user input (a key problem with some attempts at faking a CPU in a spreadsheet).

Once you have a functioning bytecode interpreter in Google Sheets, all that's left is defining the bounds of your new ISA, writing a simple two-pass assembler, and building an LLVM backend from scratch to target that ISA. (Hint: this was actually 80% of the work.)

## Challenges we ran into

This whole thing was insanely hard and we didn't know if we could get it to work at any point until the morning of submission. Google Sheets formulas are hard to wrestle with, LLVM is an insanely complex codebase, and the scope of our ambition was big.

One example of a fun challenge we faced was that of performance. Our programs were taking a very long time, and benchmarks revealed that the *more Google Sheets cells* the programs compiled to, the longer updates would take. So, we created a new method that can pack 16,666 32-bit integers into one horizontal cell instead of the previous 1, by taking advantage of Google Sheet's handling of Unicode (50,000 Unicode glyphs per cell, we can store 15 bits pet glyph, which gives us 3 glyphs per int and therefore 16,666 with exactly two chars left over for necessary surrounding brackets; we also have some extra room for pointer metadata and such if needed).

This is why there are so many Chinese characters when you execute your code. That's the memory!

![Diagram of speedup](https://i.ibb.co/JR74VGJz/Compilation-pipeline.png)


## Detected evidence (automated analysis)

Indexed codebase: 7 recognized source files, 16 KB.
- C (language) — detected in the code
- JavaScript (language) — detected in the code
- C++ (language) — claimed on Devpost, not found in the code

## Codebase structure (from repository index)

### Files (12 of 12)

```
.gitignore
build_full.sh
build.sh
bun.lock
demo/main.c
DOCS.txt
package.json
README.md
src/assembler.js
src/preview.lua
src/vm.lua
tsconfig.json
```

### Dependencies

- package.json: @types/bun@latest, typescript@^5

### Recent commits (newest first)

- Treehacks
- Add initial README content with repository info
- Initial demo
- Move demo
- Assembler usage message edit
- Cleanup
- Nice copy-paste auto sheet!
- Docs
- Docs and structure
- Push the fix
- feat: set
- Lexi fib back
- YAY! Fib works into unicode!
- output to *.sheet by default
- Allow --celly compilation
- User lexi's assembler/vm
- Merge branch 'main' into thomas-llvm-parser
- Pre cleanup
- Const instructions
- Enhance LLVM IR parsing and error reporting

## Key source files (fetched from GitHub, selected and truncated for size)

### package.json

```
{
  "name": "liveshare",
  "private": true,
  "scripts": {
    "asm": "bun src/assembler.js",
    "asm:auto": "bun assembler-auto/assembler.js",
    "fib:c2sheet": "poetry -C src/llvm-to-shasm run python -m llvm_to_shasm.cli src/llvm-to-shasm/tests/fixtures/fib.c --sheet-out out.sheet --bun-bin ${BUN_BIN:-bun}"
  },
  "devDependencies": {
    "@types/bun": "latest"
  },
  "peerDependencies": {
    "typescript": "^5"
  }
}
```

### build.sh

```shell
#!/usr/bin/env bash

if [ -z "$1" ]; then
    echo "Usage: $0 <path/to/file.c>"
    exit 1
fi

ASM_PATH=/Users/kognise/Downloads/liveshare
LLVM_PATH=/Users/kognise/Documents/Programming/github.com/llvm/llvm-project

echo "--- building clang"
cd $LLVM_PATH || exit
cmake --build build --target clang
cd $ASM_PATH || exit

echo "--- compiling c to asm"
$LLVM_PATH/build/bin/clang -S --target=fits -o code.s "$1" || exit

echo "--- compiling asm to sheets"
bun asm ./code.s ./out.sheet || exit
rm ./code.s

echo "--- emitted to out.sheet"
```

### build_full.sh

```shell
#!/usr/bin/env bash

if [ -z "$1" ]; then
    echo "Usage: $0 <path/to/file.c>"
    exit 1
fi

ASM_PATH=/Users/kognise/Downloads/liveshare
LLVM_PATH=/Users/kognise/Documents/Programming/github.com/llvm/llvm-project

echo "--- building llc"
cd $LLVM_PATH || exit
cmake --build build --target llc
cd $ASM_PATH || exit

echo "--- compiling c to llvm ir"
clang -S -emit-llvm -m32 -o ./code.ll "$1"

echo "--- compiling llvm ir to asm"
$LLVM_PATH/build/bin/llc -mtriple=fits ./code.ll || exit

echo "--- compiling asm to sheets"
bun asm ./code.s ./out.sheet || exit

echo "--- emitted to out.sheet"
```

### demo/main.c

```c
int n = 10;

int _start() {
    int out[50];

    // Calculate the nth fibonacci number
    out[0] = 0;
    out[1] = 1;
    for (int i = 2; i <= n; i++) {
        out[i] = out[i - 1] + out[i - 2];
    }

    // Return it (calling convention places it in r0)
    return out[n];
}

```

### src/preview.lua

```lua
=LET(
    x, NOW(),
    cell, E8,
    
    a_data, MID(cell, 2, 1),
    b_data, MID(cell, 3, 1),
    c_data, MID(cell, 4, 1),
    a, IF(a_data = "", 0, CODE(a_data) - 32),
    b, IF(b_data = "", 0, CODE(b_data) - 32),
    c, IF(c_data = "", 0, CODE(c_data) - 32),
    
    combined, BITOR(
        BITOR(
            BITLSHIFT(BITAND(a, 32767), 17), 
            BITLSHIFT(BITAND(b, 32767), 2)
        ),
        BITAND(c, 3)
    ),
    IF(combined >= 2^31, combined - 2^32, combined)
)

```

### src/assembler.js

```javascript
if (process.argv.length !== 4) {
    console.error(
        'Usage: bun assembler.js <asm-input> <sheet-output>',
    )
    process.exit(1)
}

const asmInput = process.argv[2]
const sheetOutput = process.argv[3]
const code = await Bun.file(asmInput).text()
const rawPreviewTemplate = await Bun.file(
    new URL('./preview.lua', import.meta.url),
).text()
const rawVmFormula = await Bun.file(
    new URL('./vm.lua', import.meta.url),
).text()

const ops = {
    lte: 0,
    lt: 14,
    add: 1,
    load: 2,
    load_a: 3,
    store: 4,
    store_a: 5,
    jmp0: 6,
    jmp0_a: 7,
    jmp: 8,
    jmp_a: 9,
    halt: 10,
    sub: 11,
    mul: 12,
    set: 13,
}

const out = [[0]]
const labelReferences = [{ label: '_start', x: 0, y: 0 }]
const labels = new Map()

function concretify(token, x) {
    const parsed = Number.parseInt(token, 10)
    if (Number.isNaN(parsed)) {
        labelReferences.push({ label: token, y: out.length, x })
        return 0
    }
    return parsed
}

for (const line of code.split('\n')) {
    const tokens = line.trim().split(/\s+/g).filter(Boolean)
    for (let i = 0; i < tokens.length; i++) {
        if (tokens[i].startsWith(';')) {
            tokens.splice(i)
            break
        }
    }
    if (tokens.length === 0) continue

    if (tokens[1] === '=') {
        const assignment = tokens
            .slice(2)
            .map((token, x) => concretify(token, x, out.length))
        if (labels.has(tokens[0]))
            throw new Error(`Label '${tokens[0]}' is already set`)
        labels.set(tokens[0], out.length - 1)
        out.push(assignment)
    } else if (tokens[0].endsWith(':')) {
        const label = tokens[0].slice(0, -1)
        if (labels.has(label)) throw new Error(`Label '${label}' is already set`)
        labels.set(label, out.length - 1)
    } else {
        const opcode = ops[tokens[0]]
        if (opcode === undefined) throw new Error(`Unknown op '${tokens[0]}'`)
        out.push([
            opcode,
            ...tokens
                .slice(1)
                .map((token, i) => concretify(token, i + 1, out.length)),
        ])
    }
}

for (const { label, y, x } of labelReferences) {
    if (!labels.has(label)) throw new Error(`Label '${label}' not found`)
    out[y][x] = labels.get(label)
}

function encodeLine(line) {
    const cells = []
    for (let i = 0; i < line.length; i += 16666) {
        const chunk = line.slice(i, i + 16666)
        const encodedCell = chunk
            .map((val) => {
                const unsigned = val >>> 0
                const a = (unsigned >>> 17) & 0x7fff
                const b = (unsigned >>> 2) & 0x7fff
                const c = unsigned & 0x3

                return (
                    String.fromCharCode(a + 32) +
                    String.fromCharCode(b + 32) +
                    String.fromCharCode(c + 32)
                )
            })
            .join('')
        cells.push(`[${encodedCell}]`)
    }
    return cells
}

function toSingleLineFormula(formula) {
    return formula.replace(/\r?\n\s*/g, ' ').trim()
}

const previewTemplate = toSingleLineFormula(rawPreviewTemplate)
const vmFormula = toSingleLineFormula(rawVmFormula)

function previewFormulaForRow(row) {
    return previewTemplate.replace(/\bE8\b/g, `E${row}`)
}

const encodedRows = out.map(encodeLine)
const START_ROW = 5
const END_ROW = 101
const COLUMN_COUNT = 7
const sheetRows = []

for (let row = 1; row <= END_ROW; row++) {
    const cells = Array(COLUMN_COUNT).fill('')

    if (row === 1) {
        cells[0] = 'Run:'
        cells[1] = '0'
    } else if (row === 2) {
        cells[0] = 'Output:'
        cells[1] = `=IFERROR(D7, "")`
    } else if (row === 4) {
        cells[1] = 'Initial'
        cells[3] = 'Preview'
        cells[4] = 'Memory'
    } else if (row >= START_ROW) {
        const matrixRow = row - START_ROW
        const encoded = encodedRows[matrixRow] ?? []

        cells[0] = matrixRow === 0 ? 'PC' : String(matrixRow - 1)
        cells[1] = encoded[0] ?? '[     ]'
        cells[2] = encoded.slice(1).join('')
        cells[3] = previewFormulaForRow(row)
        if (row === START_ROW) cells[4] = vmFormula
    }

    sheetRows.push(cells.join('\t'))
}

await Bun.file(sheetOutput).write(sheetRows.join('\n'))
console.log(':)')

```

### src/vm.lua

```lua
=ARRAYFORMULA(IF(
    $B$1 = 0,
    B5:C101,
    IF(
        REGEXMATCH(TO_TEXT(E8), "^ERROR: "),
        E8,
        LET(
            matrix, E5:G101,

            rewrite_col, LAMBDA(matrix, row, col, new_value, LET(
                real_col, FLOOR(col / 16666),
                byte_start, MOD(col, 16666) * 3 + 1,
                unsigned_val, IF(new_value < 0, new_value + 2^32, new_value),

                a, BITAND(BITRSHIFT(unsigned_val, 17), 32767),
                b, BITAND(BITRSHIFT(unsigned_val, 2), 32767),
                c, BITAND(unsigned_val, 3),
                encoded_chunk, CHAR(a + 32) & CHAR(b + 32) & CHAR(c + 32),

                MAKEARRAY(
                    ROWS(matrix),
                    COLUMNS(matrix),
                    LAMBDA(r, c,
                        IF(AND(r = row + 2, c = real_col + 1),
                            LET(
                                current_str, INDEX(matrix, r, c),
                                current_len, LEN(current_str),
                                padded_str, IF(current_len < byte_start, 
                                            current_str & REPT(CHAR(32), byte_start - current_len), 
                                            current_str),
                                REPLACE(padded_str, byte_start + 1, 3, encoded_chunk)
                            ),
                            INDEX(matrix, r, c)
                        )
                    )
                )
            )),
            deref_internal_col, LAMBDA(matrix, row, col, LET(
                real_col, FLOOR(col / 16666),
                byte_start, MOD(col, 16666) * 3 + 1,
                cell, INDEX(matrix, row + 2, real_col + 1),
                
                a_data, MID(cell, byte_start + 1, 1),
                b_data, MID(cell, byte_start + 2, 1),
                c_data, MID(cell, byte_start + 3, 1),
                a, IF(a_data = "", 0, CODE(a_data) - 32),
                b, IF(b_data = "", 0, CODE(b_data) - 32),
                c, IF(c_data = "", 0, CODE(c_data) - 32),
                
                combined, BITOR(
                    BITOR(
                        BITLSHIFT(BITAND(a, 32767), 17), 
                        BITLSHIFT(BITAND(b, 32767), 2)
                    ),
                    BITAND(c, 3)
                ),
                IF(combined >= 2^31, combined - 2^32, combined)
            )),
            
            rewrite, LAMBDA(matrix, row, new_value, rewrite_col(matrix, row, 0, new_value)),
            deref_col, LAMBDA(row, col, deref_internal_col(matrix, row, col)),
            deref, LAMBDA(row, deref_col(row, 0)),

            pc, deref(-1),
            pcm, rewrite(matrix, -1, pc + 1),
            arg, LAMBDA(i, deref_col(pc, i)),
            operation, arg(0),

            op_lte,     0,
            op_lt,      14,
            op_add,     1,
            op_sub,     11,
            op_mul,     12,
            op_set,     13,
            op_load,    2,
            op_load_a,  3,
            op_store,   4,
            op_store_a, 5,
            op_jmp0,    6,
            op_jmp0_a,  7,
            op_jmp,     8,
            op_jmp_a,   9,
            op_halt,    10,
            
            IF(operation = op_lte,
                LET(
                    out, arg(1),
                    a, arg(2),
                    b, arg(3),
                    new_value, IF(deref(a) <= deref(b), 1, 0),
                    rewrite(pcm, out, new_value)
                ),
            IF(operation = op_lt,
                LET(
                    out, arg(1),
                    a, arg(2),
                    b, arg(3),
                    new_value, IF(deref(a) < deref(b), 1, 0),
                    rewrite(pcm, out, new_value)
                ),
            IF(operation = op_add,
                LET(
                    out, arg(1),
                    a, arg(2),
                    b, arg(3),
                    new_value, deref(a) + deref(b),
                    rewrite(pcm, out, new_value)
                ),
            IF(operation = op_sub,
                LET(
                    out, arg(1),
                    a, arg(2),
                    b, arg(3),
                    new_value, deref(a) - deref(b),
                    rewrite(pcm, out, new_value)
                ),
            IF(operation = op_mul,
                LET(
                    out, arg(1),
                    a, arg(2),
                    b, arg(3),
                    new_value, deref(a) * deref(b),
                    rewrite(pcm, out, new_value)
                ),
            IF(operation = op_load,
                LET(
                    out, arg(1),
                    row, arg(2),
                    col, arg(3),
                    new_value, deref_col(row, deref(col)),
                    rewrite(pcm, out, new_value)
                ),
            IF(operation = op_load_a,
                LET(
                    out, arg(1),
                    row, arg(2),
                    col, arg(3),
                    new_value, deref_col(deref(row), deref(col)),
                    rewrite(pcm, out, new_value)
                ),
            IF(operation = op_store,
                LET(
                    row, arg(1),
                    col, arg(2),
                    in, arg(3),
                    rewrite_col(pcm, row, deref(col), deref(in))
                ),
            IF(operation = op_store_a,
                LET(
                    row, arg(1),
                    col, arg(2),
                    in, arg(3),
                    rewrite_col(pcm, deref(row), deref(col), deref(in))
                ),
            IF(operation = op_jmp0,
                LET(
                    data, arg(1),
                    dest, arg(2),
                    IF(deref(data) = 0, rewrite(matrix, -1, dest), pcm)
                ),
            IF(operation = op_jmp0_a,
                LET(
                    data, arg(1),
                    dest, arg(2),
          
[truncated — 747 more characters]
```