<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>Bitrig Blog</title>
    <description>Thoughts and updates from our team</description>
    <link>https://bitrig.com/blog</link>
    <atom:link href="https://bitrig.com/api/rss" rel="self" type="application/rss+xml" />
    <language>en-US</language>
    <lastBuildDate>Sat, 07 Mar 2026 00:25:27 GMT</lastBuildDate>
    
    <item>
      <title><![CDATA[Meet the new Bitrig Mac app]]></title>
      <description><![CDATA[Bitrig is now available for the Mac!]]></description>
      <content:encoded><![CDATA[<p>Bitrig is now available for the Mac!</p>
<p>From the engineers behind SwiftUI, Bitrig is a vibe coding app purpose-built for the Apple ecosystem. It writes native Swift, the same way a professional iOS developer would.</p>
<p><img src="/assets/hero-screenshot.jpg" alt="Meet the new Bitrig Mac app"></p>
<p>Since we launched Bitrig last summer, people have been asking for a Mac app. Bitrig for Mac lets you take advantage of a larger screen, full hardware keyboard, and all of the powerful technologies that come bundled with Xcode.</p>
<p>Bitrig for Mac compiles your code using the complete Xcode toolchain, so you have access to every framework in the iOS SDK. And it displays an inline preview of your app powered by an Xcode iPhone Simulator, so what you see matches what your users will get on TestFlight and the App Store.</p>
<p>The new Mac app also:</p>
<ul>
<li>Lets you run your app on a connected iPhone with one click</li>
<li>Helps you quickly find and fix build errors</li>
<li>Includes all the core features of the iPhone app, like TestFlight and App Store distribution</li>
</ul>
<p>There’s no extra charge to use Bitrig Mac. Your subscription and credits transfer between apps, and your projects sync automatically.</p>
<p>One early user said, “It didn’t take long for me to switch to Mac for 90% of my iterative refinement, and just think of the iPhone app as a sketchbook for ideas.”</p>
<p>Ready to get started? <a href="https://bitrig.com/download">Download the app</a>, then follow <a href="https://www.youtube.com/watch?v=nCrhhaz1_sQ">this setup tutorial</a>. Want to learn more? Watch <a href="https://www.youtube.com/watch?v=TxhqBd3_UWc">this tour</a> of Bitrig for Mac.</p>
]]></content:encoded>
      <link>https://bitrig.com/blog/meet-bitrig-for-mac</link>
      <guid>https://bitrig.com/blog/meet-bitrig-for-mac</guid>
      <pubDate>Thu, 05 Mar 2026 00:00:00 GMT</pubDate>
    </item>
    <item>
      <title><![CDATA[App Showcase: Urban Fieldwork]]></title>
      <description><![CDATA[Meet Rob, a geography teacher and subject matter expert with three apps on the App Store.]]></description>
      <content:encoded><![CDATA[<p>A British geography teacher, a father of three, and a football enthusiast, Rob can add one more line item to his biography: he’s a three-time app developer thanks to Bitrig.</p>
<p>Rob has never been short on app ideas; he just lacked the tools to bring them to life. “I am always looking for ways to use technology to help myself and colleagues work more effectively,” he told us. “I’ve always wanted to make apps for iPhone/iPad, etc., but do not have the coding ability to do this.” Xcode was another barrier. With all its complexities, even if he learned to write the code, he was worried about using it to distribute apps.</p>
<p>When Rob found a YouTube video demonstrating Bitrig, his interest was piqued. As a longtime Apple fan, finding out that the Bitrig team is made up of Apple alumni sealed the deal. “Bitrig allowed me to build apps using natural language and continuously iterate them until I was ready to share,” he explained. “It’s really simple to use, <a href="https://www.youtube.com/@BitrigApp">and the training videos</a> … were really helpful in building the app and getting it ready to share on App Store Connect. When I reached out to the Bitrig team for some guidance, they were really helpful and friendly.” (Thanks Rob!!)</p>
<p>As a geographer and educator, Rob is uniquely positioned to create an app that will help students engage and learn using tools that feel natural. His app <a href="https://apps.apple.com/us/app/urban-fieldwork/id6754923041">Urban Fieldwork</a> builds on the capabilities of the iPhone, turning a device that students already carry into a sophisticated fieldwork companion. It uses GPS to record site location, the microphone to measure sound levels, and the embedded camera to capture images. It even uses Apple’s mapping technologies to identify the category of businesses proximate to the fieldwork.</p>
<p>On supported devices, the app leans on Apple Intelligence for deeper, on-device analysis, helping students interpret their data. And when the work in the field is finished, everything can be exported in a clean PDF report or a spreadsheet for further study.</p>
<p>Urban Fieldwork has been incredibly helpful for both his students and his fellow educators. The cherry on top? “My students are definitely impressed when I show them that I’ve made an app that they can download.”</p>
<p>In addition to Urban Fieldwork, Rob has published <a href="https://apps.apple.com/us/app/send-toolkit/id6754189843">SEND ToolKit</a>, which gives teachers quick, practical guidance on supporting students with special educational needs and disabilities, all in one easy-to-access place. He also made the app <a href="https://apps.apple.com/us/app/darbys-story-generator/id6754754484">Darby’s Story</a> which uses Apple Foundation Models to generate silly bedtime stories with his son as the main character.</p>
<p>Rob is a fantastic example of how everyone with an idea can now make a fully native app, and we’re so lucky to have him as part of the Bitrig community. His advice? “If you have an idea for an app but lack the traditional expertise to develop an app, give Bitrig a go. Enjoy the learning process…. Have fun with it.”</p>
]]></content:encoded>
      <link>https://bitrig.com/blog/app-showcase-urban-fieldwork</link>
      <guid>https://bitrig.com/blog/app-showcase-urban-fieldwork</guid>
      <pubDate>Fri, 05 Dec 2025 00:00:00 GMT</pubDate>
      <enclosure url="https://bitrig.com/assets/blog/app-showcase-urban-fieldwork.png" type="image/png" length="0" />
    </item>
    <item>
      <title><![CDATA[App Showcase: Black Lens]]></title>
      <description><![CDATA[Find out how one developer used Bitrig to make a selfie app for blind and low-vision users.]]></description>
      <content:encoded><![CDATA[<p>Project Manager by day, first-ever blind reality-show contestant, and now an app developer! Meet Steve.</p>
<p>Steve has always been excited to make apps, but without a technical background, the path wasn’t easy. Steve has about 10% vision, meaning he can typically see one or two letters on a screen at a time. Traditional development tools just aren’t made with someone like Steve in mind. And then he chanced across Bitrig.</p>
<p>Because Bitrig is fully accessible, Steve was able to hit the ground running. Dictation makes it easy to work with the AI, VoiceOver reads out the transcript, and the entire interface is designed to be navigable with assistive technology. Like SwiftUI, Bitrig was built with accessibility in mind, ensuring that everyone can create great apps that are themselves accessible.</p>
<p>Thanks to his passion and determination, and Bitrig’s approachable stack, Steve is now a bona fide app developer! His first app, <a href="https://apps.apple.com/gb/app/black-lens/id6754779190">Black Lens</a>, which helps people with low-vision take great selfies, is available now on the App Store. “As a blind person, everything is difficult,” Steve told us. “It’s like walking around with a thorn in your paw. Bitrig is like pulling out that thorn.”</p>
<p>This isn’t the first time Steve has blazed a trail. He recently appeared on a reality TV show near his UK home, becoming the first blind contestant to compete. In Hunted, Series 8, he embodied the role of a fugitive, attempting to outsmart the authorities. This is a #nospoiler zone, but you can <a href="https://www.channel4.com/press/press-pack/hunted-series-8-press-pack">read all about Steve’s entertaining descent</a> into a life of crime.</p>
<p>Steve’s journey as a developer is only beginning. Keep an eye on the App Store for more of his apps, coming soon.</p>
]]></content:encoded>
      <link>https://bitrig.com/blog/app-showcase-black-lens</link>
      <guid>https://bitrig.com/blog/app-showcase-black-lens</guid>
      <pubDate>Mon, 01 Dec 2025 00:00:00 GMT</pubDate>
      <enclosure url="https://bitrig.com/assets/blog/app-showcase-black-lens.jpeg" type="image/jpeg" length="0" />
    </item>
    <item>
      <title><![CDATA[Bitrig’s Swift Interpreter: From Expressions to APIs]]></title>
      <description><![CDATA[How Bitrig’s interpreter represents expressions and extracts APIs from the SDK.]]></description>
      <content:encoded><![CDATA[<p><a href="https://apps.apple.com/us/app/bitrig/id6747835910">Bitrig</a> dynamically generates and runs Swift apps right on your phone. Normally, running Swift code would require compiling and signing with Xcode, which you can’t do on an iPhone. One of the most common questions we’re asked is: <em>how does this work</em>?</p>
<p>Welcome to Part 3 in our series on how we built Bitrig&#39;s Swift interpreter. <a href="/blog/swift-interpreter">In Part 1</a>, we introduced the core idea: interpreting Swift from Swift itself and dynamically bridging into existing APIs. <a href="/blog/interpreter-bytecode">In Part 2</a>, we covered the mechanics of converting Swift code to bytecode. In this post, we&#39;ll explain how complex expressions are evaluated at runtime and how they connect to real system framework APIs.</p>
<hr>
<h2>Representing Methods</h2>
<p>Our interpreter uses a <a href="https://en.wikipedia.org/wiki/Stack_machine">stack machine</a> to store intermediate values while evaluating expressions. We added the value stack to our interpreter last time, but we didn’t examine how it’s used.</p>
<p>Consider the expression <code>&quot;Hello &quot; + name.uppercased()</code>. It requires multiple evaluations that need to be combined. In pseudo-code:</p>
<pre><code class="language-swift">valueStack.push(evaluate(name))
valueStack.push(evaluate(valueStack.pop().uppercased()))
valueStack.push(evaluate(&quot;Hello &quot;))
valueStack.push(evaluate(valueStack.pop() + valueStack.pop()))
</code></pre>
<p>The stack provides a uniform place to store any number of intermediate values for any kind of expression.</p>
<p>Notice how the final push isn’t popped? That’s the function’s return value.</p>
<hr>
<p>Next, let’s look at how we evaluate more complex expressions like <code>name.uppercased()</code>.</p>
<p>The first step is to add instructions for encoding this into the bytecode stream:</p>
<pre><code class="language-swift">extension [Bytecode] {
    mutating func appendMethodCall(
        base: ExprSyntax, method: DeclReferenceExprSyntax, arguments: LabeledExprListSyntax
    ) {
        base.appendBytecode(&amp;self)
        appendMethodName(method.baseName.text)
        var argumentCode: [Bytecode] = []
        for argument in arguments {
            argument.expression.appendBytecode(&amp;argumentCode)
        }
        appendInt(arguments.count)
        append(contentsOf: argumentCode)
    }
}
</code></pre>
<p>From <a href="/blog/interpreter-bytecode">Part 2</a>, we already have functions for encoding expressions. Those functions can be used here to encode the base expression, the number of arguments, and each argument expression.</p>
<p>To implement <code>appendMethodName</code>, we assign a unique integer to every method name in the SDK and encode that into the bytestream. For example:</p>
<pre><code class="language-swift">extension [Bytecode] {
    mutating func appendMethodName(_ name: String) {
        switch name {
        case &quot;lowercased&quot;: appendInt(0)
        case &quot;uppercased&quot;: appendInt(1)
        case &quot;contains&quot;: appendInt(2)
        ...
        default: break
        }
    }
}
</code></pre>
<hr>
<h2>Evaluating Methods</h2>
<p>To evaluate a method call, the interpreter first needs to decode its components from the bytestream:</p>
<pre><code class="language-swift">extension Interpreter {
    func beginMethodCall() {
        guard let base = pop() else { return }
        let method = nextSymbol()
        let argumentCount = nextInt()
        var arguments: [Argument] = []
        arguments.reserveCapacity(argumentCount)
        for _ in 0..&lt;argumentCount {
            if let arg = pop() {
                arguments.append(Argument(value: arg))
            }
        }

        evaluateMethodCall(base: base, method: method, arguments: arguments)
    }
}
</code></pre>
<p>Then it can call into the corresponding framework API:</p>
<pre><code class="language-swift">func evaluateMethodCall(base: InterpreterValue, method: Int, arguments: [Argument]) -&gt; Any? {
    switch method {
        case 0: // lowercased
            if let value = base.stringValue {
                return value.lowercased()
            } else if let value = base.characterValue {
                return value.lowercased()
            }
        case 1: // uppercased
            if let value = base.stringValue {
                return value.uppercased()
            } else if let value = base.characterValue {
                return value.uppercased()
            }
        case 2: // contains
            if let value = base.stringValue {
                return value.contains(arguments.first?.value.stringValue ?? &quot;&quot;)
            }
        ...
        default: break
    }
    return nil
}
</code></pre>
<p>This is similar to the implementation of <code>evaluateInitializer</code> from <a href="/blog/swift-interpreter">Part 1</a>. The interpreter switches over the integers assigned to each method name and checks each type that has a method of the given name.</p>
<p>(One detail we’ve glossed over: arguments can have labels as well. Those can be handled by encoding the labels and pushing or popping them on a <code>labelStack</code> just like the <code>valueStack</code>.)</p>
<hr>
<h2>Sidenote: Operators</h2>
<p>Operators are represented and evaluated similarly to methods:</p>
<ul>
<li>Each operator name is assigned a unique integer</li>
<li>The left-hand and right-hand sides are encoded and decoded the same way as the base and argument expressions in a method call</li>
<li>The call into the corresponding framework API is evaluated by switching over the operator and checking the type</li>
</ul>
<p>The only added complexity is operator precedence. For example, with <code>x + y * z</code> the interpreter must evaluate <code>*</code> before <code>+</code>. Fortunately, SwiftSyntax includes a framework that solves this!</p>
<p><a href="https://swiftpackageindex.com/swiftlang/swift-syntax/602.0.0/documentation/SwiftOperators">SwiftOperators</a> can fold a flat sequence of operators into a structured tree that respects the correct precedence:</p>
<pre><code class="language-swift">let rawFile = Parser.parse(source: code)
let processedFile = try operators.foldAll(parser)
</code></pre>
<p>Once folded, operators can be handled the same way as methods.</p>
<hr>
<h2>Extracting APIs</h2>
<p>We extract the APIs the interpreter calls from the SDK’s <code>.swiftinterface</code> files. These files are valid Swift code, which means we can parse them directly with SwiftSyntax. Normally, we use SwiftSyntax to convert dynamically generated code into bytecode, but here we use it to preprocess the SDK itself.</p>
<p>A script processes each framework’s <code>.swiftinterface</code>, iterates through its declarations, and generates code:</p>
<ul>
<li>For each type, its initializers go into <code>evaluateInitializer</code>.</li>
<li>For each method, a case is added to <code>evaluateMethodCall</code> and to the lookup table used by <code>appendMethodName</code>.</li>
</ul>
<p>This approach also works for Objective-C frameworks because Xcode can emit a Swift version of an Objective-C framework’s interface. Since many iOS frameworks are still Objective-C under the hood, this greatly expands what the interpreter can access.</p>
<hr>
<p>Over the course of this series, we’ve gone from the big picture down to the nuts and bolts: starting with the idea of interpreting Swift in Swift, exploring how source code is translated into bytecode, and finally showing how expressions and APIs are resolved so real Swift code can run. Each piece—parsing, bytecode execution, and API extraction—fits together into a system that makes it possible to run Swift apps instantly, without compiling.</p>
<p>What I find most exciting about the interpreter are the possibilities that come from treating Swift not just as a compiled language, but as a dynamic, interpretable one. I hope this peek behind the curtain inspires you to imagine new ways of building with Swift.</p>
<hr>
<p>Follow our updates here and on <a href="https://linktr.ee/bitrig">social media</a>, and if you haven’t yet, give <a href="https://apps.apple.com/us/app/bitrig/id6747835910">Bitrig</a> a try, it’s the easiest way to see this in action.</p>
<p><em>Did you like learning about how Bitrig&#39;s interpreter works? Want to see it up close? <a href="/jobs">We&#39;re hiring: join us!</a></em></p>
]]></content:encoded>
      <link>https://bitrig.com/blog/interpreter-expressions</link>
      <guid>https://bitrig.com/blog/interpreter-expressions</guid>
      <pubDate>Thu, 09 Oct 2025 00:00:00 GMT</pubDate>
    </item>
    <item>
      <title><![CDATA[Bitrig’s Swift Interpreter: From Code to Bytecode]]></title>
      <description><![CDATA[How Bitrig's interpreter converts Swift constructs into bytecode.]]></description>
      <content:encoded><![CDATA[<p><a href="https://apps.apple.com/us/app/bitrig/id6747835910">Bitrig</a> dynamically generates and runs Swift apps on your phone. Normally this would require compiling and signing with Xcode, and you can’t do that on an iPhone.</p>
<p>To make it possible to instantly run your app, we built a Swift interpreter. But it’s an unusual interpreter, since it interprets from Swift… to Swift. One of the top questions we’ve gotten is how it’s implemented, so we wanted to share more about how it works.</p>
<p>Welcome to Part 2 in our series about how we built Bitrig&#39;s Swift interpreter. <a href="/blog/swift-interpreter">In Part 1</a>, we talked about the high-level idea: interpreting Swift from Swift itself, and how we can dynamically bridge into existing APIs. But how do we actually go from a Swift file on disk to something runnable inside our interpreter?</p>
<p>The short answer: we build our own bytecode.</p>
<p>Bytecode and the techniques we&#39;re using aren&#39;t novel in the world of interpreters. But they were new to us and it was not obvious exactly how to build such a system in Swift for Swift code. If you&#39;re curious to learn how we did it, then read on!</p>
<hr>
<h2>From Source to Structure</h2>
<p>The syntax tree that we get from SwiftSyntax includes mainly three types of elements: <a href="https://docs.swift.org/swift-book/documentation/the-swift-programming-language/declarations">declarations</a>, <a href="https://docs.swift.org/swift-book/documentation/the-swift-programming-language/statements">statements</a>, and <a href="https://docs.swift.org/swift-book/documentation/the-swift-programming-language/expressions">expressions</a>. If you&#39;d like a refresher on the details, check out the links. But in short:</p>
<ul>
<li><strong>Declarations</strong> define things like types, functions, and variables.</li>
<li><strong>Statements</strong> describe what happens, step by step.</li>
<li><strong>Expressions</strong> are the pieces of code that actually produce values.</li>
</ul>
<p>Declarations are what we start with, since those are the outer-most types within a Swift code document. They give us the types and functions we need, but they don’t actually <em>do</em> anything on their own. To actually start running some code, we have to take a function and interpret the array of statements that it contains.</p>
<p>To run those statements, we could walk the SwiftSyntax tree directly, but that can be slow and memory-intensive. Instead, we start by translating all of those statements into a compact intermediate bytecode.</p>
<p><a href="https://en.wikipedia.org/wiki/Bytecode">Bytecode</a> is just a stream of byte-sized instructions. Instead of a big, heterogeneous tree with dozens of node types, we get a flat, efficient representation that the interpreter can quickly step through.</p>
<p>We generate our bytecode by doing a light-weight compile of the syntax tree, where we make a runtime representation of each declaration. This representation includes an array of bytecode values for each function or computed variable it contains.</p>
<hr>
<h2>Generating Bytecode from Statements</h2>
<p>Let’s explore how we map statements to bytecode. To start, we&#39;ll create an <code>Operation</code> type that represents the different types of operations we&#39;ll need the interpreter to support. These will be close to, but not 1-to-1 with, the Swift language. We can start with a few of the simpler statement types like <code>break</code> and <code>continue</code>. We&#39;ll encode each as a single byte:</p>
<pre><code class="language-swift">enum Operation: UInt8 {
    case `break`
    case `continue`
    ...
}
</code></pre>
<p>Our bytecode will need additional data besides just the operations themselves (e.g. the arguments to the operations), so let&#39;s introduce a more general type for the bytecode itself:</p>
<pre><code class="language-swift">struct Bytecode: RawRepresentable {
    var rawValue: UInt8

    init(_ operation: Operation) {
        rawValue = operation.rawValue
    }

    init(_ value: UInt8) {
        rawValue = value
    }
}
</code></pre>
<p>Then we can add a method to go from a SwiftSyntax statement to creating bytecode:</p>
<pre><code class="language-swift">extension StmtSyntax {
    func appendBytecode(_ bytecode: inout [Bytecode]) {
        if kind == .breakStmt {
            bytecode.append(Bytecode(.break))
        } else if kind == .continueStmt {
            bytecode.append(Bytecode(.continue))
        } else if let statement = self.as(ExpressionStmtSyntax.self) {
            statement.expression.appendBytecode(&amp;bytecode)
        ...
        }
    }
}
</code></pre>
<p>Now we can create bytecode from a function by pulling out its body&#39;s statements:</p>
<pre><code class="language-swift">func createBytecode(function: FunctionDeclSyntax) -&gt; [Bytecode] {
    var bytecode: [Bytecode] = []
    let statements = function.body?.statements
    statements?.appendBytecode(&amp;bytecode)
    return bytecode
}
</code></pre>
<p>When the interpreter runs, it can just loop over the bytecode and switch on each operation type, which we&#39;ll examine in a bit.</p>
<hr>
<h2>Expressions</h2>
<p>Statements are the necessary structure, but expressions are where the real action happens. Every Swift program boils down to evaluating expressions: literals, operations, function calls, member accesses, and so on. So next, we&#39;ll discuss converting expressions into bytecode.</p>
<p>Let’s start with the simplest expressions: literals.</p>
<p>An integer literal like <code>42</code> can be represented directly in the bytecode. A byte can easily represent a number, as a <code>UInt8</code>. To be able to represent the full 64-bit space of integer values, we&#39;ll need to combine together 8 of these <code>UInt8</code>s to represent all of the necessary bits. Since this means we&#39;ll need multiple bytes, we can&#39;t express this as an initializer on <code>Bytecode</code>, so instead we&#39;ll make an append-style method on arrays of <code>Bytecode</code>:</p>
<pre><code class="language-swift">extension [Bytecode] {
    mutating func appendUInt64(_ value: UInt64) {
        // Represent the value in 8 bytes worth:
        append(Bytecode(rawValue: UInt8((value &gt;&gt; 56) &amp; 0xFF)))
        append(Bytecode(rawValue: UInt8((value &gt;&gt; 48) &amp; 0xFF)))
        append(Bytecode(rawValue: UInt8((value &gt;&gt; 40) &amp; 0xFF)))
        append(Bytecode(rawValue: UInt8((value &gt;&gt; 32) &amp; 0xFF)))
        append(Bytecode(rawValue: UInt8((value &gt;&gt; 24) &amp; 0xFF)))
        append(Bytecode(rawValue: UInt8((value &gt;&gt; 16) &amp; 0xFF)))
        append(Bytecode(rawValue: UInt8((value &gt;&gt; 8) &amp; 0xFF)))
        append(Bytecode(rawValue: UInt8(value &amp; 0xFF)))
    }
}
</code></pre>
<p>(Note: It&#39;s possible to optimize this more for common small integers by representing small integers within a single byte.)</p>
<p>With the ability to write a full 64-bit value, we can easily write <code>Int</code>s and <code>Double</code>s into the bytecode array by just converting them into a <code>UInt64</code>:</p>
<pre><code class="language-swift">extension [Bytecode] {
    mutating func appendInt(_ value: Int) {
        appendUInt64(UInt64(bitPattern: Int64(value)))
    }

    mutating func appendDouble(_ value: Double) {
        appendFullUInt64(value.bitPattern)
    }
}
</code></pre>
<p>With that, we have all the pieces we need to create the representations for some literal types:</p>
<pre><code class="language-swift">extension ExprSyntax {
    func appendBytecode(_ bytecode: inout [Bytecode]) {
        if kind == .nilLiteralExpr {
            bytecode.append(Bytecode(.nilLiteral))
        } else if let value = self.as(IntegerLiteralExprSyntax.self) {
            let number = Int(value.literal.text) ?? 0
            bytecode.append(Bytecode(.integerLiteral))
            bytecode.appendInt(value)
        } else if let value = self.as(FloatLiteralExprSyntax.self) {
            let number = Double(value.literal.text) ?? 0.0
            bytecode.append(Bytecode(.doubleLiteral))
            bytecode.appendDouble(number)
        }
    }
}
</code></pre>
<hr>
<h2>Interpreting the Bytecode</h2>
<p>Now that we have our elements built up, we can write a basic interpreter to process them. First, we&#39;ll create the interpreter type itself, which will contain an array of bytecode to interpret. Then, we&#39;ll incorporate a program counter that tells us where in the bytecode we are. Finally, we&#39;ll make a stack where we can push and pop values:</p>
<pre><code class="language-swift">struct Interpreter {
    let bytecode: [Bytecode]
    private var pc = 0
    private var valueStack: [InterpreterValue] = []
}
</code></pre>
<p>The values on the stack are just the runtime values we discussed <a href="/blog/swift-interpreter">last time</a>.</p>
<p>Then we can make a method to loop over the bytecode, switch over the different operation types, and process them. For the literals, we push the value they create onto our stack.</p>
<pre><code class="language-swift">extension Interpreter {
    mutating func evaluateOperations() -&gt; InterpreterResult {
        while let operation = nextOperation() {
            let result = evaluateOperation(operation)
            if result != .normal {
                return result
            }
        }
        return .normal
    }

    mutating func evaluateOperation(_ operation: Operation) -&gt; InterpreterResult {
        switch operation {
        case .break:
            return .break
        case .continue:
            return .continue
        case .nilLiteral:
            valueStack.append(.nil)
            return .normal
        case .integerLiteral:
            valueStack.append(.native(nextInt()))
            return .normal
        case .doubleLiteral:
            valueStack.append(.native(nextDouble()))
            return .normal
        ...
    }
}
</code></pre>
<p>The result we return is just metadata about whether execution ended in any unusual way, which we need for loops, function calls, etc.</p>
<p>The final piece we need for the interpreter is a way to extract the necessary values from the bytecode array. This is effectively the inverse operation of what we did earlier to encode those values:</p>
<pre><code class="language-swift">extension Interpreter {
    mutating func nextOperation() -&gt; Operation? {
        let result = pc &lt; bytecode.count ? bytecode[pc] : nil
        pc += 1
        return result.flatMap { .init(rawValue: $0.rawValue) }
    }

    mutating func nextInt() -&gt; Int {
        Int(Int64(bitPattern: nextUInt64()))
    }

    mutating func nextDouble() -&gt; Double {
        guard let value = nextUInt64() else { return 0.0 }
        return Double(bitPattern: value)
    }

    mutating func nextUInt64() -&gt; UInt64 {
        if pc &gt;= bytecode.count - 7 {
            print(&quot;ERROR: Attempt to read past bytecode&quot;)
            return nil
        }
        let result = UInt64(bytecode[pc].rawValue) &lt;&lt; 56 |
            UInt64(bytecode[pc + 1].rawValue) &lt;&lt; 48 |
            UInt64(bytecode[pc + 2].rawValue) &lt;&lt; 40 |
            UInt64(bytecode[pc + 3].rawValue) &lt;&lt; 32 |
            UInt64(bytecode[pc + 4].rawValue) &lt;&lt; 24 |
            UInt64(bytecode[pc + 5].rawValue) &lt;&lt; 16 |
            UInt64(bytecode[pc + 6].rawValue) &lt;&lt; 8 |
            UInt64(bytecode[pc + 7].rawValue)
        pc += 8
        return result
    }
}
</code></pre>
<p>And now we have a working (but limited) bytecode interpreter!</p>
<hr>
<h2>What’s Next</h2>
<p>So far we&#39;ve covered how to get from source to basic, runnable bytecode. Next time, we&#39;ll look at encoding and evaluating more advanced expressions like function calls and initializers and how that ties into calling framework APIs that we saw <a href="/blog/swift-interpreter">previously</a>.</p>
<p>Follow our updates here and on <a href="https://linktr.ee/bitrig">social media</a>, and if you haven’t yet, give <a href="https://apps.apple.com/us/app/bitrig/id6747835910">Bitrig</a> a try, it’s the easiest way to see this in action.</p>
<p><em>Did you like learning about how Bitrig&#39;s interpreter works? Want to see it up close? <a href="/jobs">We&#39;re hiring: join us!</a></em></p>
]]></content:encoded>
      <link>https://bitrig.com/blog/interpreter-bytecode</link>
      <guid>https://bitrig.com/blog/interpreter-bytecode</guid>
      <pubDate>Tue, 16 Sep 2025 00:00:00 GMT</pubDate>
    </item>
    <item>
      <title><![CDATA[Bitrig’s Swift Interpreter: Building an interpreter for Swift in Swift]]></title>
      <description><![CDATA[Bitrig uses a Swift interpreter to render apps. How does it work?]]></description>
      <content:encoded><![CDATA[<p>Bitrig dynamically generates and runs Swift apps on your phone. Normally this would require compiling and signing with Xcode, and you can’t do that on an iPhone.</p>
<p>To make it possible to instantly run your app, we built a Swift interpreter. But it’s an unusual interpreter, since it interprets from Swift… to Swift. One of the top questions we’ve gotten is how it’s implemented, so we wanted to share how it works. To make this more accessible and interesting, we simplified some of the more esoteric details. But we hope you’ll come away with a high-level picture of how the interpreter works.</p>
<p>The Swift project helpfully provides a way to reuse all of the parsing logic from the compiler: <a href="https://github.com/swiftlang/swift-syntax">SwiftSyntax</a>. This made our job a lot easier. We can easily take some Swift code and get a parsed tree out of it, which we can use to evaluate and call into to get dynamic runtime values. Let’s dig deeper.</p>
<p>We can start with generating the simplest kind of runtime values. For any literals (strings, floating point numbers, integers, and booleans), we can create corresponding Swift instances (<code>String</code>, <code>Double</code>, <code>Int</code>, <code>Bool</code>) to represent them. Since we’re not compiling this, we don’t know ahead of time what all the types will be, so we need to type erase all instances. Let’s make an enum to represent these runtime interpreter values (since we&#39;ll have multiple kinds soon):</p>
<pre><code class="language-swift">enum InterpreterValue {
    case nativeValue(Any)
}
</code></pre>
<p>Next, we&#39;ll expand our interpreter runtime values to be able to represent developer-defined types, too. Let’s say we have a struct with two fields: a string and an integer. We’ll store it as a type that has a dictionary mapping from the property name to the runtime value. When an initializer gets called, we simply need to map the arguments to the property names and populate the dictionary.</p>
<pre><code class="language-swift">enum InterpreterValue {
    case nativeValue(Any)
    case customInstance(CustomInstance)
}
struct CustomInstance {
    var type: InterpreterType
    var values: [String: InterpreterValue]
}
</code></pre>
<p>But, what happens when we want to call an API that comes from a framework, like SwiftUI? For example, let’s say we have a call to <code>Text(&quot;Hello World&quot;)</code>. We don’t want to rewrite all of the APIs, the whole benefit of making a native app is being able to call into those implementations! Well, those APIs are available for us to call into since the interpreter is also written in Swift (naturally!). We just need to change from a dynamic call to a compiled one. We can do that by pre-compiling a call to the <code>Text</code> initializer that can take dynamic arguments. Something like this:</p>
<pre><code class="language-swift">func evaluateTextInitializer(arguments: [Argument]) -&gt; Text {
  Text(arguments.first?.value.stringValue ?? &quot;&quot;)
}
</code></pre>
<p>But of course, we need more than just the <code>Text</code> initializer, so we&#39;ll generalize this to any initializer we might be called with:</p>
<pre><code class="language-swift">func evaluateInitializer(type: String, arguments: [Argument]) -&gt; Any? {
  if type == &quot;Text&quot; {
    return Text(arguments.first?.value.stringValue ?? &quot;&quot;)
  } else if type == &quot;Image&quot; {
    return Image(arguments.first?.value.stringValue ?? &quot;&quot;)
  ...
}
</code></pre>
<p>We can follow this same pattern for all other API types: function calls, properties, subscripts, etc. The difficult part is that there are a <strong>lot</strong> of APIs. It’s not practical to hand-write code to call into all of them, but fortunately there is a structured list of all of them: the .swiftinterface file for each framework. We can parse those files to get a list of all of the APIs we need and then generate the necessary code to call into them.</p>
<p>One interesting thing about taking this approach to its most extreme is that even very basic operations that you might expect any interpreter to implement, like basic numeric operations, can still call into their framework implementations. So this kind of interpreter doesn’t know how to calculate or evaluate anything itself, and is really more of a glorified <a href="https://en.wikipedia.org/wiki/Foreign_function_interface">foreign function interface</a>, but from dynamic Swift to compiled Swift.</p>
<p>One last important challenge is how to make custom types conform to framework protocols. For example, how do we make a custom SwiftUI <code>View</code>? Well, at runtime, we need a concrete type that conforms to the desired protocol. To do this, we can make stub types that conform to the protocol, but instead of having any logic of their own, simply call out to the interpreter to implement any requirements. Let’s look at <code>Shape</code>, a simple example:</p>
<pre><code class="language-swift">struct ShapeStub: Shape {
    var interpreter: Interpreter
    var instance: CustomInstance

    var layoutDirectionBehavior: LayoutDirectionBehavior {
        instance.instanceMemberProperty(&quot;layoutDirectionBehavior&quot;, interpreter: interpreter).layoutDirectionBehaviorValue
    }
    func path(in rect: CGRect) -&gt; Path {
        let arguments = [Argument(label: &quot;in&quot;, value: rect)]
        return instance.instanceFunction(&quot;path&quot;, arguments: arguments, interpreter: interpreter).pathValue
    }
}
</code></pre>
<p>Coming back to <code>View</code>, this works the same way, with a little extra complexity because of the associated type that we have to type erase:</p>
<pre><code class="language-swift">struct ViewStub: View {
    var interpreter: Interpreter
    var instance: CustomInstance

    var body: AnyView {
        instance.instanceMemberProperty(&quot;body&quot;, interpreter: interpreter).viewValue
            .map { AnyView($0) }
    }
}
</code></pre>
<p>And now we can make views that have dynamic implementations!</p>
<hr>
<p>That’s a broad survey of how the interpreter is implemented. If you want to try it out in practice, download <a href="https://apps.apple.com/us/app/bitrig/id6747835910">Bitrig</a>!</p>
<p>If there’s more you want to know about the interpreter, or Bitrig as a whole, let us know!</p>
<p><em>Did you like learning about how Bitrig&#39;s interpreter works? Want to see it up close? <a href="/jobs">We&#39;re hiring: join us!</a></em></p>
]]></content:encoded>
      <link>https://bitrig.com/blog/swift-interpreter</link>
      <guid>https://bitrig.com/blog/swift-interpreter</guid>
      <pubDate>Thu, 04 Sep 2025 00:00:00 GMT</pubDate>
    </item>
  </channel>
</rss>