{"id":16871713,"url":"https://github.com/phase/f2","last_synced_at":"2025-04-05T11:42:31.223Z","repository":{"id":86155059,"uuid":"111897008","full_name":"phase/f2","owner":"phase","description":"Language with a compile time memory management algorithm targeting the LLVM","archived":false,"fork":false,"pushed_at":"2018-04-17T07:16:26.000Z","size":116,"stargazers_count":4,"open_issues_count":2,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-02-10T22:45:21.139Z","etag":null,"topics":["compiler","llvm"],"latest_commit_sha":null,"homepage":"","language":"Kotlin","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mpl-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/phase.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2017-11-24T08:55:25.000Z","updated_at":"2023-02-19T08:49:36.000Z","dependencies_parsed_at":null,"dependency_job_id":"d42a9a42-fa75-4b2e-b860-45eafcc7abad","html_url":"https://github.com/phase/f2","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/phase%2Ff2","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/phase%2Ff2/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/phase%2Ff2/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/phase%2Ff2/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/phase","download_url":"https://codeload.github.com/phase/f2/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247332524,"owners_count":20921852,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["compiler","llvm"],"created_at":"2024-10-13T15:09:31.763Z","updated_at":"2025-04-05T11:42:31.200Z","avatar_url":"https://github.com/phase.png","language":"Kotlin","funding_links":[],"categories":[],"sub_categories":[],"readme":"# codename `f2`\n\n\u003e `f2` is the placeholder name for this language until I think of a\n\u003e cooler name.\n\nThis is a low-level language with _compile time memory management_ that\nwill soon be viable for general purpose development, with a focus on\nportability.\n\nThe main idea this language is pushing is that memory can be managed at\n_compile time._ We'll see how feasible this as we go.\n\nCurrently, the language is parsed into an AST using ANTLR, translated\ninto an IR, and that IR is converted to LLVM IR.\n\nThe syntax is influenced by Haskell, C, Prolog, and everything else out\nthere. Here's what the language can support:\n\n```haskell\nstruct X {\n  a : Int32\n}\n\nstruct Y {\n  x : X\n}\n\nf :: X -\u003e Int32\nf x = x.a.\n\ng :: Int32 -\u003e X\ng a = X{a}.\n\nh :: Int32 -\u003e Int32\nh a = let x = X{a},\n      x.a.\n\ni :: Int32 -\u003e Y\ni a = Y{X{a}}.\n\nj :: Int32 -\u003e Int32\nj a = let x = X{a},\n      let y = Y{x},\n      let w = y.x,\n      w.a.\n```\n\nThis has some functions that include heap and stack allocations.\nThis is translated into IR, which can be printed out:\n\n```\nfun f(%0 : X) : Int32 ()\n    FieldGetInstruction(debugInfo=(14,6), registerIndex=0, fieldIndex=0)\n    StoreInstruction(debugInfo=(14,6), register=1)\n    ReturnInstruction(debugInfo=(14,6), registerIndex=1)\n\nfun g(%0 : Int32) : X ()\n    HeapAllocateInstruction(debugInfo=(17,6), type=X)\n    StoreInstruction(debugInfo=(17,6), register=1)\n    FieldSetInstruction(debugInfo=(17,8), structRegisterIndex=1, fieldIndex=0, valueRegisterIndex=0)\n    ReturnInstruction(debugInfo=(17,6), registerIndex=1)\n\nfun h(%0 : Int32) : Int32 ()\n    StackAllocateInstruction(debugInfo=(20,14), type=X)\n    StoreInstruction(debugInfo=(20,14), register=1)\n    FieldSetInstruction(debugInfo=(20,16), structRegisterIndex=1, fieldIndex=0, valueRegisterIndex=0)\n    FieldGetInstruction(debugInfo=(21,6), registerIndex=1, fieldIndex=0)\n    StoreInstruction(debugInfo=(21,6), register=2)\n    ReturnInstruction(debugInfo=(21,6), registerIndex=2)\n\nfun i(%0 : Int32) : Y ()\n    HeapAllocateInstruction(debugInfo=(24,8), type=X)\n    StoreInstruction(debugInfo=(24,8), register=1)\n    FieldSetInstruction(debugInfo=(24,10), structRegisterIndex=1, fieldIndex=0, valueRegisterIndex=0)\n    HeapAllocateInstruction(debugInfo=(24,6), type=Y)\n    StoreInstruction(debugInfo=(24,6), register=2)\n    FieldSetInstruction(debugInfo=(24,8), structRegisterIndex=2, fieldIndex=0, valueRegisterIndex=1)\n    ReturnInstruction(debugInfo=(24,6), registerIndex=2)\n\nfun j(%0 : Int32) : Int32 ()\n    StackAllocateInstruction(debugInfo=(27,14), type=X)\n    StoreInstruction(debugInfo=(27,14), register=1)\n    FieldSetInstruction(debugInfo=(27,16), structRegisterIndex=1, fieldIndex=0, valueRegisterIndex=0)\n    StackAllocateInstruction(debugInfo=(28,14), type=Y)\n    StoreInstruction(debugInfo=(28,14), register=2)\n    FieldSetInstruction(debugInfo=(28,16), structRegisterIndex=2, fieldIndex=0, valueRegisterIndex=1)\n    FieldGetInstruction(debugInfo=(29,14), registerIndex=2, fieldIndex=0)\n    StoreInstruction(debugInfo=(29,6), register=3)\n    FieldGetInstruction(debugInfo=(30,6), registerIndex=3, fieldIndex=0)\n    StoreInstruction(debugInfo=(30,6), register=4)\n    ReturnInstruction(debugInfo=(30,6), registerIndex=4)\n```\n\nThe IR is register (\u0026 stack?) based, which translates easily to\nLLVM IR:\n\n```llvm\ndefine i32 @f(%X*) {\nentry:\n  %1 = getelementptr inbounds %X, %X* %0, i32 0, i32 0\n  %2 = load i32, i32* %1\n  ret i32 %2\n}\n\ndefine %X* @g(i32) {\nentry:\n  %1 = call i8* @malloc(i64 4)\n  %2 = bitcast i8* %1 to %X*\n  %3 = getelementptr inbounds %X, %X* %2, i32 0, i32 0\n  store i32 %0, i32* %3\n  ret %X* %2\n}\n\ndefine i32 @h(i32) {\nentry:\n  %1 = alloca %X\n  %2 = getelementptr inbounds %X, %X* %1, i32 0, i32 0\n  store i32 %0, i32* %2\n  %3 = getelementptr inbounds %X, %X* %1, i32 0, i32 0\n  %4 = load i32, i32* %3\n  ret i32 %4\n}\n\ndefine %Y* @i(i32) {\nentry:\n  %1 = call i8* @malloc(i64 4)\n  %2 = bitcast i8* %1 to %X*\n  %3 = getelementptr inbounds %X, %X* %2, i32 0, i32 0\n  store i32 %0, i32* %3\n  %4 = call i8* @malloc(i64 8)\n  %5 = bitcast i8* %4 to %Y*\n  %6 = getelementptr inbounds %Y, %Y* %5, i32 0, i32 0\n  store %X* %2, %X** %6\n  ret %Y* %5\n}\n\ndefine i32 @j(i32) {\nentry:\n  %1 = alloca %X\n  %2 = getelementptr inbounds %X, %X* %1, i32 0, i32 0\n  store i32 %0, i32* %2\n  %3 = alloca %Y\n  %4 = getelementptr inbounds %Y, %Y* %3, i32 0, i32 0\n  store %X* %1, %X** %4\n  %5 = getelementptr inbounds %Y, %Y* %3, i32 0, i32 0\n  %6 = load %X*, %X** %5\n  %7 = getelementptr inbounds %X, %X* %6, i32 0, i32 0\n  %8 = load i32, i32* %7\n  ret i32 %8\n}\n```\n\nHeap and stack allocations are properly translated to `malloc` and\n`alloca`. This part is easy. The more challenging part is knowing\n_when_ to free this memory. Here's an example of one way we're\nfreeing memory at compiletime:\n\n```haskell\nstruct X {\n  a : Int32\n}\n\nbox :: Int32 -\u003e X\nbox a = X{a}. -- memory is allocated here\n\nf :: Int32 -\u003e Int32\nf i = let x = box(i),\n      let a = firstThing(x),\n      let b = secondThing(x),\n      let c = thirdThing(x), -- memory can be freed after this call\n      a + b + c.\n```\n\nThis is translated to:\n\n```llvm\ndefine %X* @box(i32) {\nentry:\n  %1 = call i8* @malloc(i64 4) ; memory allocated on the heap here\n  %2 = bitcast i8* %1 to %X*\n  %3 = getelementptr inbounds %X, %X* %2, i32 0, i32 0\n  store i32 %0, i32* %3\n  ret %X* %2\n}\n\ndefine i32 @f(i32) {\nentry:\n  %1 = call %X* @box(i32 %0)\n  %2 = call i32 @firstThing(%X* %1)\n  %3 = call i32 @secondThing(%X* %1)\n  %4 = call i32 @thirdThing(%X* %1)\n  %5 = bitcast %X* %1 to i8*\n  call void @free(i8* %5) ; that memory is freed here\n  %6 = call i32 @internal_add_i32(i32 %3, i32 %4)\n  %7 = call i32 @internal_add_i32(i32 %2, i32 %6)\n  ret i32 %7\n}\n```\n\nBoom! Memory freeing has been determined at compile time! This won't\nwork for everything, and control hasn't been implemented, but it's\ngetting there.\n\nThere are a lot more tests in the test suite (or at least there will\nbe).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fphase%2Ff2","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fphase%2Ff2","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fphase%2Ff2/lists"}