this repo has no description
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Add lambda lifting

+110 -1
+110 -1
compiler.py
··· 40 40 assert len(b) == 1 41 41 self.byte = b[0] 42 42 43 + def __eq__(self, other): 44 + return isinstance(other, Char) and self.byte == other.byte 45 + 46 + def __repr__(self): 47 + return f"Char({self.byte})" 48 + 43 49 NEXT_LABEL = -1 44 50 45 51 CLOSURE_BASE = "rdi" ··· 54 60 def stack_at(si): 55 61 assert si < 0 56 62 return indirect("rsp", si) 63 + 64 + BUILTINS = frozenset({ 65 + "add1", "integer->char", 66 + "char->integer", "null?", "zero?", 67 + "not", "integer?", "boolean?", "+", 68 + "cons", "car", "cdr", 69 + }) 57 70 58 71 def compile_expr(expr, code, si, env): 59 72 emit = code.append ··· 226 239 case _: 227 240 raise NotImplementedError(lexpr) 228 241 242 + class LambdaConverter: 243 + def __init__(self): 244 + self.labels = {} 245 + 246 + def push_label(self, params, freevars, body): 247 + result = f"f{len(self.labels)}" 248 + self.labels[result] = ["code", params, freevars, body] 249 + return result 250 + 251 + def convert(self, expr, bound, free): 252 + match expr: 253 + case int(_) | Char(): 254 + return expr 255 + case str(_) if expr in bound or expr in BUILTINS: 256 + return expr 257 + case str(_): 258 + free.add(expr) 259 + return expr 260 + case ["lambda", params, body]: 261 + body_free = set() 262 + assert all(isinstance(v, str) for v in params) 263 + body = self.convert(body, bound | set(params), body_free) 264 + assert all(isinstance(v, str) for v in body_free) 265 + body_free = sorted(body_free) 266 + label = self.push_label(params, body_free, body) 267 + return ["closure", label, *body_free] 268 + case ["let", bindings, body]: 269 + raise NotImplementedError(expr) 270 + case ["if", test, conseq, alt]: 271 + raise NotImplementedError(expr) 272 + case [func, *args]: 273 + result = [] if isinstance(func, str) and func in BUILTINS else ["funcall"] 274 + for e in expr: 275 + result.append(self.convert(e, bound, free)) 276 + return result 277 + case _: 278 + raise NotImplementedError(expr) 279 + 280 + def lift_lambdas(expr): 281 + conv = LambdaConverter() 282 + expr = conv.convert(expr, set(), set()) 283 + labels = [[name, code] for name, code in conv.labels.items()] 284 + return ["labels", labels, expr] 285 + 229 286 def compile_program(expr): 230 287 code = [".intel_syntax", ".global scheme_entry"] 231 288 match expr: ··· 237 294 compile_expr(body, code, si=-WORD_SIZE, env={}) 238 295 code.append("ret") 239 296 case _: 240 - raise NotImplementedError(expr) 297 + expr = lift_lambdas(expr) 298 + assert isinstance(expr, list) and expr[0] == "labels" 299 + return compile_program(expr) 241 300 return "\n".join(code) 242 301 243 302 def link(program, outfile=None, verbose=True): ··· 252 311 run(["ccache", "clang", "-masm=intel", f.name, "-c", "-o", compiled_object], verbose=verbose) 253 312 run(["ccache", "clang", "-O0", "-no-pie", compiled_object, runtime_o.name, "-o", outfile], verbose=verbose) 254 313 return outfile 314 + 315 + class LambdaTests(unittest.TestCase): 316 + def test_int(self): 317 + self.assertEqual(lift_lambdas(3), ["labels", [], 3]) 318 + 319 + def test_bool(self): 320 + self.assertEqual(lift_lambdas(True), ["labels", [], True]) 321 + self.assertEqual(lift_lambdas(False), ["labels", [], False]) 322 + 323 + def test_char(self): 324 + self.assertEqual(lift_lambdas(Char("a")), ["labels", [], Char("a")]) 325 + 326 + def test_freevar(self): 327 + self.assertEqual(lift_lambdas("x"), ["labels", [], "x"]) 328 + 329 + def test_plus(self): 330 + self.assertEqual(lift_lambdas(["+", 3, 4]), ["labels", [], ["+", 3, 4]]) 331 + 332 + def test_call(self): 333 + self.assertEqual(lift_lambdas(["f", 3, 4]), ["labels", [], ["funcall", "f", 3, 4]]) 334 + 335 + def test_lambda_no_params_no_freevars(self): 336 + self.assertEqual(lift_lambdas(["lambda", [], 3]), 337 + ["labels", [ 338 + ["f0", ["code", [], [], 3]], 339 + ], ["closure", "f0"]]) 340 + 341 + def test_lambda_no_params_with_freevars(self): 342 + self.assertEqual(lift_lambdas(["lambda", [], "z"]), 343 + ["labels", [ 344 + ["f0", ["code", [], ["z"], "z"]], 345 + ], ["closure", "f0", "z"]]) 346 + 347 + def test_lambda_with_params_no_freevars(self): 348 + self.assertEqual(lift_lambdas(["lambda", ["x"], "x"]), 349 + ["labels", [ 350 + ["f0", ["code", ["x"], [], "x"]], 351 + ], ["closure", "f0"]]) 352 + 353 + def test_lambda_with_params_and_freevars(self): 354 + self.assertEqual(lift_lambdas(["lambda", ["x"], ["+", "x", "y"]]), 355 + ["labels", 356 + [["f0", ["code", ["x"], ["y"], ["+", "x", "y"]]]], 357 + ["closure", "f0", "y"]]) 255 358 256 359 class EndToEndTests(unittest.TestCase): 257 360 def _run(self, expr): ··· 449 552 ["let", [["f", ["closure", "const", "v"]]], 450 553 ["funcall", "f"]]] 451 554 ]), "3") 555 + 556 + def test_lambda(self): 557 + self.assertEqual(self._run_program(["lambda", ["x"], "x"]), "<closure>") 558 + 559 + def test_call_lambda(self): 560 + self.assertEqual(self._run_program([["lambda", ["x"], "x"], 3]), "3") 452 561 453 562 454 563 if __name__ == "__main__":