this repo has no description

Allocate a separate const heap (#159)

Allocate the memory in a separate section, `const_heap`, and don't visit
it during GC.

Output constant objects at the top level for `[1,2,3]` look like:

```c
CONST_HEAP struct list const_list_0 = {
.HEAD.tag = TAG_LIST,
.first = (struct object*)(((uword)3 << kSmallIntTagBits)),
.rest = ((struct object*)kEmptyListTag)};
CONST_HEAP struct list const_list_1 = {
.HEAD.tag = TAG_LIST,
.first = (struct object*)(((uword)2 << kSmallIntTagBits)),
.rest = ptrto(const_list_0)};
CONST_HEAP struct list const_list_2 = {
.HEAD.tag = TAG_LIST,
.first = (struct object*)(((uword)1 << kSmallIntTagBits)),
.rest = ptrto(const_list_1)};
```

Fix #157.

authored by bernsteinbear.com and committed by

GitHub 7e341d54 9f44251e

+116 -1
+75
compiler.py
··· 66 66 self.record_builders: Dict[Tuple[str, ...], CompiledFunction] = {} 67 67 self.variant_tags: Dict[str, int] = {} 68 68 self.debug: bool = False 69 + self.const_heap: typing.List[str] = [] 69 70 70 71 def record_key(self, key: str) -> str: 71 72 if key not in self.record_keys: ··· 172 173 val = self.compile(funcenv, exp.body) 173 174 fn.code.append(f"return {val};") 174 175 self.function = cur 176 + if not fn.fields: 177 + # TODO(max): Closure over freevars but only consts 178 + return self._const_closure(fn) 175 179 return self.make_closure(env, fn) 176 180 177 181 def try_match(self, env: Env, arg: str, pattern: Object, fallthrough: str) -> Env: ··· 251 255 # Pacify the C compiler 252 256 self._emit("return NULL;") 253 257 self.function = cur 258 + if not fn.fields: 259 + # TODO(max): Closure over freevars but only consts 260 + return self._const_closure(fn) 254 261 return self.make_closure(env, fn) 255 262 256 263 def make_closure(self, env: Env, fn: CompiledFunction) -> str: ··· 260 267 self._debug("collect(heap);") 261 268 return name 262 269 270 + def _is_const(self, exp: Object) -> bool: 271 + if isinstance(exp, Int): 272 + return True 273 + if isinstance(exp, String): 274 + return True 275 + if isinstance(exp, Variant): 276 + return self._is_const(exp.value) 277 + if isinstance(exp, Record): 278 + return all(self._is_const(value) for value in exp.data.values()) 279 + if isinstance(exp, List): 280 + return all(self._is_const(item) for item in exp.items) 281 + if isinstance(exp, Hole): 282 + return True 283 + if isinstance(exp, Function) and len(free_in(exp)) == 0: 284 + return True 285 + return False 286 + 287 + def _const_obj(self, type: str, tag: str, contents: str) -> str: 288 + result = self.gensym(f"const_{type}") 289 + self.const_heap.append(f"CONST_HEAP struct {type} {result} = {{.HEAD.tag={tag}, {contents} }};") 290 + return f"ptrto({result})" 291 + 292 + def _const_cons(self, first: str, rest: str) -> str: 293 + return self._const_obj("list", "TAG_LIST", f".first={first}, .rest={rest}") 294 + 295 + def _const_closure(self, fn: CompiledFunction) -> str: 296 + assert len(fn.fields) == 0 297 + return self._const_obj("closure", "TAG_CLOSURE", f".fn={fn.name}, .size=0") 298 + 299 + def _emit_const(self, exp: Object) -> str: 300 + assert self._is_const(exp), f"not a constant {exp}" 301 + if isinstance(exp, Hole): 302 + return "((struct object*)kHoleTag)" 303 + if isinstance(exp, Int): 304 + # TODO(max): Bignum 305 + return f"(struct object*)(((uword){exp.value} << kSmallIntTagBits))" 306 + if isinstance(exp, List): 307 + items = [self._emit_const(item) for item in exp.items] 308 + result = "((struct object*)kEmptyListTag)" 309 + for item in reversed(items): 310 + result = self._const_cons(item, result) 311 + return result 312 + if isinstance(exp, String): 313 + if len(exp.value) < 8: 314 + raise NotImplementedError("small strings") 315 + return self._const_obj( 316 + "heap_string", "TAG_STRING", f".size={len(exp.value)}, .data={json.dumps(exp.value)}" 317 + ) 318 + if isinstance(exp, Variant): 319 + self.variant_tag(exp.tag) 320 + value = self._emit_const(exp.value) 321 + return self._const_obj("variant", "TAG_VARIANT", f".tag=Tag_{exp.tag}, .value={value}") 322 + if isinstance(exp, Record): 323 + values = {self.record_key(key): self._emit_const(value) for key, value in exp.data.items()} 324 + fields = ",\n".join(f"{{.key={key}, .value={value} }}" for key, value in values.items()) 325 + return self._const_obj("record", "TAG_RECORD", f".size={len(values)}, .fields={{ {fields} }}") 326 + if isinstance(exp, Function): 327 + assert len(free_in(exp)) == 0, "only constant functions can be constified" 328 + return self.compile_function({}, exp, name=None) 329 + raise NotImplementedError(f"const {exp}") 330 + 263 331 def compile(self, env: Env, exp: Object) -> str: 332 + if self._is_const(exp): 333 + return self._emit_const(exp) 264 334 if isinstance(exp, Int): 265 335 # TODO(max): Bignum 266 336 self._debug("collect(heap);") ··· 429 499 # Declare all functions 430 500 for function in compiler.functions: 431 501 print(function.decl() + ";", file=f) 502 + # Emit the const heap 503 + print("#define ptrto(obj) ((struct object*)((uword)&(obj) + 1))", file=f) 504 + print('#define CONST_HEAP __attribute__((section("const_heap")))', file=f) 505 + for line in compiler.const_heap: 506 + print(line, file=f) 432 507 for function in compiler.functions: 433 508 print(f"{function.decl()} {{", file=f) 434 509 for line in function.code:
+15 -1
compiler_tests.py
··· 54 54 def test_record(self) -> None: 55 55 self.assertEqual(self._run("{a = 1, b = 2}"), "{a = 1, b = 2}\n") 56 56 57 + def test_record_builder(self) -> None: 58 + self.assertEqual(self._run("f 1 2 . f = x -> y -> {a = x, b = y}"), "{a = 1, b = 2}\n") 59 + 57 60 def test_record_access(self) -> None: 58 61 self.assertEqual(self._run("rec@a . rec = {a = 1, b = 2}"), "1\n") 59 62 63 + def test_record_builder_access(self) -> None: 64 + self.assertEqual(self._run("(f 1 2)@a . f = x -> y -> {a = x, b = y}"), "1\n") 65 + 60 66 def test_hole(self) -> None: 61 67 self.assertEqual(self._run("()"), "()\n") 62 68 63 69 def test_variant(self) -> None: 64 70 self.assertEqual(self._run("# foo 123"), "#foo 123\n") 65 71 72 + def test_variant_builder(self) -> None: 73 + self.assertEqual(self._run("f 123 . f = x -> # foo x"), "#foo 123\n") 74 + 66 75 def test_function(self) -> None: 67 76 self.assertEqual(self._run("f 1 . f = x -> x + 1"), "2\n") 68 77 78 + def test_anonymous_function_as_value(self) -> None: 79 + self.assertEqual(self._run("x -> x"), "<closure>\n") 80 + 81 + def test_anonymous_function(self) -> None: 82 + self.assertEqual(self._run("((x -> x + 1) 1)"), "2\n") 83 + 69 84 def test_match_int(self) -> None: 70 85 self.assertEqual(self._run("f 3 . f = | 1 -> 2 | 3 -> 4"), "4\n") 71 86 72 - @unittest.skipIf("tcc" in os.environ.get("CC", ""), "TODO(max): Fix; TCC emits crashy code") 73 87 def test_match_list(self) -> None: 74 88 self.assertEqual(self._run("f [4, 5] . f = | [1, 2] -> 3 | [4, 5] -> 6"), "6\n") 75 89
+26
runtime.c
··· 179 179 return (struct object*)(addr | (uword)1ULL); 180 180 } 181 181 182 + #ifdef __TINYC__ 183 + // libc defines __attribute__ as an empty macro if the compiler is not GCC or 184 + // GCC < 2. We know tcc has supported __attribute__(section(...)) for 20+ years 185 + // so we can undefine it. 186 + // See tinycc-devel: https://lists.nongnu.org/archive/html/tinycc-devel/2018-04/msg00008.html 187 + // and my StackOverflow question: https://stackoverflow.com/q/78638571/569183 188 + #undef __attribute__ 189 + #endif 190 + 191 + extern char __start_const_heap[]; 192 + extern char __stop_const_heap[]; 193 + 194 + bool in_const_heap(struct gc_obj* obj) { 195 + return (uword)obj >= (uword)__start_const_heap && 196 + (uword)obj < (uword)__stop_const_heap; 197 + } 198 + 182 199 void visit_field(struct object** pointer, struct gc_heap* heap) { 183 200 if (!is_heap_object(*pointer)) { 184 201 return; 185 202 } 186 203 struct gc_obj* from = as_heap_object(*pointer); 204 + if (in_const_heap(from)) { 205 + return; 206 + } 187 207 struct gc_obj* to = is_forwarded(from) ? forwarded(from) : copy(heap, from); 188 208 *pointer = heap_tag((uintptr_t)to); 189 209 } ··· 730 750 putchar('\n'); 731 751 return obj; 732 752 } 753 + 754 + // Put something in the const heap so that __start_const_heap and 755 + // __stop_const_heap are defined by the linker. 756 + __attribute__((section("const_heap"), used)) struct heap_string private_unused_const_heap = { 757 + .HEAD.tag = TAG_STRING, .size = 11, .data = "hello world" 758 + };