MiniSwift – Swift compiler that runs in the browser via WebAssembly

Hi everyone;

I’ve been working on a project called MiniSwift. A Swift compiler written from scratch in C that runs entirely in the browser via WebAssembly.

The project includes: - Lexer - Parser - Semantic analysis - Intermediate Representation (IR) - Optional SSA - WASM code generation - Swift STDLib.

The goal was not to be fully compatible with Apple Swift yet, but to build a complete compiler pipeline that can run Swift code instantly in the browser with no server and no LLVM dependency.

Current status: - Swift standard library tests: 750 / 750 passing - Compile + run time in browser: ~0.1 ms per iteration - No server, everything runs client-side - About 70k lines of C code - Zero external dependencies

The main motivation was educational: I wanted a Swift playground that runs instantly, works offline, and shows how a compiler pipeline works end-to-end.

I’d love feedback from compiler / Swift / WASM people.

Live Playground: https://miniswift.run/playground.html

8 Likes

It's a neat project…but if it doesn't have value semantics, it isn't Swift! Here's your next test case:

var numbers = [1, 2, 3]
var moreNumbers = numbers
numbers[1] = 1000
print(moreNumbers[1]) // should print 2

I look forward to seeing it grow :-)

5 Likes

Thanks for the test case! Just pushed a fix. Array now has proper value semantics. var b = a makes a deep copy via __array_create_n. Try it on miniswift.run!

2 Likes

print appears to have a couple of bugs:

  • It doesn't respect CustomStringConvertible or CustomDebugStringConvertible. I have no idea where it gets the string for struct instances from, but this seems to print "4200" for some reason:
    struct Foo: CustomStringConvertible, CustomDebugStringConvertible {
        var description: String { "description" }
        var debugDescription: String { "debugDescription" }
    }
    
    print(Foo())
    
  • as Any / as any P / etc seems to change how enums are printed:
    enum Foo {
        case bar
    }
    
    print(Foo.bar) // "bar"
    print(Foo.bar as Any) // "0"
    

I also can't seem to surface any compiler errors. Various forms of invalid code, such as an empty switch, single quotes, and mismatched parentheses, compile successfully. This code for example should give at least three syntax errors, but instead prints "0":

let x := 'a'
print((x)

and it prints something different if you remove the colon, and a third thing if you add Int between the colon and the equals sign. (Can you guess what? I certainly couldn't!)

I still think this is a neat concept, but it seems like it has a long way to go.

1 Like

Thanks for digging into it.

You're right about print, right now it's very primitive and mostly does type-based printing (Int, String, etc.) and doesn't yet respect CustomStringConvertible / CustomDebugStringConvertible. The protocol conformance machinery exists in the compiler, but print() doesn't use witness tables yet. That's something I definitely want to fix.

The enum as Any behavior is also related. When you cast to Any it gets boxed and what print currently sees is basically the raw tag value, so it prints 0 instead of "bar". Same root problem: print isn't doing proper protocol-based dispatch yet.

The parser errors are on me. The parser is currently very permissive and tries to keep going even when the syntax is wrong, so a lot of invalid code ends up compiling and producing weird results instead of proper errors. Tightening error reporting and making the parser less forgiving is pretty high on my list.

MiniSwift is still very much a “happy path” compiler right now – it handles valid code much better than invalid code. Error reporting, diagnostics, and some runtime behaviors like print are definitely areas that need a lot more work.

Really appreciate you trying to break it. That's exactly the kind of feedback I need.

2 Likes

I'm very curious what the parser is doing to make these happen:

let x = 1 + * 2
print(x) // 4209
print(1 + * 2) // 4201 2
1 Like

:smiley:

Honestly this is starting to feel like one of those JavaScript “good parts / bad parts” experiments where you poke the language and weird things fall out. The difference is I am definitely not Brendan Eich :grinning_face_with_smiling_eyes:

Jokes aside, thanks for finding these, this was actually very helpful. I just pushed a fix and deployed it.

Three parser improvements just went live:

  • 1 + * 2 now gives “expected expression after operator” instead of silently producing garbage
  • 'a' now reports “single-quoted string is not allowed; use double quotes”
  • Operators are only accepted as identifiers in function argument context (so things like reduce(0, +) still work)

You can try the same invalid examples again on miniswift.run they should now produce proper diagnostics instead of strange numbers.

1 Like

Those cases work for me! However while testing whether overload were supported I got a runtime crash:

func f(_ x: Int) -> String {
    return "\(x)"
}

func f(_ s: String) -> String {
    return "bye"
}

print(f("4"))

Unfortunately, the page didn't seem to recover from this, as it didn't manage to print anything else out until I refreshed.


Is there a page where I could report bugs like this instead of using this thread?

1 Like

Thanks for testing! The overload case (f("4") → "bye") actually works correctly. The crash you saw was likely a WASM runtime trap from a previous run that left the page in a broken state.

Just deployed a fix: runtime errors are now caught cleanly and the playground recovers without needing a refresh. Try it again on miniswift.run.

For bug reports, I'll set up a GitHub Issues page soon, for now this thread works great. Your reports have already led to 3 fixes deployed today!

2 Likes

Hi all again,

Apologies if this feels like a duplicate follow-up.

I’ve open-sourced the core frontend components of MiniSwift, including the lexer, parser, and semantic analysis layer:

The goal is to make the compiler pipeline more transparent and easier to experiment with independently of the full runtime.

Feedback, issues, and contributions are very welcome.

Thanks!