pyscriptc 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pyscriptc/__init__.py +3 -0
- pyscriptc/__main__.py +3 -0
- pyscriptc/_posthog_token.py +1 -0
- pyscriptc/builtins.py +156 -0
- pyscriptc/canonical.py +167 -0
- pyscriptc/check.py +49 -0
- pyscriptc/cli.py +271 -0
- pyscriptc/coverage.py +388 -0
- pyscriptc/debug.py +120 -0
- pyscriptc/diagnostics.py +179 -0
- pyscriptc/errors.py +56 -0
- pyscriptc/ir.py +365 -0
- pyscriptc/llvm.py +2129 -0
- pyscriptc/log.py +127 -0
- pyscriptc/lower/__init__.py +1071 -0
- pyscriptc/lower/_calls.py +1499 -0
- pyscriptc/lower/_classes.py +969 -0
- pyscriptc/lower/_core.py +301 -0
- pyscriptc/lower/_expr.py +886 -0
- pyscriptc/lower/_stdlib.py +491 -0
- pyscriptc/lower/_stdlib_shared.py +93 -0
- pyscriptc/lower/_stmt.py +1173 -0
- pyscriptc/package.py +249 -0
- pyscriptc/restlower.py +1008 -0
- pyscriptc/runtime/ps_argv.c +28 -0
- pyscriptc/runtime/ps_b64.c +168 -0
- pyscriptc/runtime/ps_coll.c +133 -0
- pyscriptc/runtime/ps_dt.c +388 -0
- pyscriptc/runtime/ps_hash.c +411 -0
- pyscriptc/runtime/ps_help.c +219 -0
- pyscriptc/runtime/ps_hmac.c +120 -0
- pyscriptc/runtime/ps_io.c +198 -0
- pyscriptc/runtime/ps_island.c +272 -0
- pyscriptc/runtime/ps_json.c +402 -0
- pyscriptc/runtime/ps_mix.c +47 -0
- pyscriptc/runtime/ps_more.c +1089 -0
- pyscriptc/runtime/ps_osmore.c +447 -0
- pyscriptc/runtime/ps_path.c +547 -0
- pyscriptc/runtime/ps_re.c +1102 -0
- pyscriptc/runtime/ps_rt.c +1175 -0
- pyscriptc/runtime/ps_rt.h +189 -0
- pyscriptc/runtime/ps_struct.c +291 -0
- pyscriptc/runtime/ps_time.c +207 -0
- pyscriptc/runtime/ps_unicode.c +1521 -0
- pyscriptc/runtime/ps_url.c +327 -0
- pyscriptc/stdlib_queue.py +191 -0
- pyscriptc/stdmore.py +1596 -0
- pyscriptc/telemetry.py +219 -0
- pyscriptc-0.1.0.dist-info/METADATA +187 -0
- pyscriptc-0.1.0.dist-info/RECORD +54 -0
- pyscriptc-0.1.0.dist-info/WHEEL +5 -0
- pyscriptc-0.1.0.dist-info/entry_points.txt +2 -0
- pyscriptc-0.1.0.dist-info/licenses/LICENSE +201 -0
- pyscriptc-0.1.0.dist-info/top_level.txt +1 -0
pyscriptc/llvm.py
ADDED
|
@@ -0,0 +1,2129 @@
|
|
|
1
|
+
"""Lower spike IR to textual LLVM IR and a host binary.
|
|
2
|
+
|
|
3
|
+
`int` stays a Python int in the IR. This backend uses i64 only when the value
|
|
4
|
+
fits, and checked arithmetic traps on overflow instead of wrapping. A literal
|
|
5
|
+
that does not fit is refused: it is not truncated.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import struct
|
|
11
|
+
import subprocess
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from typing import Any
|
|
14
|
+
|
|
15
|
+
from pyscriptc.builtins import EXCEPTIONS, USER_EXC_BASE
|
|
16
|
+
from pyscriptc.errors import FitError, IRError
|
|
17
|
+
from pyscriptc.ir import assert_same_cpython, fits_i64
|
|
18
|
+
from pyscriptc.stdlib_queue import MATH_FUNCTIONS
|
|
19
|
+
|
|
20
|
+
_ICMP = {"<": "slt", "<=": "sle", ">": "sgt", ">=": "sge", "==": "eq", "!=": "ne"}
|
|
21
|
+
_FOP = {"+": "fadd", "-": "fsub", "*": "fmul"}
|
|
22
|
+
_INT_HELPER = {"+": "iadd", "-": "isub", "*": "imul", "//": "floordiv"}
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def emit_llvm(
|
|
26
|
+
module: dict[str, Any],
|
|
27
|
+
call: str | None = None,
|
|
28
|
+
args: list[Any] | None = None,
|
|
29
|
+
standalone: bool = False,
|
|
30
|
+
) -> str:
|
|
31
|
+
"""With `standalone` and no `call`, main does nothing: module level code is not lowered."""
|
|
32
|
+
assert_same_cpython(module)
|
|
33
|
+
if module["fences"]:
|
|
34
|
+
raise IRError("refusing to emit LLVM while fences are present")
|
|
35
|
+
if not module["functions"]:
|
|
36
|
+
raise IRError("module has no functions to emit")
|
|
37
|
+
return Emitter(module).emit(call, [] if args is None else args, standalone)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def link_binary(
|
|
41
|
+
llvm_path: Path,
|
|
42
|
+
output: Path,
|
|
43
|
+
driver: str = "clang",
|
|
44
|
+
target: str | None = None,
|
|
45
|
+
record: list[list[str]] | None = None,
|
|
46
|
+
) -> None:
|
|
47
|
+
"""Link `.ll` text plus the C runtime into `output`.
|
|
48
|
+
|
|
49
|
+
Two-stage: `.ll -> .o`, then objects + runtime `*.c` link. Required for
|
|
50
|
+
`zig cc` on macOS (a `.ll` on the link line writes an empty binary there)
|
|
51
|
+
and keeps per-target artifacts clean. `driver` is `clang` or `zig`
|
|
52
|
+
(`zig cc`); `target` is a zig-style triple and requires the zig driver —
|
|
53
|
+
it bundles the per-target sysroot that `clang --target` does not.
|
|
54
|
+
"""
|
|
55
|
+
output.parent.mkdir(parents=True, exist_ok=True)
|
|
56
|
+
if target is not None and driver != "zig":
|
|
57
|
+
raise IRError("--target requires --driver zig (bundled sysroot per target)")
|
|
58
|
+
cc = "zig cc" if driver == "zig" else driver
|
|
59
|
+
runtime = Path(__file__).resolve().parent / "runtime"
|
|
60
|
+
text = llvm_path.read_text(encoding="utf-8")
|
|
61
|
+
sources = [str(path) for path in sorted(runtime.glob("*.c"))]
|
|
62
|
+
target_flags = ["-target", target] if target is not None else []
|
|
63
|
+
tail = ["-lm", "-o", str(output)]
|
|
64
|
+
if "ps_island_" in text:
|
|
65
|
+
if target is not None:
|
|
66
|
+
raise IRError(
|
|
67
|
+
"island fallback embeds the host CPython; "
|
|
68
|
+
"cross-compiling it needs a cross-built libpython (explain-only)"
|
|
69
|
+
)
|
|
70
|
+
import sysconfig
|
|
71
|
+
|
|
72
|
+
include = sysconfig.get_path("include")
|
|
73
|
+
libdir = sysconfig.get_config_var("LIBDIR")
|
|
74
|
+
version = sysconfig.get_config_var("VERSION")
|
|
75
|
+
target_flags.extend(["-I", include])
|
|
76
|
+
tail = [f"-L{libdir}", f"-lpython{version}", f"-Wl,-rpath,{libdir}", *tail]
|
|
77
|
+
else:
|
|
78
|
+
sources = [source for source in sources if not source.endswith("ps_island.c")]
|
|
79
|
+
# zig cannot compile C sources inside a link invocation for Mach-O, so
|
|
80
|
+
# every unit goes to .o first and the link line carries objects only.
|
|
81
|
+
units = [llvm_path, *(Path(source) for source in sources)]
|
|
82
|
+
objects = [output.with_name(f"{output.name}.{unit.stem}.o") for unit in units]
|
|
83
|
+
proc: subprocess.CompletedProcess[str] | None = None
|
|
84
|
+
for unit, obj in zip(units, objects, strict=True):
|
|
85
|
+
kind = ["-x", "ir"] if driver == "clang" and unit == llvm_path else []
|
|
86
|
+
argv = [*cc.split(), "-O2", *kind, "-c", str(unit), "-o", str(obj), *target_flags]
|
|
87
|
+
if record is not None:
|
|
88
|
+
record.append([str(arg) for arg in argv])
|
|
89
|
+
proc = subprocess.run(argv, capture_output=True, text=True)
|
|
90
|
+
if proc.returncode != 0:
|
|
91
|
+
break
|
|
92
|
+
if proc is not None and proc.returncode == 0:
|
|
93
|
+
# no -O on the link line: it is a compile flag and zig's Mach-O
|
|
94
|
+
# driver crashes on it (0.16.0)
|
|
95
|
+
argv = [*cc.split(), *objects, *target_flags, *tail]
|
|
96
|
+
if record is not None:
|
|
97
|
+
record.append([str(arg) for arg in argv])
|
|
98
|
+
proc = subprocess.run(argv, capture_output=True, text=True)
|
|
99
|
+
if proc is None or proc.returncode != 0:
|
|
100
|
+
detail = (proc.stderr or proc.stdout).strip() if proc is not None else ""
|
|
101
|
+
raise IRError(f"{cc} failed to assemble LLVM IR: {detail}")
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
class Emitter:
|
|
105
|
+
def __init__(self, module: dict[str, Any]) -> None:
|
|
106
|
+
self.module = module
|
|
107
|
+
self.functions = {fn["name"]: fn for fn in module["functions"]}
|
|
108
|
+
self.out: list[str] = []
|
|
109
|
+
self.globals: list[str] = []
|
|
110
|
+
self.needed: set[str] = set()
|
|
111
|
+
self.string_data: list[bytes] = []
|
|
112
|
+
self.tid = 0
|
|
113
|
+
self.lid = 0
|
|
114
|
+
self.slots: dict[str, tuple[str, dict[str, Any]]] = {}
|
|
115
|
+
self.terminated = False
|
|
116
|
+
self.try_depth = 0
|
|
117
|
+
self.outline_depth = 0
|
|
118
|
+
self.outline_n = 0
|
|
119
|
+
self.outline_lines: list[str] = []
|
|
120
|
+
self.slot_order: list[str] = []
|
|
121
|
+
self.slot_types: dict[str, dict[str, Any]] = {}
|
|
122
|
+
self.ret_ptr: str | None = None
|
|
123
|
+
self.ret_llvm: str | None = None
|
|
124
|
+
self.flow_ptr: str | None = None
|
|
125
|
+
self.class_index: dict[str, dict[str, Any]] = {}
|
|
126
|
+
self.json_n = 0
|
|
127
|
+
self.atexit_n = 0
|
|
128
|
+
self.abi_decls: dict[str, tuple[str, list[str]]] = {}
|
|
129
|
+
|
|
130
|
+
def emit(self, call: str | None, args: list[Any], standalone: bool = False) -> str:
|
|
131
|
+
self.class_index = {cls["name"]: cls for cls in self.module.get("classes", [])}
|
|
132
|
+
for cls in self.module.get("classes", []):
|
|
133
|
+
body = "i8" if not cls["fields"] else ", ".join(_llvm_type(field["type"]) for field in cls["fields"])
|
|
134
|
+
self.globals.append(f"%cls.{cls['name']} = type {{ {body} }}")
|
|
135
|
+
for method in cls["methods"]:
|
|
136
|
+
symbol = f"cls.{cls['name']}.{method['name']}"
|
|
137
|
+
self.functions[symbol] = {**method, "name": symbol}
|
|
138
|
+
for fn in self.module["functions"]:
|
|
139
|
+
self.emit_function(fn)
|
|
140
|
+
for cls in self.module.get("classes", []):
|
|
141
|
+
for method in cls["methods"]:
|
|
142
|
+
self.emit_function(self.functions[f"cls.{cls['name']}.{method['name']}"])
|
|
143
|
+
if call is not None:
|
|
144
|
+
self.emit_main(call, args)
|
|
145
|
+
elif standalone:
|
|
146
|
+
# A script's synthesized entry (E1 item 1) is the program: run it
|
|
147
|
+
# in source order. A definition-only module keeps the no-op main.
|
|
148
|
+
entry = self.module.get("entry")
|
|
149
|
+
entry_fn = next((fn for fn in self.module["functions"] if fn["name"] == entry), None)
|
|
150
|
+
if entry_fn is not None and entry_fn["name"] == "main" and not entry_fn["params"]:
|
|
151
|
+
self.emit_main("main", [])
|
|
152
|
+
else:
|
|
153
|
+
self.out.extend(["define i32 @main(i32 %argc, ptr %argv) {", "entry:", " ret i32 0", "}", ""])
|
|
154
|
+
self.out.extend(self.outline_lines)
|
|
155
|
+
header = ["%ps.str = type { i64, ptr }", ""]
|
|
156
|
+
header.extend(self.globals)
|
|
157
|
+
if self.globals:
|
|
158
|
+
header.append("")
|
|
159
|
+
decls = _declarations(self.needed)
|
|
160
|
+
decls.extend(self._abi_lines())
|
|
161
|
+
header.extend(decls)
|
|
162
|
+
if decls:
|
|
163
|
+
header.append("")
|
|
164
|
+
header.extend(_helpers(self.needed))
|
|
165
|
+
return "\n".join(header + self.out).rstrip() + "\n"
|
|
166
|
+
|
|
167
|
+
def emit_function(self, fn: dict[str, Any]) -> None:
|
|
168
|
+
ret = _llvm_type(fn["returnType"])
|
|
169
|
+
params = ", ".join(f"{_llvm_type(p['type'])} %{_safe(p['name'])}" for p in fn["params"])
|
|
170
|
+
self.out.append(f"define {ret} @{_fn_symbol(fn['name'])}({params}) {{")
|
|
171
|
+
self._reset_function()
|
|
172
|
+
self.block("entry")
|
|
173
|
+
self.slot_order = []
|
|
174
|
+
self.slot_types = {}
|
|
175
|
+
for local in fn["locals"]:
|
|
176
|
+
if local["type"]["kind"] == "none":
|
|
177
|
+
# None is not a runtime value. The local is the singleton, so it has no slot.
|
|
178
|
+
self.slots[local["name"]] = ("", local["type"])
|
|
179
|
+
continue
|
|
180
|
+
ptr = self.tmp()
|
|
181
|
+
self.line(f"{ptr} = alloca {_llvm_type(local['type'])}")
|
|
182
|
+
self.slots[local["name"]] = (ptr, local["type"])
|
|
183
|
+
self.slot_order.append(local["name"])
|
|
184
|
+
self.slot_types[local["name"]] = local["type"]
|
|
185
|
+
self.ret_llvm = None if fn["returnType"]["kind"] == "none" else _llvm_type(fn["returnType"])
|
|
186
|
+
self.ret_ptr = None if self.ret_llvm is None else self._alloca(self.ret_llvm)
|
|
187
|
+
self.flow_ptr = self._alloca("i32")
|
|
188
|
+
for param in fn["params"]:
|
|
189
|
+
self.store(param["name"], f"%{_safe(param['name'])}")
|
|
190
|
+
self.emit_body(fn["body"])
|
|
191
|
+
if not self.terminated:
|
|
192
|
+
if fn["returnType"]["kind"] == "none":
|
|
193
|
+
self.line("ret void")
|
|
194
|
+
else:
|
|
195
|
+
self.line("unreachable")
|
|
196
|
+
self.out.append("}")
|
|
197
|
+
self.out.append("")
|
|
198
|
+
|
|
199
|
+
def emit_main(self, name: str, args: list[Any]) -> None:
|
|
200
|
+
fn = self.functions.get(name)
|
|
201
|
+
if fn is None:
|
|
202
|
+
raise IRError(f"no function {name} to call")
|
|
203
|
+
params = fn["params"]
|
|
204
|
+
if len(args) > len(params):
|
|
205
|
+
raise FitError(f"{name} expects {len(params)} argument(s), got {len(args)}")
|
|
206
|
+
filled: list[Any] = list(args)
|
|
207
|
+
for param in params[len(args) :]:
|
|
208
|
+
default = param.get("default")
|
|
209
|
+
if not isinstance(default, dict) or default.get("kind") != "const":
|
|
210
|
+
raise FitError(f"{name} expects {len(params)} argument(s), got {len(args)}")
|
|
211
|
+
filled.append(_ir_const_value(param["type"], default["value"]))
|
|
212
|
+
prepared = [_prepare_arg(param, value) for param, value in zip(params, filled, strict=True)]
|
|
213
|
+
self.out.append("define i32 @main(i32 %argc, ptr %argv) {")
|
|
214
|
+
self._reset_function()
|
|
215
|
+
self.block("entry")
|
|
216
|
+
self._abi_decl("ps_argv_init", "void", ["i32", "ptr"])
|
|
217
|
+
self._call_void("ps_argv_init", ["i32 %argc", "ptr %argv"])
|
|
218
|
+
values = [self.materialize_arg(param["type"], value) for param, value in zip(params, prepared, strict=True)]
|
|
219
|
+
result = self.call_function(fn, values)
|
|
220
|
+
if fn["returnType"]["kind"] != "none":
|
|
221
|
+
self.print_value(fn["returnType"], result)
|
|
222
|
+
self.line("ret i32 0")
|
|
223
|
+
self.out.append("}")
|
|
224
|
+
self.out.append("")
|
|
225
|
+
|
|
226
|
+
def emit_body(self, body: list[dict[str, Any]]) -> None:
|
|
227
|
+
for stmt in body:
|
|
228
|
+
if self.terminated:
|
|
229
|
+
break
|
|
230
|
+
self.emit_stmt(stmt)
|
|
231
|
+
|
|
232
|
+
def emit_stmt(self, stmt: dict[str, Any]) -> None:
|
|
233
|
+
kind = stmt["kind"]
|
|
234
|
+
if kind == "assign":
|
|
235
|
+
if stmt["value"]["type"]["kind"] == "none":
|
|
236
|
+
if stmt["value"]["kind"] != "const":
|
|
237
|
+
self.emit_expr(stmt["value"])
|
|
238
|
+
return
|
|
239
|
+
self.store(stmt["target"], self.emit_expr(stmt["value"]))
|
|
240
|
+
elif kind == "expr":
|
|
241
|
+
if stmt["expr"]["kind"] == "const" and stmt["expr"]["type"]["kind"] == "none":
|
|
242
|
+
return
|
|
243
|
+
self.emit_expr(stmt["expr"])
|
|
244
|
+
elif kind == "return":
|
|
245
|
+
self._emit_return(stmt)
|
|
246
|
+
elif kind == "pass":
|
|
247
|
+
return
|
|
248
|
+
elif kind == "setitem":
|
|
249
|
+
self.emit_setitem(stmt)
|
|
250
|
+
elif kind == "setattr":
|
|
251
|
+
self.emit_setattr(stmt)
|
|
252
|
+
elif kind == "raise":
|
|
253
|
+
self.emit_raise(stmt)
|
|
254
|
+
self.terminated = True
|
|
255
|
+
elif kind == "try":
|
|
256
|
+
self.emit_try(stmt)
|
|
257
|
+
elif kind == "if":
|
|
258
|
+
self.emit_if(stmt)
|
|
259
|
+
elif kind == "while":
|
|
260
|
+
self.emit_while(stmt)
|
|
261
|
+
else:
|
|
262
|
+
raise IRError(f"cannot emit statement {kind}")
|
|
263
|
+
|
|
264
|
+
def emit_if(self, stmt: dict[str, Any]) -> None:
|
|
265
|
+
cond = self.emit_expr(stmt["cond"])
|
|
266
|
+
then_l = self.fresh("then")
|
|
267
|
+
else_l = self.fresh("else")
|
|
268
|
+
merge_l = self.fresh("merge")
|
|
269
|
+
self.line(f"br i1 {cond}, label %{then_l}, label %{else_l}")
|
|
270
|
+
self.terminated = True
|
|
271
|
+
self.block(then_l)
|
|
272
|
+
self.emit_body(stmt["then"])
|
|
273
|
+
then_dead = self.terminated
|
|
274
|
+
if not then_dead:
|
|
275
|
+
self.line(f"br label %{merge_l}")
|
|
276
|
+
self.terminated = True
|
|
277
|
+
self.block(else_l)
|
|
278
|
+
self.emit_body(stmt["else"])
|
|
279
|
+
else_dead = self.terminated
|
|
280
|
+
if not else_dead:
|
|
281
|
+
self.line(f"br label %{merge_l}")
|
|
282
|
+
self.terminated = True
|
|
283
|
+
if then_dead and else_dead:
|
|
284
|
+
self.terminated = True
|
|
285
|
+
return
|
|
286
|
+
self.block(merge_l)
|
|
287
|
+
|
|
288
|
+
def emit_while(self, stmt: dict[str, Any]) -> None:
|
|
289
|
+
cond_l = self.fresh("while.cond")
|
|
290
|
+
body_l = self.fresh("while.body")
|
|
291
|
+
end_l = self.fresh("while.end")
|
|
292
|
+
self.line(f"br label %{cond_l}")
|
|
293
|
+
self.terminated = True
|
|
294
|
+
self.block(cond_l)
|
|
295
|
+
cond = self.emit_expr(stmt["cond"])
|
|
296
|
+
self.line(f"br i1 {cond}, label %{body_l}, label %{end_l}")
|
|
297
|
+
self.terminated = True
|
|
298
|
+
self.block(body_l)
|
|
299
|
+
self.emit_body(stmt["body"])
|
|
300
|
+
if not self.terminated:
|
|
301
|
+
self.line(f"br label %{cond_l}")
|
|
302
|
+
self.terminated = True
|
|
303
|
+
self.block(end_l)
|
|
304
|
+
|
|
305
|
+
def emit_try(self, stmt: dict[str, Any]) -> None:
|
|
306
|
+
self.needed.add("exc")
|
|
307
|
+
outline = self._emit_outline(stmt["body"])
|
|
308
|
+
env = self._pack_env()
|
|
309
|
+
if self.flow_ptr is None:
|
|
310
|
+
raise IRError("try has no flow slot")
|
|
311
|
+
code = self._call_ret("i32", "ps_try_call", [f"ptr @{outline}", f"ptr {env}", f"ptr {self.flow_ptr}"])
|
|
312
|
+
raised = self.tmp()
|
|
313
|
+
self.line(f"{raised} = icmp ne i32 {code}, 0")
|
|
314
|
+
hand_l = self.fresh("try.handler")
|
|
315
|
+
cont_l = self.fresh("try.cont")
|
|
316
|
+
self.line(f"br i1 {raised}, label %{hand_l}, label %{cont_l}")
|
|
317
|
+
self.terminated = True
|
|
318
|
+
self.block(hand_l)
|
|
319
|
+
merge_l = self.fresh("try.merge")
|
|
320
|
+
ret_l = self.fresh("try.ret")
|
|
321
|
+
ecode = self._call_ret("i32", "ps_exc_code", [])
|
|
322
|
+
for index, handler in enumerate(stmt["handlers"]):
|
|
323
|
+
take_l = self.fresh(f"try.h{index}")
|
|
324
|
+
miss_l = self.fresh("try.miss")
|
|
325
|
+
cond = self._exc_match(ecode, handler)
|
|
326
|
+
self.line(f"br i1 {cond}, label %{take_l}, label %{miss_l}")
|
|
327
|
+
self.terminated = True
|
|
328
|
+
self.block(take_l)
|
|
329
|
+
self.emit_body(handler["body"])
|
|
330
|
+
if not self.terminated:
|
|
331
|
+
self.line(f"br label %{merge_l}")
|
|
332
|
+
self.terminated = True
|
|
333
|
+
self.block(miss_l)
|
|
334
|
+
self.line("call void @ps_reraise() noreturn")
|
|
335
|
+
self.line("unreachable")
|
|
336
|
+
self.terminated = True
|
|
337
|
+
self.block(cont_l)
|
|
338
|
+
flow = self._load("i32", self.flow_ptr)
|
|
339
|
+
leave = self.tmp()
|
|
340
|
+
self.line(f"{leave} = icmp eq i32 {flow}, 1")
|
|
341
|
+
self.line(f"br i1 {leave}, label %{ret_l}, label %{merge_l}")
|
|
342
|
+
self.terminated = True
|
|
343
|
+
self.block(ret_l)
|
|
344
|
+
self._emit_propagated_return()
|
|
345
|
+
self.block(merge_l)
|
|
346
|
+
|
|
347
|
+
def _emit_return(self, stmt: dict[str, Any]) -> None:
|
|
348
|
+
kind = stmt["value"]["type"]["kind"]
|
|
349
|
+
if self.outline_depth:
|
|
350
|
+
if kind != "none":
|
|
351
|
+
value = self.emit_expr(stmt["value"])
|
|
352
|
+
if self.ret_ptr is None or self.ret_llvm is None:
|
|
353
|
+
raise IRError("return inside try has no slot")
|
|
354
|
+
self.line(f"store {self.ret_llvm} {value}, ptr {self.ret_ptr}")
|
|
355
|
+
elif stmt["value"]["kind"] != "const":
|
|
356
|
+
self.emit_expr(stmt["value"])
|
|
357
|
+
if self.terminated:
|
|
358
|
+
return
|
|
359
|
+
self.line("ret i32 1")
|
|
360
|
+
elif kind == "none":
|
|
361
|
+
if stmt["value"]["kind"] != "const":
|
|
362
|
+
self.emit_expr(stmt["value"])
|
|
363
|
+
if self.terminated:
|
|
364
|
+
return
|
|
365
|
+
self.line("ret void")
|
|
366
|
+
else:
|
|
367
|
+
value = self.emit_expr(stmt["value"])
|
|
368
|
+
self.line(f"ret {_llvm_type(stmt['value']['type'])} {value}")
|
|
369
|
+
self.terminated = True
|
|
370
|
+
|
|
371
|
+
def _emit_propagated_return(self) -> None:
|
|
372
|
+
if self.outline_depth:
|
|
373
|
+
self.line("ret i32 1")
|
|
374
|
+
elif self.ret_llvm is None:
|
|
375
|
+
self.line("ret void")
|
|
376
|
+
else:
|
|
377
|
+
if self.ret_ptr is None:
|
|
378
|
+
raise IRError("return has no slot")
|
|
379
|
+
value = self._load(self.ret_llvm, self.ret_ptr)
|
|
380
|
+
self.line(f"ret {self.ret_llvm} {value}")
|
|
381
|
+
self.terminated = True
|
|
382
|
+
|
|
383
|
+
def _emit_outline(self, body: list[dict[str, Any]]) -> str:
|
|
384
|
+
name = f"ps.try.{self.outline_n}"
|
|
385
|
+
self.outline_n += 1
|
|
386
|
+
saved_out = self.out
|
|
387
|
+
saved_slots = self.slots
|
|
388
|
+
saved_flow = self.flow_ptr
|
|
389
|
+
saved_ret = self.ret_ptr
|
|
390
|
+
saved_term = self.terminated
|
|
391
|
+
saved_lid = self.lid
|
|
392
|
+
saved_tid = self.tid
|
|
393
|
+
saved_depth = self.outline_depth
|
|
394
|
+
self.out = []
|
|
395
|
+
self.out.append(f"define internal i32 @{name}(ptr %env) {{")
|
|
396
|
+
self.tid = 0
|
|
397
|
+
self.lid = 0
|
|
398
|
+
self.terminated = False
|
|
399
|
+
self.outline_depth = saved_depth + 1
|
|
400
|
+
self.block("entry")
|
|
401
|
+
self._bind_env("%env")
|
|
402
|
+
self.flow_ptr = self._alloca("i32")
|
|
403
|
+
self.emit_body(body)
|
|
404
|
+
if not self.terminated:
|
|
405
|
+
self.line("ret i32 0")
|
|
406
|
+
self.out.append("}")
|
|
407
|
+
self.out.append("")
|
|
408
|
+
self.outline_lines.extend(self.out)
|
|
409
|
+
self.out = saved_out
|
|
410
|
+
self.slots = saved_slots
|
|
411
|
+
self.flow_ptr = saved_flow
|
|
412
|
+
self.ret_ptr = saved_ret
|
|
413
|
+
self.terminated = saved_term
|
|
414
|
+
self.lid = saved_lid
|
|
415
|
+
self.tid = saved_tid
|
|
416
|
+
self.outline_depth = saved_depth
|
|
417
|
+
return name
|
|
418
|
+
|
|
419
|
+
def _env_pointers(self) -> list[str]:
|
|
420
|
+
ptrs = [self.slots[name][0] for name in self.slot_order]
|
|
421
|
+
if self.ret_ptr is not None:
|
|
422
|
+
ptrs.append(self.ret_ptr)
|
|
423
|
+
return ptrs
|
|
424
|
+
|
|
425
|
+
def _pack_env(self) -> str:
|
|
426
|
+
ptrs = self._env_pointers()
|
|
427
|
+
if not ptrs:
|
|
428
|
+
return "null"
|
|
429
|
+
env = self._alloca(f"[{len(ptrs)} x ptr]")
|
|
430
|
+
for index, ptr in enumerate(ptrs):
|
|
431
|
+
dest = env if index == 0 else self.tmp()
|
|
432
|
+
if index != 0:
|
|
433
|
+
self.line(f"{dest} = getelementptr ptr, ptr {env}, i64 {index}")
|
|
434
|
+
self.line(f"store ptr {ptr}, ptr {dest}")
|
|
435
|
+
return env
|
|
436
|
+
|
|
437
|
+
def _bind_env(self, env: str) -> None:
|
|
438
|
+
self.slots = {}
|
|
439
|
+
for index, name in enumerate(self.slot_order):
|
|
440
|
+
loaded = self._load_env_ptr(env, index)
|
|
441
|
+
self.slots[name] = (loaded, self.slot_types[name])
|
|
442
|
+
if self.ret_llvm is not None:
|
|
443
|
+
self.ret_ptr = self._load_env_ptr(env, len(self.slot_order))
|
|
444
|
+
|
|
445
|
+
def _load_env_ptr(self, env: str, index: int) -> str:
|
|
446
|
+
if index == 0:
|
|
447
|
+
return self._load("ptr", env)
|
|
448
|
+
gep = self.tmp()
|
|
449
|
+
self.line(f"{gep} = getelementptr ptr, ptr {env}, i64 {index}")
|
|
450
|
+
return self._load("ptr", gep)
|
|
451
|
+
|
|
452
|
+
def emit_raise(self, stmt: dict[str, Any]) -> None:
|
|
453
|
+
self.needed.add("exc")
|
|
454
|
+
code = stmt["code"] if "code" in stmt else EXCEPTIONS[stmt["exc"]]
|
|
455
|
+
if stmt.get("reraise"):
|
|
456
|
+
self.line("call void @ps_reraise() noreturn")
|
|
457
|
+
elif stmt.get("message") is None:
|
|
458
|
+
self.line(f"call void @ps_raise_code(i32 {code}) noreturn")
|
|
459
|
+
else:
|
|
460
|
+
message = self.emit_expr(stmt["message"])
|
|
461
|
+
length, ptr = self._buf_parts(message)
|
|
462
|
+
self.line(f"call void @ps_raise_msg(i32 {code}, i64 {length}, ptr {ptr}) noreturn")
|
|
463
|
+
self.line("unreachable")
|
|
464
|
+
|
|
465
|
+
def emit_setitem(self, stmt: dict[str, Any]) -> None:
|
|
466
|
+
container = self.emit_expr(stmt["container"])
|
|
467
|
+
index = self.emit_expr(stmt["index"])
|
|
468
|
+
value = self.emit_expr(stmt["value"])
|
|
469
|
+
kind = stmt["container"]["type"]["kind"]
|
|
470
|
+
if kind == "list":
|
|
471
|
+
self.needed.add("seq")
|
|
472
|
+
slot = self._stash(stmt["value"]["type"], value)
|
|
473
|
+
self.line(f"call void @ps_seq_set(ptr {container}, i64 {index}, ptr {slot})")
|
|
474
|
+
return
|
|
475
|
+
if kind == "dict":
|
|
476
|
+
self.needed.add("map")
|
|
477
|
+
key = self._stash(stmt["index"]["type"], index)
|
|
478
|
+
slot = self._stash(stmt["value"]["type"], value)
|
|
479
|
+
self.line(f"call void @ps_map_put(ptr {container}, ptr {key}, ptr {slot})")
|
|
480
|
+
return
|
|
481
|
+
raise IRError(f"cannot assign into {kind}")
|
|
482
|
+
|
|
483
|
+
def emit_setattr(self, stmt: dict[str, Any]) -> None:
|
|
484
|
+
obj = self.emit_expr(stmt["object"])
|
|
485
|
+
value = self.emit_expr(stmt["value"])
|
|
486
|
+
field = self._field(stmt["object"]["type"]["name"], stmt["name"])
|
|
487
|
+
slot = self._field_ptr(stmt["object"]["type"]["name"], obj, field)
|
|
488
|
+
self.line(f"store {_llvm_type(field['type'])} {value}, ptr {slot}")
|
|
489
|
+
|
|
490
|
+
def _exc_match(self, code: str, handler: dict[str, Any] | list[str]) -> str:
|
|
491
|
+
names = handler if isinstance(handler, list) else handler["exc"]
|
|
492
|
+
checks: list[str] = []
|
|
493
|
+
for name in names:
|
|
494
|
+
if name not in EXCEPTIONS:
|
|
495
|
+
continue
|
|
496
|
+
wanted = EXCEPTIONS[name]
|
|
497
|
+
call = self._call_ret("i32", "ps_exc_isa", [f"i32 {code}", f"i32 {wanted}"])
|
|
498
|
+
checks.append(self._flag(call))
|
|
499
|
+
if name == "Exception":
|
|
500
|
+
# User codes have no runtime parent chain yet; every user
|
|
501
|
+
# exception inherits a lowered builtin, so all of them are
|
|
502
|
+
# Exception descendants.
|
|
503
|
+
reg = self.tmp()
|
|
504
|
+
self.line(f"{reg} = icmp sge i32 {code}, {USER_EXC_BASE}")
|
|
505
|
+
checks.append(reg)
|
|
506
|
+
numbers = handler.get("codes") if isinstance(handler, dict) else None
|
|
507
|
+
if not numbers:
|
|
508
|
+
numbers = [EXCEPTIONS[name] for name in names if name in EXCEPTIONS]
|
|
509
|
+
for number in numbers:
|
|
510
|
+
if number < USER_EXC_BASE:
|
|
511
|
+
continue
|
|
512
|
+
reg = self.tmp()
|
|
513
|
+
self.line(f"{reg} = icmp eq i32 {code}, {number}")
|
|
514
|
+
checks.append(reg)
|
|
515
|
+
acc: str | None = None
|
|
516
|
+
for reg in checks:
|
|
517
|
+
if acc is None:
|
|
518
|
+
acc = reg
|
|
519
|
+
continue
|
|
520
|
+
nxt = self.tmp()
|
|
521
|
+
self.line(f"{nxt} = or i1 {acc}, {reg}")
|
|
522
|
+
acc = nxt
|
|
523
|
+
if acc is None:
|
|
524
|
+
raise IRError("except handler has no exception name")
|
|
525
|
+
return acc
|
|
526
|
+
|
|
527
|
+
def emit_expr(self, expr: dict[str, Any]) -> str:
|
|
528
|
+
kind = expr["kind"]
|
|
529
|
+
if kind == "const":
|
|
530
|
+
return self.emit_const(expr)
|
|
531
|
+
if kind == "name":
|
|
532
|
+
return self.load(expr["id"])
|
|
533
|
+
if kind == "unary":
|
|
534
|
+
operand = self.emit_expr(expr["operand"])
|
|
535
|
+
if expr["op"] == "-" and expr["type"]["kind"] == "int":
|
|
536
|
+
return self.helper_call("ineg", "i64", [operand])
|
|
537
|
+
if expr["op"] == "-" and expr["type"]["kind"] == "float":
|
|
538
|
+
reg = self.tmp()
|
|
539
|
+
self.line(f"{reg} = fneg double {operand}")
|
|
540
|
+
return reg
|
|
541
|
+
if expr["op"] == "-" and expr["type"]["kind"] == "class":
|
|
542
|
+
mem = self._alloc_class("TimeDelta")
|
|
543
|
+
self._abi_decl("ps_delta_neg", "void", ["ptr", "ptr"])
|
|
544
|
+
self._call_void("ps_delta_neg", [f"ptr {operand}", f"ptr {mem}"])
|
|
545
|
+
return mem
|
|
546
|
+
if expr["op"] == "not":
|
|
547
|
+
reg = self.tmp()
|
|
548
|
+
self.line(f"{reg} = xor i1 {operand}, true")
|
|
549
|
+
return reg
|
|
550
|
+
raise IRError(f"cannot emit unary {expr['op']}")
|
|
551
|
+
if kind == "binary":
|
|
552
|
+
return self.emit_binary(expr, self.emit_expr(expr["left"]), self.emit_expr(expr["right"]))
|
|
553
|
+
if kind == "compare":
|
|
554
|
+
return self.emit_compare(expr)
|
|
555
|
+
if kind == "call":
|
|
556
|
+
return self.emit_call(expr)
|
|
557
|
+
if kind == "list":
|
|
558
|
+
return self.emit_list(expr)
|
|
559
|
+
if kind == "dict":
|
|
560
|
+
return self.emit_dict(expr)
|
|
561
|
+
if kind == "subscript":
|
|
562
|
+
return self.emit_subscript(expr)
|
|
563
|
+
if kind == "attr":
|
|
564
|
+
return self.emit_attr(expr)
|
|
565
|
+
if kind == "cast":
|
|
566
|
+
value = self.emit_expr(expr["value"])
|
|
567
|
+
reg = self.tmp()
|
|
568
|
+
self.line(f"{reg} = sitofp i64 {value} to double")
|
|
569
|
+
return reg
|
|
570
|
+
if kind == "contains":
|
|
571
|
+
return self.emit_contains(expr)
|
|
572
|
+
raise IRError(f"cannot emit expression {kind}")
|
|
573
|
+
|
|
574
|
+
def emit_const(self, expr: dict[str, Any]) -> str:
|
|
575
|
+
kind = expr["type"]["kind"]
|
|
576
|
+
value = expr["value"]
|
|
577
|
+
if kind == "int":
|
|
578
|
+
number = int(value)
|
|
579
|
+
if not fits_i64(number):
|
|
580
|
+
raise FitError(f"integer literal {value} does not fit in i64; refusing to truncate")
|
|
581
|
+
return str(number)
|
|
582
|
+
if kind == "bool":
|
|
583
|
+
return "true" if value else "false"
|
|
584
|
+
if kind == "float":
|
|
585
|
+
return _llvm_double(float(value))
|
|
586
|
+
if kind == "str":
|
|
587
|
+
return self.string_value(value)
|
|
588
|
+
if kind == "bytes":
|
|
589
|
+
return self.string_value(value, binary=True)
|
|
590
|
+
if kind == "none":
|
|
591
|
+
raise IRError("None is not a runtime value")
|
|
592
|
+
raise IRError(f"cannot emit constant of type {kind}")
|
|
593
|
+
|
|
594
|
+
def emit_binary(self, expr: dict[str, Any], left: str, right: str) -> str:
|
|
595
|
+
kind = expr["type"]["kind"]
|
|
596
|
+
op = expr["op"]
|
|
597
|
+
if kind == "int":
|
|
598
|
+
return self.helper_call(_INT_HELPER[op], "i64", [left, right])
|
|
599
|
+
if kind == "float":
|
|
600
|
+
reg = self.tmp()
|
|
601
|
+
self.line(f"{reg} = {_FOP[op]} double {left}, {right}")
|
|
602
|
+
return reg
|
|
603
|
+
if kind in {"str", "bytes"} and op == "+":
|
|
604
|
+
return self.helper_call("strconcat", "%ps.str", [left, right], arg_type="%ps.str")
|
|
605
|
+
if expr["left"]["type"]["kind"] == "class":
|
|
606
|
+
return self._emit_class_binary(expr, left, right)
|
|
607
|
+
raise IRError(f"cannot emit {op} on {kind}")
|
|
608
|
+
|
|
609
|
+
def emit_compare(self, expr: dict[str, Any]) -> str:
|
|
610
|
+
left_kind = expr["left"]["type"]["kind"]
|
|
611
|
+
right_kind = expr["right"]["type"]["kind"]
|
|
612
|
+
op = expr["op"]
|
|
613
|
+
if left_kind == "none" or right_kind == "none":
|
|
614
|
+
if op not in {"==", "!="}:
|
|
615
|
+
raise IRError(f"cannot compare None with {op}")
|
|
616
|
+
self._emit_none_side(expr["left"])
|
|
617
|
+
self._emit_none_side(expr["right"])
|
|
618
|
+
# None equals only None. A str, int, or other value is never None.
|
|
619
|
+
same = left_kind == "none" and right_kind == "none"
|
|
620
|
+
truth = same if op == "==" else not same
|
|
621
|
+
reg = self.tmp()
|
|
622
|
+
bit = "true" if truth else "false"
|
|
623
|
+
self.line(f"{reg} = icmp eq i1 {bit}, true")
|
|
624
|
+
return reg
|
|
625
|
+
left = self.emit_expr(expr["left"])
|
|
626
|
+
right = self.emit_expr(expr["right"])
|
|
627
|
+
kind = left_kind
|
|
628
|
+
reg = self.tmp()
|
|
629
|
+
if kind in {"int", "bool"}:
|
|
630
|
+
self.line(f"{reg} = icmp {_ICMP[op]} {'i64' if kind == 'int' else 'i1'} {left}, {right}")
|
|
631
|
+
return reg
|
|
632
|
+
if kind == "float":
|
|
633
|
+
pred = {"==": "oeq", "!=": "une", "<": "olt", "<=": "ole", ">": "ogt", ">=": "oge"}[op]
|
|
634
|
+
self.line(f"{reg} = fcmp {pred} double {left}, {right}")
|
|
635
|
+
return reg
|
|
636
|
+
if kind in {"str", "bytes"}:
|
|
637
|
+
cmp = self._buf_cmp(left, right)
|
|
638
|
+
pred = {"==": "eq", "!=": "ne", "<": "slt", "<=": "sle", ">": "sgt", ">=": "sge"}[op]
|
|
639
|
+
self.line(f"{reg} = icmp {pred} i32 {cmp}, 0")
|
|
640
|
+
return reg
|
|
641
|
+
if kind == "class":
|
|
642
|
+
return self._emit_class_cmp(expr, left, right)
|
|
643
|
+
raise IRError(f"cannot compare {kind}")
|
|
644
|
+
|
|
645
|
+
def _emit_none_side(self, expr: dict[str, Any]) -> None:
|
|
646
|
+
"""Emit a call that returns None. A None constant or a None local is not a register."""
|
|
647
|
+
if expr["type"]["kind"] != "none":
|
|
648
|
+
self.emit_expr(expr)
|
|
649
|
+
return
|
|
650
|
+
if expr["kind"] == "const" or expr["kind"] == "name":
|
|
651
|
+
return
|
|
652
|
+
self.emit_expr(expr)
|
|
653
|
+
|
|
654
|
+
def emit_call(self, expr: dict[str, Any]) -> str:
|
|
655
|
+
callee = expr["callee"]
|
|
656
|
+
values = [self.emit_expr(arg) for arg in expr["args"]]
|
|
657
|
+
if callee["kind"] == "builtin":
|
|
658
|
+
return self.emit_builtin(callee["name"], values, [arg["type"] for arg in expr["args"]], expr)
|
|
659
|
+
if callee["kind"] == "module":
|
|
660
|
+
return self.emit_module_call(expr)
|
|
661
|
+
if callee["kind"] == "method":
|
|
662
|
+
return self.emit_method(expr, values)
|
|
663
|
+
if callee["kind"] == "class":
|
|
664
|
+
return self.emit_constructor(callee["name"], values)
|
|
665
|
+
return self.call_function(self.functions[callee["name"]], values)
|
|
666
|
+
|
|
667
|
+
def emit_builtin(self, name: str, values: list[str], arg_types: list[dict[str, Any]], expr: dict[str, Any]) -> str:
|
|
668
|
+
if name == "print":
|
|
669
|
+
self._emit_print(values, arg_types, expr)
|
|
670
|
+
return ""
|
|
671
|
+
if name == "len" and arg_types[0]["kind"] in {"str", "bytes"}:
|
|
672
|
+
reg = self.tmp()
|
|
673
|
+
self.line(f"{reg} = extractvalue %ps.str {values[0]}, 0")
|
|
674
|
+
return reg
|
|
675
|
+
if name == "len" and arg_types[0]["kind"] == "list":
|
|
676
|
+
self.needed.add("seq")
|
|
677
|
+
return self._call_ret("i64", "ps_seq_len", [f"ptr {values[0]}"])
|
|
678
|
+
if name == "len" and arg_types[0]["kind"] == "dict":
|
|
679
|
+
self.needed.add("map")
|
|
680
|
+
return self._call_ret("i64", "ps_map_len", [f"ptr {values[0]}"])
|
|
681
|
+
if name == "len" and arg_types[0]["kind"] == "class" and arg_types[0].get("name") == "Deque":
|
|
682
|
+
self._abi_decl("ps_deque_len", "i64", ["ptr"])
|
|
683
|
+
return self._call_ret("i64", "ps_deque_len", [f"ptr {values[0]}"])
|
|
684
|
+
if name in {"min", "max"}:
|
|
685
|
+
return self._emit_minmax(name, values, arg_types)
|
|
686
|
+
if name == "int":
|
|
687
|
+
if arg_types[0]["kind"] in {"bytes", "str"}:
|
|
688
|
+
self._abi_decl("ps_int_from_bytes", "i64", ["i64", "ptr"])
|
|
689
|
+
length, ptr = self._buf_parts(values[0])
|
|
690
|
+
return self._call_ret("i64", "ps_int_from_bytes", [f"i64 {length}", f"ptr {ptr}"])
|
|
691
|
+
self.needed.add("math")
|
|
692
|
+
return self._call_ret("i64", "ps_float_to_int", [f"double {values[0]}"])
|
|
693
|
+
if name == "float" and arg_types[0]["kind"] == "str":
|
|
694
|
+
self._abi_decl("ps_float_from_str", "double", ["i64", "ptr"])
|
|
695
|
+
length, ptr = self._buf_parts(values[0])
|
|
696
|
+
return self._call_ret("double", "ps_float_from_str", [f"i64 {length}", f"ptr {ptr}"])
|
|
697
|
+
if name == "str":
|
|
698
|
+
kind = arg_types[0]["kind"]
|
|
699
|
+
if kind == "str":
|
|
700
|
+
return values[0]
|
|
701
|
+
if kind == "int":
|
|
702
|
+
self._abi_decl("ps_i64_str", "void", ["i64", "ptr"])
|
|
703
|
+
return self._buf_out("ps_i64_str", [f"i64 {values[0]}"])
|
|
704
|
+
if kind == "float":
|
|
705
|
+
self._abi_decl("ps_f64_str", "void", ["double", "ptr"])
|
|
706
|
+
return self._buf_out("ps_f64_str", [f"double {values[0]}"])
|
|
707
|
+
if kind == "bool":
|
|
708
|
+
t_len = self.tmp()
|
|
709
|
+
t_ptr = self.tmp()
|
|
710
|
+
self.line(f"{t_len} = select i1 {values[0]}, i64 4, i64 5")
|
|
711
|
+
self.line(
|
|
712
|
+
f"{t_ptr} = select i1 {values[0]}, ptr {self.format_global('boolT', b'True')}, "
|
|
713
|
+
f"ptr {self.format_global('boolF', b'False')}"
|
|
714
|
+
)
|
|
715
|
+
agg = self.tmp()
|
|
716
|
+
self.line(f"{agg} = insertvalue %ps.str poison, i64 {t_len}, 0")
|
|
717
|
+
ret = self.tmp()
|
|
718
|
+
self.line(f"{ret} = insertvalue %ps.str {agg}, ptr {t_ptr}, 1")
|
|
719
|
+
return ret
|
|
720
|
+
if kind == "bytes":
|
|
721
|
+
self._abi_decl("ps_repr_bytes", "void", ["i64", "ptr", "ptr"])
|
|
722
|
+
length, ptr = self._buf_parts(values[0])
|
|
723
|
+
return self._buf_out("ps_repr_bytes", [f"i64 {length}", f"ptr {ptr}"])
|
|
724
|
+
if kind == "class" and arg_types[0].get("name") == "Path":
|
|
725
|
+
self._abi_decl("ps_path_posix", "void", ["ptr", "ptr"])
|
|
726
|
+
return self._buf_out("ps_path_posix", [f"ptr {values[0]}"])
|
|
727
|
+
raise IRError(f"no LLVM lowering for builtin {name}")
|
|
728
|
+
|
|
729
|
+
def emit_module_call(self, expr: dict[str, Any]) -> str:
|
|
730
|
+
callee = expr["callee"]
|
|
731
|
+
module = callee["module"]
|
|
732
|
+
name = callee["name"]
|
|
733
|
+
args = expr["args"]
|
|
734
|
+
values = [self.emit_expr(arg) for arg in args]
|
|
735
|
+
if expr.get("fn"):
|
|
736
|
+
return self._emit_reduce(expr)
|
|
737
|
+
if name == "exit":
|
|
738
|
+
return self._emit_exit(values[0])
|
|
739
|
+
if module == "atexit" and name == "register":
|
|
740
|
+
return self._emit_atexit(expr)
|
|
741
|
+
if expr.get("symbol") == "ps_path_join":
|
|
742
|
+
return self._emit_join(values)
|
|
743
|
+
if expr.get("symbol") in {"ps_deque_append", "ps_deque_appendleft"}:
|
|
744
|
+
return self._emit_deque_store(expr, values)
|
|
745
|
+
if expr.get("symbol") in {"ps_deque_pop", "ps_deque_popleft"}:
|
|
746
|
+
return self._emit_deque_load(expr, values)
|
|
747
|
+
if expr.get("symbol") == "ps_list_push":
|
|
748
|
+
return self._emit_list_push(expr, values)
|
|
749
|
+
if expr.get("symbol") == "ps_map_del":
|
|
750
|
+
self.needed.add("map")
|
|
751
|
+
key = self._stash(expr["args"][1]["type"], values[1])
|
|
752
|
+
self._abi_decl("ps_map_del", "void", ["ptr", "ptr"])
|
|
753
|
+
self._call_void("ps_map_del", [f"ptr {values[0]}", f"ptr {key}"])
|
|
754
|
+
return ""
|
|
755
|
+
if expr.get("symbol") == "ps_dict_items":
|
|
756
|
+
# The element is a synthetic tuple_N class; the C side packs key and
|
|
757
|
+
# value cells at byte offsets read off the class layout.
|
|
758
|
+
tname = expr["type"]["elem"]["name"]
|
|
759
|
+
size_ptr = self.tmp()
|
|
760
|
+
size = self.tmp()
|
|
761
|
+
self.line(f"{size_ptr} = getelementptr %cls.{tname}, ptr null, i64 1")
|
|
762
|
+
self.line(f"{size} = ptrtoint ptr {size_ptr} to i64")
|
|
763
|
+
voff_ptr = self.tmp()
|
|
764
|
+
voff = self.tmp()
|
|
765
|
+
self.line(f"{voff_ptr} = getelementptr %cls.{tname}, ptr null, i32 0, i32 1")
|
|
766
|
+
self.line(f"{voff} = ptrtoint ptr {voff_ptr} to i64")
|
|
767
|
+
self._abi_decl("ps_dict_items", "ptr", ["ptr", "i64", "i64"])
|
|
768
|
+
return self._call_ret("ptr", "ps_dict_items", [f"ptr {values[0]}", f"i64 {voff}", f"i64 {size}"])
|
|
769
|
+
if expr.get("symbol"):
|
|
770
|
+
return self._emit_abi(expr, values)
|
|
771
|
+
if module == "math":
|
|
772
|
+
return self._emit_math(name, values)
|
|
773
|
+
if module == "re":
|
|
774
|
+
self.needed.add("re")
|
|
775
|
+
return self._emit_re(name, values)
|
|
776
|
+
if module == "json":
|
|
777
|
+
self.needed.add("json")
|
|
778
|
+
if name == "loads":
|
|
779
|
+
return self._emit_json_loads(values[0], expr["type"])
|
|
780
|
+
return self._emit_json_dumps(values[0], args[0]["type"])
|
|
781
|
+
if module == "urllib.parse":
|
|
782
|
+
self.needed.add("url")
|
|
783
|
+
return self._emit_url(name, values)
|
|
784
|
+
if module == "base64":
|
|
785
|
+
self.needed.add("b64")
|
|
786
|
+
return self._emit_b64(name, values)
|
|
787
|
+
if module == "hashlib":
|
|
788
|
+
self.needed.add("hash")
|
|
789
|
+
return self._emit_hash(name, values)
|
|
790
|
+
if module == "struct":
|
|
791
|
+
self.needed.add("struct")
|
|
792
|
+
return self._emit_struct(name, values, args, expr["type"])
|
|
793
|
+
raise IRError(f"no LLVM lowering for {module}.{name}")
|
|
794
|
+
|
|
795
|
+
def _emit_math(self, name: str, values: list[str]) -> str:
|
|
796
|
+
spec = MATH_FUNCTIONS[name]
|
|
797
|
+
if name == "isnan":
|
|
798
|
+
reg = self.tmp()
|
|
799
|
+
self.line(f"{reg} = fcmp uno double {values[0]}, 0.0")
|
|
800
|
+
return reg
|
|
801
|
+
if name == "isinf":
|
|
802
|
+
abs_reg = self.tmp()
|
|
803
|
+
self.line(f"{abs_reg} = call double @llvm.fabs.f64(double {values[0]})")
|
|
804
|
+
self.needed.add("fabs")
|
|
805
|
+
reg = self.tmp()
|
|
806
|
+
self.line(f"{reg} = fcmp oeq double {abs_reg}, 0x7FF0000000000000")
|
|
807
|
+
return reg
|
|
808
|
+
self.needed.add("math")
|
|
809
|
+
return self._call_ret(spec["llvm"], spec["symbol"], [f"double {value}" for value in values])
|
|
810
|
+
|
|
811
|
+
def _emit_re(self, name: str, values: list[str]) -> str:
|
|
812
|
+
if name == "compile":
|
|
813
|
+
return self._emit_pattern(values[0])
|
|
814
|
+
if name == "escape":
|
|
815
|
+
length, ptr = self._buf_parts(values[0])
|
|
816
|
+
return self._buf_out("ps_re_escape", [f"i64 {length}", f"ptr {ptr}"])
|
|
817
|
+
if name == "findall":
|
|
818
|
+
pattern_len, pattern_ptr = self._buf_parts(values[0])
|
|
819
|
+
text_len, text_ptr = self._buf_parts(values[1])
|
|
820
|
+
return self._call_ret(
|
|
821
|
+
"ptr",
|
|
822
|
+
"ps_re_findall",
|
|
823
|
+
[f"i64 {pattern_len}", f"ptr {pattern_ptr}", f"i64 {text_len}", f"ptr {text_ptr}"],
|
|
824
|
+
)
|
|
825
|
+
if name == "split":
|
|
826
|
+
pattern_len, pattern_ptr = self._buf_parts(values[0])
|
|
827
|
+
text_len, text_ptr = self._buf_parts(values[1])
|
|
828
|
+
return self._call_ret(
|
|
829
|
+
"ptr",
|
|
830
|
+
"ps_re_split",
|
|
831
|
+
[f"i64 {pattern_len}", f"ptr {pattern_ptr}", f"i64 {text_len}", f"ptr {text_ptr}"],
|
|
832
|
+
)
|
|
833
|
+
if name == "sub":
|
|
834
|
+
pattern_len, pattern_ptr = self._buf_parts(values[0])
|
|
835
|
+
repl_len, repl_ptr = self._buf_parts(values[1])
|
|
836
|
+
text_len, text_ptr = self._buf_parts(values[2])
|
|
837
|
+
return self._buf_out(
|
|
838
|
+
"ps_re_sub",
|
|
839
|
+
[
|
|
840
|
+
f"i64 {pattern_len}",
|
|
841
|
+
f"ptr {pattern_ptr}",
|
|
842
|
+
f"i64 {repl_len}",
|
|
843
|
+
f"ptr {repl_ptr}",
|
|
844
|
+
f"i64 {text_len}",
|
|
845
|
+
f"ptr {text_ptr}",
|
|
846
|
+
],
|
|
847
|
+
)
|
|
848
|
+
raise IRError(f"no LLVM lowering for re.{name}")
|
|
849
|
+
|
|
850
|
+
def _emit_pattern(self, pattern: str) -> str:
|
|
851
|
+
self.needed.add("alloc")
|
|
852
|
+
size_ptr = self.tmp()
|
|
853
|
+
size = self.tmp()
|
|
854
|
+
self.line(f"{size_ptr} = getelementptr %cls.Pattern, ptr null, i64 1")
|
|
855
|
+
self.line(f"{size} = ptrtoint ptr {size_ptr} to i64")
|
|
856
|
+
mem = self._call_ret("ptr", "ps_alloc", [f"i64 {size}"])
|
|
857
|
+
slot = self.tmp()
|
|
858
|
+
self.line(f"{slot} = getelementptr %cls.Pattern, ptr {mem}, i32 0, i32 0")
|
|
859
|
+
self.line(f"store %ps.str {pattern}, ptr {slot}")
|
|
860
|
+
return mem
|
|
861
|
+
|
|
862
|
+
def _emit_b64(self, name: str, values: list[str]) -> str:
|
|
863
|
+
symbols = {
|
|
864
|
+
"b64encode": "ps_b64_std_encode",
|
|
865
|
+
"standard_b64encode": "ps_b64_std_encode",
|
|
866
|
+
"urlsafe_b64encode": "ps_b64_url_encode",
|
|
867
|
+
"b64decode": "ps_b64_std_decode",
|
|
868
|
+
"standard_b64decode": "ps_b64_std_decode",
|
|
869
|
+
"urlsafe_b64decode": "ps_b64_url_decode",
|
|
870
|
+
}
|
|
871
|
+
length, ptr = self._buf_parts(values[0])
|
|
872
|
+
return self._buf_out(symbols[name], [f"i64 {length}", f"ptr {ptr}"])
|
|
873
|
+
|
|
874
|
+
def _emit_hash(self, name: str, values: list[str]) -> str:
|
|
875
|
+
symbols = {
|
|
876
|
+
"md5": "ps_hash_md5",
|
|
877
|
+
"sha1": "ps_hash_sha1",
|
|
878
|
+
"sha256": "ps_hash_sha256",
|
|
879
|
+
"sha384": "ps_hash_sha384",
|
|
880
|
+
"sha512": "ps_hash_sha512",
|
|
881
|
+
}
|
|
882
|
+
if name in symbols:
|
|
883
|
+
length, ptr = self._buf_parts(values[0])
|
|
884
|
+
return self._call_ret("ptr", symbols[name], [f"i64 {length}", f"ptr {ptr}"])
|
|
885
|
+
if name == "hexdigest":
|
|
886
|
+
return self._buf_out("ps_hash_hex", [f"ptr {values[0]}"])
|
|
887
|
+
if name == "digest":
|
|
888
|
+
return self._buf_out("ps_hash_digest", [f"ptr {values[0]}"])
|
|
889
|
+
raise IRError(f"no LLVM lowering for hashlib.{name}")
|
|
890
|
+
|
|
891
|
+
def _emit_url(self, name: str, values: list[str]) -> str:
|
|
892
|
+
if name in {"quote", "quote_plus"}:
|
|
893
|
+
text_len, text_ptr = self._buf_parts(values[0])
|
|
894
|
+
safe_len, safe_ptr = self._buf_parts(values[1])
|
|
895
|
+
symbol = "ps_url_quote" if name == "quote" else "ps_url_quote_plus"
|
|
896
|
+
return self._buf_out(symbol, [f"i64 {text_len}", f"ptr {text_ptr}", f"i64 {safe_len}", f"ptr {safe_ptr}"])
|
|
897
|
+
if name in {"unquote", "unquote_plus"}:
|
|
898
|
+
length, ptr = self._buf_parts(values[0])
|
|
899
|
+
symbol = "ps_url_unquote" if name == "unquote" else "ps_url_unquote_plus"
|
|
900
|
+
return self._buf_out(symbol, [f"i64 {length}", f"ptr {ptr}"])
|
|
901
|
+
if name == "urlencode":
|
|
902
|
+
return self._buf_out("ps_url_encode", [f"ptr {values[0]}"])
|
|
903
|
+
if name == "parse_qs":
|
|
904
|
+
length, ptr = self._buf_parts(values[0])
|
|
905
|
+
return self._call_ret("ptr", "ps_url_parse_qs", [f"i64 {length}", f"ptr {ptr}"])
|
|
906
|
+
if name == "urlsplit":
|
|
907
|
+
return self._emit_urlsplit(values[0])
|
|
908
|
+
raise IRError(f"no LLVM lowering for urllib.parse.{name}")
|
|
909
|
+
|
|
910
|
+
def _emit_urlsplit(self, value: str) -> str:
|
|
911
|
+
self.needed.add("alloc")
|
|
912
|
+
size_ptr = self.tmp()
|
|
913
|
+
size = self.tmp()
|
|
914
|
+
self.line(f"{size_ptr} = getelementptr %cls.SplitResult, ptr null, i64 1")
|
|
915
|
+
self.line(f"{size} = ptrtoint ptr {size_ptr} to i64")
|
|
916
|
+
mem = self._call_ret("ptr", "ps_alloc", [f"i64 {size}"])
|
|
917
|
+
length, ptr = self._buf_parts(value)
|
|
918
|
+
self.line(f"call void @ps_url_split(i64 {length}, ptr {ptr}, ptr {mem})")
|
|
919
|
+
return mem
|
|
920
|
+
|
|
921
|
+
def _emit_struct(self, name: str, values: list[str], args: list[dict[str, Any]], typ: dict[str, Any]) -> str:
|
|
922
|
+
if name == "pack":
|
|
923
|
+
return self._emit_struct_pack(values, args)
|
|
924
|
+
elem = typ["elem"]["kind"]
|
|
925
|
+
want = {"int": 1, "float": 2, "bytes": 3}[elem]
|
|
926
|
+
fmt_len, fmt_ptr = self._buf_parts(values[0])
|
|
927
|
+
data_len, data_ptr = self._buf_parts(values[1])
|
|
928
|
+
return self._call_ret(
|
|
929
|
+
"ptr",
|
|
930
|
+
"ps_struct_unpack",
|
|
931
|
+
[f"i64 {fmt_len}", f"ptr {fmt_ptr}", f"i64 {data_len}", f"ptr {data_ptr}", f"i32 {want}"],
|
|
932
|
+
)
|
|
933
|
+
|
|
934
|
+
def _emit_struct_pack(self, values: list[str], args: list[dict[str, Any]]) -> str:
|
|
935
|
+
fmt_len, fmt_ptr = self._buf_parts(values[0])
|
|
936
|
+
payload = list(zip(values[1:], args[1:], strict=True))
|
|
937
|
+
if not payload:
|
|
938
|
+
arg_ptr = "null"
|
|
939
|
+
else:
|
|
940
|
+
self._ensure_arg_type()
|
|
941
|
+
count = len(payload)
|
|
942
|
+
slot = self.tmp()
|
|
943
|
+
self.line(f"{slot} = alloca [{count} x %ps.arg]")
|
|
944
|
+
for index, (value, arg) in enumerate(payload):
|
|
945
|
+
base = self.tmp()
|
|
946
|
+
self.line(f"{base} = getelementptr [{count} x %ps.arg], ptr {slot}, i64 0, i64 {index}")
|
|
947
|
+
self._store_ps_arg(base, value, arg["type"])
|
|
948
|
+
arg_ptr = self.tmp()
|
|
949
|
+
self.line(f"{arg_ptr} = getelementptr [{count} x %ps.arg], ptr {slot}, i64 0, i64 0")
|
|
950
|
+
return self._buf_out(
|
|
951
|
+
"ps_struct_pack", [f"i64 {fmt_len}", f"ptr {fmt_ptr}", f"ptr {arg_ptr}", f"i64 {len(payload)}"]
|
|
952
|
+
)
|
|
953
|
+
|
|
954
|
+
def _ensure_arg_type(self) -> None:
|
|
955
|
+
line = "%ps.arg = type { i32, i32, i64, double, %ps.str }"
|
|
956
|
+
if line not in self.globals:
|
|
957
|
+
self.globals.append(line)
|
|
958
|
+
|
|
959
|
+
def _store_ps_arg(self, base: str, value: str, typ: dict[str, Any]) -> None:
|
|
960
|
+
kind = typ["kind"]
|
|
961
|
+
code = 1 if kind == "int" else 2 if kind == "float" else 3
|
|
962
|
+
kind_ptr = self.tmp()
|
|
963
|
+
self.line(f"{kind_ptr} = getelementptr %ps.arg, ptr {base}, i32 0, i32 0")
|
|
964
|
+
self.line(f"store i32 {code}, ptr {kind_ptr}")
|
|
965
|
+
if kind == "int":
|
|
966
|
+
slot = self.tmp()
|
|
967
|
+
self.line(f"{slot} = getelementptr %ps.arg, ptr {base}, i32 0, i32 2")
|
|
968
|
+
self.line(f"store i64 {value}, ptr {slot}")
|
|
969
|
+
return
|
|
970
|
+
if kind == "float":
|
|
971
|
+
slot = self.tmp()
|
|
972
|
+
self.line(f"{slot} = getelementptr %ps.arg, ptr {base}, i32 0, i32 3")
|
|
973
|
+
self.line(f"store double {value}, ptr {slot}")
|
|
974
|
+
return
|
|
975
|
+
slot = self.tmp()
|
|
976
|
+
self.line(f"{slot} = getelementptr %ps.arg, ptr {base}, i32 0, i32 4")
|
|
977
|
+
self.line(f"store %ps.str {value}, ptr {slot}")
|
|
978
|
+
|
|
979
|
+
def _emit_json_loads(self, text: str, typ: dict[str, Any]) -> str:
|
|
980
|
+
symbol, root = self._json_global(typ)
|
|
981
|
+
length, ptr = self._buf_parts(text)
|
|
982
|
+
slot = self._json_alloca(typ)
|
|
983
|
+
self.line(f"call void @ps_json_loads(i64 {length}, ptr {ptr}, ptr {symbol}, i32 {root}, ptr {slot})")
|
|
984
|
+
return self._json_read(typ, slot)
|
|
985
|
+
|
|
986
|
+
def _emit_json_dumps(self, value: str, typ: dict[str, Any]) -> str:
|
|
987
|
+
symbol, root = self._json_global(typ)
|
|
988
|
+
slot = self._json_write(typ, value)
|
|
989
|
+
return self._buf_out("ps_json_dumps", [f"ptr {symbol}", f"i32 {root}", f"ptr {slot}"])
|
|
990
|
+
|
|
991
|
+
def _json_global(self, typ: dict[str, Any]) -> tuple[str, int]:
|
|
992
|
+
entries: list[tuple[int, int] | None] = []
|
|
993
|
+
|
|
994
|
+
def add(node: dict[str, Any]) -> int:
|
|
995
|
+
idx = len(entries)
|
|
996
|
+
entries.append(None)
|
|
997
|
+
kind = node["kind"]
|
|
998
|
+
if kind == "int":
|
|
999
|
+
entries[idx] = (1, 0)
|
|
1000
|
+
elif kind == "bool":
|
|
1001
|
+
entries[idx] = (2, 0)
|
|
1002
|
+
elif kind == "str":
|
|
1003
|
+
entries[idx] = (3, 0)
|
|
1004
|
+
elif kind == "list":
|
|
1005
|
+
child = add(node["elem"])
|
|
1006
|
+
entries[idx] = (4, child)
|
|
1007
|
+
elif kind == "dict":
|
|
1008
|
+
child = add(node["value"])
|
|
1009
|
+
entries[idx] = (5, child)
|
|
1010
|
+
else:
|
|
1011
|
+
raise IRError(f"no JSON type for {kind}")
|
|
1012
|
+
return idx
|
|
1013
|
+
|
|
1014
|
+
root = add(typ)
|
|
1015
|
+
typed = ", ".join(f"%ps.json.ty {{ i32 {kind}, i32 {child} }}" for kind, child in entries if kind is not None)
|
|
1016
|
+
symbol = f"@ps.json.{self.json_n}"
|
|
1017
|
+
self.json_n += 1
|
|
1018
|
+
if "%ps.json.ty = type { i32, i32 }" not in self.globals:
|
|
1019
|
+
self.globals.append("%ps.json.ty = type { i32, i32 }")
|
|
1020
|
+
self.globals.append(f"{symbol} = private constant [{len(entries)} x %ps.json.ty] [{typed}]")
|
|
1021
|
+
return symbol, root
|
|
1022
|
+
|
|
1023
|
+
def _json_alloca(self, typ: dict[str, Any]) -> str:
|
|
1024
|
+
kind = typ["kind"]
|
|
1025
|
+
if kind == "bool":
|
|
1026
|
+
return self._alloca("i32")
|
|
1027
|
+
if kind in {"list", "dict"}:
|
|
1028
|
+
return self._alloca("ptr")
|
|
1029
|
+
return self._alloca(_llvm_type(typ))
|
|
1030
|
+
|
|
1031
|
+
def _json_read(self, typ: dict[str, Any], slot: str) -> str:
|
|
1032
|
+
kind = typ["kind"]
|
|
1033
|
+
if kind == "bool":
|
|
1034
|
+
raw = self._load("i32", slot)
|
|
1035
|
+
reg = self.tmp()
|
|
1036
|
+
self.line(f"{reg} = trunc i32 {raw} to i1")
|
|
1037
|
+
return reg
|
|
1038
|
+
if kind in {"list", "dict"}:
|
|
1039
|
+
return self._load("ptr", slot)
|
|
1040
|
+
return self._load(_llvm_type(typ), slot)
|
|
1041
|
+
|
|
1042
|
+
def _json_write(self, typ: dict[str, Any], value: str) -> str:
|
|
1043
|
+
slot = self._json_alloca(typ)
|
|
1044
|
+
kind = typ["kind"]
|
|
1045
|
+
if kind == "bool":
|
|
1046
|
+
wide = self.tmp()
|
|
1047
|
+
self.line(f"{wide} = zext i1 {value} to i32")
|
|
1048
|
+
self.line(f"store i32 {wide}, ptr {slot}")
|
|
1049
|
+
return slot
|
|
1050
|
+
if kind in {"list", "dict"}:
|
|
1051
|
+
self.line(f"store ptr {value}, ptr {slot}")
|
|
1052
|
+
return slot
|
|
1053
|
+
self.line(f"store {_llvm_type(typ)} {value}, ptr {slot}")
|
|
1054
|
+
return slot
|
|
1055
|
+
|
|
1056
|
+
def _buf_out(self, symbol: str, args: list[str]) -> str:
|
|
1057
|
+
out = self._alloca("%ps.str")
|
|
1058
|
+
self.line(f"call void @{symbol}({', '.join([*args, f'ptr {out}'])})")
|
|
1059
|
+
return self._load("%ps.str", out)
|
|
1060
|
+
|
|
1061
|
+
def _abi_decl(self, symbol: str, ret: str, params: list[str]) -> None:
|
|
1062
|
+
self.abi_decls[symbol] = (ret, params)
|
|
1063
|
+
|
|
1064
|
+
def _abi_lines(self) -> list[str]:
|
|
1065
|
+
lines = []
|
|
1066
|
+
for symbol, (ret, params) in self.abi_decls.items():
|
|
1067
|
+
if symbol == "exit":
|
|
1068
|
+
lines.append("declare void @exit(i32) noreturn")
|
|
1069
|
+
continue
|
|
1070
|
+
rendered = ", ".join(params)
|
|
1071
|
+
lines.append(f"declare {ret} @{symbol}({rendered})")
|
|
1072
|
+
return lines
|
|
1073
|
+
|
|
1074
|
+
def _call_void(self, name: str, args: list[str]) -> None:
|
|
1075
|
+
self.line(f"call void @{name}({', '.join(args)})")
|
|
1076
|
+
|
|
1077
|
+
def _flag(self, raw: str) -> str:
|
|
1078
|
+
reg = self.tmp()
|
|
1079
|
+
self.line(f"{reg} = icmp ne i32 {raw}, 0")
|
|
1080
|
+
return reg
|
|
1081
|
+
|
|
1082
|
+
def _alloc_class(self, name: str) -> str:
|
|
1083
|
+
self.needed.add("alloc")
|
|
1084
|
+
size_ptr = self.tmp()
|
|
1085
|
+
size = self.tmp()
|
|
1086
|
+
self.line(f"{size_ptr} = getelementptr %cls.{name}, ptr null, i64 1")
|
|
1087
|
+
self.line(f"{size} = ptrtoint ptr {size_ptr} to i64")
|
|
1088
|
+
return self._call_ret("ptr", "ps_alloc", [f"i64 {size}"])
|
|
1089
|
+
|
|
1090
|
+
def _c_pieces(self, value: str, typ: dict[str, Any]) -> list[tuple[str, str]]:
|
|
1091
|
+
kind = typ["kind"]
|
|
1092
|
+
if kind == "int":
|
|
1093
|
+
return [(f"i64 {value}", "i64")]
|
|
1094
|
+
if kind == "float":
|
|
1095
|
+
return [(f"double {value}", "double")]
|
|
1096
|
+
if kind in {"str", "bytes"}:
|
|
1097
|
+
length, ptr = self._buf_parts(value)
|
|
1098
|
+
return [(f"i64 {length}", "i64"), (f"ptr {ptr}", "ptr")]
|
|
1099
|
+
if kind in {"list", "dict", "class"}:
|
|
1100
|
+
return [(f"ptr {value}", "ptr")]
|
|
1101
|
+
raise IRError(f"cannot pass {kind} to a runtime call")
|
|
1102
|
+
|
|
1103
|
+
def _emit_abi(self, expr: dict[str, Any], values: list[str]) -> str:
|
|
1104
|
+
symbol = expr["symbol"]
|
|
1105
|
+
rendered: list[str] = []
|
|
1106
|
+
types: list[str] = []
|
|
1107
|
+
for value, arg in zip(values, expr["args"], strict=True):
|
|
1108
|
+
for piece, typ in self._c_pieces(value, arg["type"]):
|
|
1109
|
+
rendered.append(piece)
|
|
1110
|
+
types.append(typ)
|
|
1111
|
+
ret = expr["type"]
|
|
1112
|
+
kind = ret["kind"]
|
|
1113
|
+
if kind in {"str", "bytes"}:
|
|
1114
|
+
self._abi_decl(symbol, "void", [*types, "ptr"])
|
|
1115
|
+
return self._buf_out(symbol, rendered)
|
|
1116
|
+
if kind == "int":
|
|
1117
|
+
self._abi_decl(symbol, "i64", types)
|
|
1118
|
+
return self._call_ret("i64", symbol, rendered)
|
|
1119
|
+
if kind == "float":
|
|
1120
|
+
self._abi_decl(symbol, "double", types)
|
|
1121
|
+
return self._call_ret("double", symbol, rendered)
|
|
1122
|
+
if kind == "bool":
|
|
1123
|
+
self._abi_decl(symbol, "i32", types)
|
|
1124
|
+
return self._flag(self._call_ret("i32", symbol, rendered))
|
|
1125
|
+
if kind == "none":
|
|
1126
|
+
self._abi_decl(symbol, "void", types)
|
|
1127
|
+
self._call_void(symbol, rendered)
|
|
1128
|
+
return ""
|
|
1129
|
+
if kind == "list" or (kind == "class" and ret.get("name") in {"Deque", "TextIO", "BytesIO"}):
|
|
1130
|
+
self._abi_decl(symbol, "ptr", types)
|
|
1131
|
+
return self._call_ret("ptr", symbol, rendered)
|
|
1132
|
+
if kind == "class":
|
|
1133
|
+
self._abi_decl(symbol, "void", [*types, "ptr"])
|
|
1134
|
+
mem = self._alloc_class(ret["name"])
|
|
1135
|
+
self._call_void(symbol, [*rendered, f"ptr {mem}"])
|
|
1136
|
+
return mem
|
|
1137
|
+
raise IRError(f"cannot emit {symbol}")
|
|
1138
|
+
|
|
1139
|
+
def _emit_list_push(self, expr: dict[str, Any], values: list[str]) -> str:
|
|
1140
|
+
seq, elem = values
|
|
1141
|
+
slot = self._stash(expr["args"][1]["type"], elem)
|
|
1142
|
+
self._abi_decl("ps_seq_push", "void", ["ptr", "ptr"])
|
|
1143
|
+
self._call_void("ps_seq_push", [f"ptr {seq}", f"ptr {slot}"])
|
|
1144
|
+
return ""
|
|
1145
|
+
|
|
1146
|
+
def _emit_atexit(self, expr: dict[str, Any]) -> str:
|
|
1147
|
+
name = expr["args"][0]["value"]
|
|
1148
|
+
fn = self.functions.get(name)
|
|
1149
|
+
if fn is None:
|
|
1150
|
+
raise IRError(f"atexit.register target {name} was not lowered")
|
|
1151
|
+
symbol = _fn_symbol(name)
|
|
1152
|
+
wrap = f"ps.atexit.{self.atexit_n}"
|
|
1153
|
+
self.atexit_n += 1
|
|
1154
|
+
ret = fn["returnType"]
|
|
1155
|
+
if ret["kind"] == "none":
|
|
1156
|
+
body = f" call void @{symbol}()\n ret void\n"
|
|
1157
|
+
else:
|
|
1158
|
+
body = f" call {_llvm_type(ret)} @{symbol}()\n ret void\n"
|
|
1159
|
+
self.outline_lines.append(f"define internal void @{wrap}() {{\nentry:\n{body}}}")
|
|
1160
|
+
self._abi_decl("atexit", "i32", ["ptr"])
|
|
1161
|
+
reg = self.tmp()
|
|
1162
|
+
self.line(f"{reg} = call i32 @atexit(ptr @{wrap})")
|
|
1163
|
+
return ""
|
|
1164
|
+
|
|
1165
|
+
def _emit_exit(self, value: str) -> str:
|
|
1166
|
+
self._abi_decl("exit", "void", ["i32"])
|
|
1167
|
+
narrow = self.tmp()
|
|
1168
|
+
self.line(f"{narrow} = trunc i64 {value} to i32")
|
|
1169
|
+
self._call_void("exit", [f"i32 {narrow}"])
|
|
1170
|
+
self.line("unreachable")
|
|
1171
|
+
self.terminated = True
|
|
1172
|
+
return ""
|
|
1173
|
+
|
|
1174
|
+
def _emit_join(self, values: list[str]) -> str:
|
|
1175
|
+
count = len(values)
|
|
1176
|
+
slot = self.tmp()
|
|
1177
|
+
self.line(f"{slot} = alloca [{count} x %ps.str]")
|
|
1178
|
+
for index, value in enumerate(values):
|
|
1179
|
+
dest = self.tmp()
|
|
1180
|
+
self.line(f"{dest} = getelementptr [{count} x %ps.str], ptr {slot}, i64 0, i64 {index}")
|
|
1181
|
+
self.line(f"store %ps.str {value}, ptr {dest}")
|
|
1182
|
+
first = self.tmp()
|
|
1183
|
+
self.line(f"{first} = getelementptr [{count} x %ps.str], ptr {slot}, i64 0, i64 0")
|
|
1184
|
+
self._abi_decl("ps_path_join", "void", ["ptr", "i64", "ptr"])
|
|
1185
|
+
return self._buf_out("ps_path_join", [f"ptr {first}", f"i64 {count}"])
|
|
1186
|
+
|
|
1187
|
+
def _emit_deque_store(self, expr: dict[str, Any], values: list[str]) -> str:
|
|
1188
|
+
slot = self._stash(expr["args"][1]["type"], values[1])
|
|
1189
|
+
symbol = expr["symbol"]
|
|
1190
|
+
self._abi_decl(symbol, "void", ["ptr", "ptr"])
|
|
1191
|
+
self._call_void(symbol, [f"ptr {values[0]}", f"ptr {slot}"])
|
|
1192
|
+
return ""
|
|
1193
|
+
|
|
1194
|
+
def _emit_deque_load(self, expr: dict[str, Any], values: list[str]) -> str:
|
|
1195
|
+
out = self._alloca(_llvm_type(expr["type"]))
|
|
1196
|
+
symbol = expr["symbol"]
|
|
1197
|
+
self._abi_decl(symbol, "void", ["ptr", "ptr"])
|
|
1198
|
+
self._call_void(symbol, [f"ptr {values[0]}", f"ptr {out}"])
|
|
1199
|
+
return self._load(_llvm_type(expr["type"]), out)
|
|
1200
|
+
|
|
1201
|
+
def _emit_class_cmp(self, expr: dict[str, Any], left: str, right: str) -> str:
|
|
1202
|
+
name = expr["left"]["type"]["name"]
|
|
1203
|
+
op = expr["op"]
|
|
1204
|
+
if name == "Path":
|
|
1205
|
+
left_ptr = self.tmp()
|
|
1206
|
+
right_ptr = self.tmp()
|
|
1207
|
+
self.line(f"{left_ptr} = getelementptr %cls.Path, ptr {left}, i32 0, i32 0")
|
|
1208
|
+
self.line(f"{right_ptr} = getelementptr %cls.Path, ptr {right}, i32 0, i32 0")
|
|
1209
|
+
cmp = self._buf_cmp(self._load("%ps.str", left_ptr), self._load("%ps.str", right_ptr))
|
|
1210
|
+
else:
|
|
1211
|
+
count = len(self.class_index[name]["fields"])
|
|
1212
|
+
self._abi_decl("ps_i64_lex", "i32", ["ptr", "ptr", "i64"])
|
|
1213
|
+
cmp = self._call_ret("i32", "ps_i64_lex", [f"ptr {left}", f"ptr {right}", f"i64 {count}"])
|
|
1214
|
+
pred = {"==": "eq", "!=": "ne", "<": "slt", "<=": "sle", ">": "sgt", ">=": "sge"}[op]
|
|
1215
|
+
reg = self.tmp()
|
|
1216
|
+
self.line(f"{reg} = icmp {pred} i32 {cmp}, 0")
|
|
1217
|
+
return reg
|
|
1218
|
+
|
|
1219
|
+
def _emit_class_binary(self, expr: dict[str, Any], left: str, right: str) -> str:
|
|
1220
|
+
left_name = expr["left"]["type"]["name"]
|
|
1221
|
+
right_name = expr["right"]["type"]["name"]
|
|
1222
|
+
out_name = expr["type"]["name"]
|
|
1223
|
+
mem = self._alloc_class(out_name)
|
|
1224
|
+
sign = "1" if expr["op"] == "+" else "-1"
|
|
1225
|
+
if left_name in {"Date", "DateTime"} and right_name == "TimeDelta":
|
|
1226
|
+
symbol = "ps_date_add" if left_name == "Date" else "ps_dt_add"
|
|
1227
|
+
self._abi_decl(symbol, "void", ["ptr", "ptr", "i32", "ptr"])
|
|
1228
|
+
self._call_void(symbol, [f"ptr {left}", f"ptr {right}", f"i32 {sign}", f"ptr {mem}"])
|
|
1229
|
+
return mem
|
|
1230
|
+
if left_name == "TimeDelta" and right_name == "TimeDelta":
|
|
1231
|
+
self._abi_decl("ps_delta_add", "void", ["ptr", "ptr", "i32", "ptr"])
|
|
1232
|
+
self._call_void("ps_delta_add", [f"ptr {left}", f"ptr {right}", f"i32 {sign}", f"ptr {mem}"])
|
|
1233
|
+
return mem
|
|
1234
|
+
if expr["op"] == "-" and left_name == right_name and left_name in {"Date", "DateTime"}:
|
|
1235
|
+
symbol = "ps_date_diff" if left_name == "Date" else "ps_dt_diff"
|
|
1236
|
+
self._abi_decl(symbol, "void", ["ptr", "ptr", "ptr"])
|
|
1237
|
+
self._call_void(symbol, [f"ptr {left}", f"ptr {right}", f"ptr {mem}"])
|
|
1238
|
+
return mem
|
|
1239
|
+
raise IRError(f"cannot emit {expr['op']} on {left_name}")
|
|
1240
|
+
|
|
1241
|
+
def _emit_minmax(self, name: str, values: list[str], arg_types: list[dict[str, Any]]) -> str:
|
|
1242
|
+
kind = arg_types[0]["kind"]
|
|
1243
|
+
left, right = values
|
|
1244
|
+
if kind == "int":
|
|
1245
|
+
pred = "sle" if name == "min" else "sge"
|
|
1246
|
+
cmp = self.tmp()
|
|
1247
|
+
self.line(f"{cmp} = icmp {pred} i64 {left}, {right}")
|
|
1248
|
+
reg = self.tmp()
|
|
1249
|
+
self.line(f"{reg} = select i1 {cmp}, i64 {left}, i64 {right}")
|
|
1250
|
+
return reg
|
|
1251
|
+
if kind == "float":
|
|
1252
|
+
pred = "ole" if name == "min" else "oge"
|
|
1253
|
+
cmp = self.tmp()
|
|
1254
|
+
self.line(f"{cmp} = fcmp {pred} double {left}, {right}")
|
|
1255
|
+
reg = self.tmp()
|
|
1256
|
+
self.line(f"{reg} = select i1 {cmp}, double {left}, double {right}")
|
|
1257
|
+
return reg
|
|
1258
|
+
cmp = self._buf_cmp(left, right)
|
|
1259
|
+
pred = "sle" if name == "min" else "sge"
|
|
1260
|
+
flag = self.tmp()
|
|
1261
|
+
self.line(f"{flag} = icmp {pred} i32 {cmp}, 0")
|
|
1262
|
+
reg = self.tmp()
|
|
1263
|
+
self.line(f"{reg} = select i1 {flag}, %ps.str {left}, %ps.str {right}")
|
|
1264
|
+
return reg
|
|
1265
|
+
|
|
1266
|
+
def _emit_reduce(self, expr: dict[str, Any]) -> str:
|
|
1267
|
+
seq = self.emit_expr(expr["args"][0])
|
|
1268
|
+
elem = expr["args"][0]["type"]["elem"]
|
|
1269
|
+
llvm_t = _llvm_type(elem)
|
|
1270
|
+
self.needed.add("seq")
|
|
1271
|
+
length = self._call_ret("i64", "ps_seq_len", [f"ptr {seq}"])
|
|
1272
|
+
acc_slot = self._alloca(llvm_t)
|
|
1273
|
+
idx_slot = self._alloca("i64")
|
|
1274
|
+
if len(expr["args"]) == 2:
|
|
1275
|
+
init = self.emit_expr(expr["args"][1])
|
|
1276
|
+
self.line(f"store {llvm_t} {init}, ptr {acc_slot}")
|
|
1277
|
+
self.line(f"store i64 0, ptr {idx_slot}")
|
|
1278
|
+
else:
|
|
1279
|
+
empty = self.tmp()
|
|
1280
|
+
self.line(f"{empty} = icmp eq i64 {length}, 0")
|
|
1281
|
+
bad = self.fresh("red.empty")
|
|
1282
|
+
ok = self.fresh("red.start")
|
|
1283
|
+
self.line(f"br i1 {empty}, label %{bad}, label %{ok}")
|
|
1284
|
+
self.terminated = True
|
|
1285
|
+
self.block(bad)
|
|
1286
|
+
self.needed.add("exc")
|
|
1287
|
+
self.line("call void @ps_raise_code(i32 1) noreturn")
|
|
1288
|
+
self.line("unreachable")
|
|
1289
|
+
self.terminated = True
|
|
1290
|
+
self.block(ok)
|
|
1291
|
+
first = self._seq_at(seq, "0", elem)
|
|
1292
|
+
self.line(f"store {llvm_t} {first}, ptr {acc_slot}")
|
|
1293
|
+
self.line(f"store i64 1, ptr {idx_slot}")
|
|
1294
|
+
cond_l = self.fresh("red.cond")
|
|
1295
|
+
body_l = self.fresh("red.body")
|
|
1296
|
+
end_l = self.fresh("red.end")
|
|
1297
|
+
self.line(f"br label %{cond_l}")
|
|
1298
|
+
self.terminated = True
|
|
1299
|
+
self.block(cond_l)
|
|
1300
|
+
idx = self._load("i64", idx_slot)
|
|
1301
|
+
cmp = self.tmp()
|
|
1302
|
+
self.line(f"{cmp} = icmp slt i64 {idx}, {length}")
|
|
1303
|
+
self.line(f"br i1 {cmp}, label %{body_l}, label %{end_l}")
|
|
1304
|
+
self.terminated = True
|
|
1305
|
+
self.block(body_l)
|
|
1306
|
+
current = self._load(llvm_t, acc_slot)
|
|
1307
|
+
item = self._seq_at(seq, idx, elem)
|
|
1308
|
+
nxt = self.call_function(self.functions[expr["fn"]], [current, item])
|
|
1309
|
+
self.line(f"store {llvm_t} {nxt}, ptr {acc_slot}")
|
|
1310
|
+
bumped = self.tmp()
|
|
1311
|
+
self.line(f"{bumped} = add i64 {idx}, 1")
|
|
1312
|
+
self.line(f"store i64 {bumped}, ptr {idx_slot}")
|
|
1313
|
+
self.line(f"br label %{cond_l}")
|
|
1314
|
+
self.terminated = True
|
|
1315
|
+
self.block(end_l)
|
|
1316
|
+
return self._load(llvm_t, acc_slot)
|
|
1317
|
+
|
|
1318
|
+
def _seq_at(self, seq: str, index: str, elem: dict[str, Any]) -> str:
|
|
1319
|
+
self.needed.add("seq")
|
|
1320
|
+
out = self._alloca(_llvm_type(elem))
|
|
1321
|
+
self.line(f"call void @ps_seq_get(ptr {seq}, i64 {index}, ptr {out})")
|
|
1322
|
+
return self._load(_llvm_type(elem), out)
|
|
1323
|
+
|
|
1324
|
+
def emit_method(self, expr: dict[str, Any], values: list[str]) -> str:
|
|
1325
|
+
callee = expr["callee"]
|
|
1326
|
+
name = callee["name"]
|
|
1327
|
+
kind = str(callee.get("receiver", "str"))
|
|
1328
|
+
self.needed.add("str")
|
|
1329
|
+
length, ptr = self._buf_parts(values[0])
|
|
1330
|
+
if name in {"strip", "lstrip", "rstrip", "upper", "lower"}:
|
|
1331
|
+
out = self._alloca("%ps.str")
|
|
1332
|
+
self.line(f"call void @ps_str_{name}(i64 {length}, ptr {ptr}, ptr {out})")
|
|
1333
|
+
return self._load("%ps.str", out)
|
|
1334
|
+
if name in {"title", "capitalize", "casefold", "swapcase"}:
|
|
1335
|
+
self._abi_decl(f"ps_str_{name}", "void", ["i64", "ptr", "ptr"])
|
|
1336
|
+
return self._buf_out(f"ps_str_{name}", [f"i64 {length}", f"ptr {ptr}"])
|
|
1337
|
+
if name in {"isdigit", "isalpha", "isalnum", "isspace", "isupper", "islower"}:
|
|
1338
|
+
self._abi_decl(f"ps_str_{name}", "i32", ["i64", "ptr"])
|
|
1339
|
+
return self._flag(self._call_ret("i32", f"ps_str_{name}", [f"i64 {length}", f"ptr {ptr}"]))
|
|
1340
|
+
if name == "encode":
|
|
1341
|
+
self._abi_decl("ps_utf8_encode", "void", ["i64", "ptr", "ptr"])
|
|
1342
|
+
return self._buf_out("ps_utf8_encode", [f"i64 {length}", f"ptr {ptr}"])
|
|
1343
|
+
if name == "decode":
|
|
1344
|
+
self._abi_decl("ps_utf8_decode", "void", ["i64", "ptr", "ptr"])
|
|
1345
|
+
return self._buf_out("ps_utf8_decode", [f"i64 {length}", f"ptr {ptr}"])
|
|
1346
|
+
if name == "splitlines":
|
|
1347
|
+
self._abi_decl("ps_str_splitlines", "ptr", ["i64", "ptr", "i64"])
|
|
1348
|
+
return self._call_ret("ptr", "ps_str_splitlines", [f"i64 {length}", f"ptr {ptr}", "i64 0"])
|
|
1349
|
+
if name == "expandtabs":
|
|
1350
|
+
tabsize = values[1] if len(values) > 1 else "8"
|
|
1351
|
+
self._abi_decl("ps_str_expandtabs", "void", ["i64", "ptr", "i64", "ptr"])
|
|
1352
|
+
return self._buf_out("ps_str_expandtabs", [f"i64 {length}", f"ptr {ptr}", f"i64 {tabsize}"])
|
|
1353
|
+
if name == "zfill":
|
|
1354
|
+
self._abi_decl("ps_str_zfill", "void", ["i64", "ptr", "i64", "ptr"])
|
|
1355
|
+
return self._buf_out("ps_str_zfill", [f"i64 {length}", f"ptr {ptr}", f"i64 {values[1]}"])
|
|
1356
|
+
if name in {"ljust", "rjust", "center"}:
|
|
1357
|
+
side = {"ljust": "0", "rjust": "1", "center": "2"}[name]
|
|
1358
|
+
if len(values) > 2:
|
|
1359
|
+
fill_len, fill_ptr = self._buf_parts(values[2])
|
|
1360
|
+
else:
|
|
1361
|
+
fill_len, fill_ptr = "1", self.format_global("fill", b" ")
|
|
1362
|
+
self._abi_decl("ps_str_justify", "void", ["i64", "ptr", "i64", "i64", "ptr", "i64", "ptr"])
|
|
1363
|
+
return self._buf_out(
|
|
1364
|
+
"ps_str_justify",
|
|
1365
|
+
[
|
|
1366
|
+
f"i64 {length}",
|
|
1367
|
+
f"ptr {ptr}",
|
|
1368
|
+
f"i64 {values[1]}",
|
|
1369
|
+
f"i64 {fill_len}",
|
|
1370
|
+
f"ptr {fill_ptr}",
|
|
1371
|
+
f"i64 {side}",
|
|
1372
|
+
],
|
|
1373
|
+
)
|
|
1374
|
+
if name in {"partition", "rpartition"}:
|
|
1375
|
+
sep_len, sep_ptr = self._buf_parts(values[1])
|
|
1376
|
+
tname = callee["tuple"]
|
|
1377
|
+
mem = self._alloc_class(tname)
|
|
1378
|
+
right = "1" if name == "rpartition" else "0"
|
|
1379
|
+
self._abi_decl("ps_str_partition", "void", ["i64", "ptr", "i64", "ptr", "i64", "ptr"])
|
|
1380
|
+
self._call_void(
|
|
1381
|
+
"ps_str_partition",
|
|
1382
|
+
[
|
|
1383
|
+
f"i64 {length}",
|
|
1384
|
+
f"ptr {ptr}",
|
|
1385
|
+
f"i64 {sep_len}",
|
|
1386
|
+
f"ptr {sep_ptr}",
|
|
1387
|
+
f"i64 {right}",
|
|
1388
|
+
f"ptr {mem}",
|
|
1389
|
+
],
|
|
1390
|
+
)
|
|
1391
|
+
return mem
|
|
1392
|
+
if name in {"split", "rsplit"}:
|
|
1393
|
+
if len(values) == 1:
|
|
1394
|
+
sep_len, sep_ptr = "0", "null"
|
|
1395
|
+
mode = "2" if kind == "bytes" else "1"
|
|
1396
|
+
maxsplit = "-1"
|
|
1397
|
+
else:
|
|
1398
|
+
sep_len, sep_ptr = self._buf_parts(values[1])
|
|
1399
|
+
mode = "0"
|
|
1400
|
+
maxsplit = values[2] if len(values) > 2 else "-1"
|
|
1401
|
+
right = "1" if name == "rsplit" else "0"
|
|
1402
|
+
self._abi_decl("ps_str_split_m", "ptr", ["i64", "ptr", "i64", "ptr", "i64", "i64", "i64"])
|
|
1403
|
+
return self._call_ret(
|
|
1404
|
+
"ptr",
|
|
1405
|
+
"ps_str_split_m",
|
|
1406
|
+
[
|
|
1407
|
+
f"i64 {length}",
|
|
1408
|
+
f"ptr {ptr}",
|
|
1409
|
+
f"i64 {sep_len}",
|
|
1410
|
+
f"ptr {sep_ptr}",
|
|
1411
|
+
f"i64 {mode}",
|
|
1412
|
+
f"i64 {maxsplit}",
|
|
1413
|
+
f"i64 {right}",
|
|
1414
|
+
],
|
|
1415
|
+
)
|
|
1416
|
+
if name == "join":
|
|
1417
|
+
self._abi_decl("ps_str_join", "void", ["i64", "ptr", "ptr", "ptr"])
|
|
1418
|
+
return self._buf_out("ps_str_join", [f"i64 {length}", f"ptr {ptr}", f"ptr {values[1]}"])
|
|
1419
|
+
arg_len, arg_ptr = self._buf_parts(values[1])
|
|
1420
|
+
if name in {"removeprefix", "removesuffix"}:
|
|
1421
|
+
self._abi_decl(f"ps_str_{name}", "void", ["i64", "ptr", "i64", "ptr", "ptr"])
|
|
1422
|
+
return self._buf_out(f"ps_str_{name}", [f"i64 {length}", f"ptr {ptr}", f"i64 {arg_len}", f"ptr {arg_ptr}"])
|
|
1423
|
+
if name == "replace":
|
|
1424
|
+
new_len, new_ptr = self._buf_parts(values[2])
|
|
1425
|
+
out = self._alloca("%ps.str")
|
|
1426
|
+
self.line(
|
|
1427
|
+
"call void @ps_str_replace("
|
|
1428
|
+
f"i64 {length}, ptr {ptr}, i64 {arg_len}, ptr {arg_ptr}, i64 {new_len}, ptr {new_ptr}, ptr {out})"
|
|
1429
|
+
)
|
|
1430
|
+
return self._load("%ps.str", out)
|
|
1431
|
+
if name in {"find", "count", "index", "rfind"}:
|
|
1432
|
+
symbol = {"find": "ps_str_find", "count": "ps_str_count", "index": "ps_str_index", "rfind": "ps_str_rfind"}[
|
|
1433
|
+
name
|
|
1434
|
+
]
|
|
1435
|
+
# ps_str_find is already declared by the "str" runtime block.
|
|
1436
|
+
if name != "find":
|
|
1437
|
+
self._abi_decl(symbol, "i64", ["i64", "ptr", "i64", "ptr"])
|
|
1438
|
+
return self._call_ret("i64", symbol, [f"i64 {length}", f"ptr {ptr}", f"i64 {arg_len}", f"ptr {arg_ptr}"])
|
|
1439
|
+
if name in {"startswith", "endswith"}:
|
|
1440
|
+
flag = self._call_ret(
|
|
1441
|
+
"i32", f"ps_str_{name}", [f"i64 {length}", f"ptr {ptr}", f"i64 {arg_len}", f"ptr {arg_ptr}"]
|
|
1442
|
+
)
|
|
1443
|
+
reg = self.tmp()
|
|
1444
|
+
self.line(f"{reg} = icmp ne i32 {flag}, 0")
|
|
1445
|
+
return reg
|
|
1446
|
+
raise IRError(f"no LLVM lowering for method {name}")
|
|
1447
|
+
|
|
1448
|
+
def emit_constructor(self, name: str, values: list[str]) -> str:
|
|
1449
|
+
self.needed.add("alloc")
|
|
1450
|
+
size_ptr = self.tmp()
|
|
1451
|
+
size = self.tmp()
|
|
1452
|
+
self.line(f"{size_ptr} = getelementptr %cls.{name}, ptr null, i64 1")
|
|
1453
|
+
self.line(f"{size} = ptrtoint ptr {size_ptr} to i64")
|
|
1454
|
+
mem = self._call_ret("ptr", "ps_alloc", [f"i64 {size}"])
|
|
1455
|
+
init = f"cls.{name}.__init__"
|
|
1456
|
+
if init in self.functions:
|
|
1457
|
+
fn = self.functions[init]
|
|
1458
|
+
self.call_function(fn, [mem, *values])
|
|
1459
|
+
return mem
|
|
1460
|
+
|
|
1461
|
+
def emit_list(self, expr: dict[str, Any]) -> str:
|
|
1462
|
+
self.needed.add("seq")
|
|
1463
|
+
elem = expr["type"]["elem"]
|
|
1464
|
+
values = [self.emit_expr(item) for item in expr["elems"]]
|
|
1465
|
+
reg = self._call_ret("ptr", "ps_seq_new", [f"i64 {_elem_size(elem)}", f"i64 {len(values)}"])
|
|
1466
|
+
for index, value in enumerate(values):
|
|
1467
|
+
slot = self._stash(elem, value)
|
|
1468
|
+
self.line(f"call void @ps_seq_set(ptr {reg}, i64 {index}, ptr {slot})")
|
|
1469
|
+
return reg
|
|
1470
|
+
|
|
1471
|
+
def emit_dict(self, expr: dict[str, Any]) -> str:
|
|
1472
|
+
self.needed.add("map")
|
|
1473
|
+
key_t = expr["type"]["key"]
|
|
1474
|
+
val_t = expr["type"]["value"]
|
|
1475
|
+
key_kind = 1 if key_t["kind"] == "int" else 2
|
|
1476
|
+
stored = [(self.emit_expr(pair["key"]), self.emit_expr(pair["value"])) for pair in expr["pairs"]]
|
|
1477
|
+
reg = self._call_ret(
|
|
1478
|
+
"ptr",
|
|
1479
|
+
"ps_map_new",
|
|
1480
|
+
[
|
|
1481
|
+
f"i32 {key_kind}",
|
|
1482
|
+
f"i64 {_elem_size(key_t)}",
|
|
1483
|
+
f"i64 {_elem_size(val_t)}",
|
|
1484
|
+
f"i64 {len(stored)}",
|
|
1485
|
+
],
|
|
1486
|
+
)
|
|
1487
|
+
for key_v, val_v in stored:
|
|
1488
|
+
key = self._stash(key_t, key_v)
|
|
1489
|
+
value = self._stash(val_t, val_v)
|
|
1490
|
+
self.line(f"call void @ps_map_put(ptr {reg}, ptr {key}, ptr {value})")
|
|
1491
|
+
return reg
|
|
1492
|
+
|
|
1493
|
+
def emit_subscript(self, expr: dict[str, Any]) -> str:
|
|
1494
|
+
value = self.emit_expr(expr["value"])
|
|
1495
|
+
index = self.emit_expr(expr["index"])
|
|
1496
|
+
kind = expr["value"]["type"]["kind"]
|
|
1497
|
+
if kind == "list":
|
|
1498
|
+
self.needed.add("seq")
|
|
1499
|
+
out = self._alloca(_llvm_type(expr["type"]))
|
|
1500
|
+
self.line(f"call void @ps_seq_get(ptr {value}, i64 {index}, ptr {out})")
|
|
1501
|
+
return self._load(_llvm_type(expr["type"]), out)
|
|
1502
|
+
if kind == "dict":
|
|
1503
|
+
self.needed.add("map")
|
|
1504
|
+
key = self._stash(expr["index"]["type"], index)
|
|
1505
|
+
out = self._alloca(_llvm_type(expr["type"]))
|
|
1506
|
+
self.line(f"call void @ps_map_get(ptr {value}, ptr {key}, ptr {out})")
|
|
1507
|
+
return self._load(_llvm_type(expr["type"]), out)
|
|
1508
|
+
if kind == "str":
|
|
1509
|
+
self.needed.add("str")
|
|
1510
|
+
length, ptr = self._buf_parts(value)
|
|
1511
|
+
out = self._alloca("%ps.str")
|
|
1512
|
+
self.line(f"call void @ps_str_get(i64 {length}, ptr {ptr}, i64 {index}, ptr {out})")
|
|
1513
|
+
return self._load("%ps.str", out)
|
|
1514
|
+
if kind == "bytes":
|
|
1515
|
+
self.needed.add("str")
|
|
1516
|
+
length, ptr = self._buf_parts(value)
|
|
1517
|
+
return self._call_ret("i64", "ps_buf_at", [f"i64 {length}", f"ptr {ptr}", f"i64 {index}"])
|
|
1518
|
+
if kind == "class" and expr["value"]["type"].get("name") == "Deque":
|
|
1519
|
+
out = self._alloca(_llvm_type(expr["type"]))
|
|
1520
|
+
self._abi_decl("ps_deque_get", "void", ["ptr", "i64", "ptr"])
|
|
1521
|
+
self._call_void("ps_deque_get", [f"ptr {value}", f"i64 {index}", f"ptr {out}"])
|
|
1522
|
+
return self._load(_llvm_type(expr["type"]), out)
|
|
1523
|
+
raise IRError(f"cannot index {kind}")
|
|
1524
|
+
|
|
1525
|
+
def emit_attr(self, expr: dict[str, Any]) -> str:
|
|
1526
|
+
obj = self.emit_expr(expr["value"])
|
|
1527
|
+
field = self._field(expr["value"]["type"]["name"], expr["name"])
|
|
1528
|
+
slot = self._field_ptr(expr["value"]["type"]["name"], obj, field)
|
|
1529
|
+
return self._load(_llvm_type(field["type"]), slot)
|
|
1530
|
+
|
|
1531
|
+
def emit_contains(self, expr: dict[str, Any]) -> str:
|
|
1532
|
+
needle = self.emit_expr(expr["needle"])
|
|
1533
|
+
hay = self.emit_expr(expr["hay"])
|
|
1534
|
+
hay_kind = expr["hay"]["type"]["kind"]
|
|
1535
|
+
if hay_kind in {"str", "bytes"}:
|
|
1536
|
+
self.needed.add("str")
|
|
1537
|
+
hay_len, hay_ptr = self._buf_parts(hay)
|
|
1538
|
+
needle_len, needle_ptr = self._buf_parts(needle)
|
|
1539
|
+
found = self._call_ret(
|
|
1540
|
+
"i64", "ps_str_find", [f"i64 {hay_len}", f"ptr {hay_ptr}", f"i64 {needle_len}", f"ptr {needle_ptr}"]
|
|
1541
|
+
)
|
|
1542
|
+
flag = self.tmp()
|
|
1543
|
+
self.line(f"{flag} = icmp sge i64 {found}, 0")
|
|
1544
|
+
elif hay_kind == "list":
|
|
1545
|
+
self.needed.add("seq")
|
|
1546
|
+
elem = expr["hay"]["type"]["elem"]["kind"]
|
|
1547
|
+
if elem == "int":
|
|
1548
|
+
flag = self._call_ret("i32", "ps_seq_has_i64", [f"ptr {hay}", f"i64 {needle}"])
|
|
1549
|
+
elif elem == "float":
|
|
1550
|
+
flag = self._call_ret("i32", "ps_seq_has_f64", [f"ptr {hay}", f"double {needle}"])
|
|
1551
|
+
else:
|
|
1552
|
+
length, ptr = self._buf_parts(needle)
|
|
1553
|
+
flag = self._call_ret("i32", "ps_seq_has_buf", [f"ptr {hay}", f"i64 {length}", f"ptr {ptr}"])
|
|
1554
|
+
bit = self.tmp()
|
|
1555
|
+
self.line(f"{bit} = icmp ne i32 {flag}, 0")
|
|
1556
|
+
flag = bit
|
|
1557
|
+
elif hay_kind == "dict":
|
|
1558
|
+
self.needed.add("map")
|
|
1559
|
+
key = self._stash(expr["needle"]["type"], needle)
|
|
1560
|
+
raw = self._call_ret("i32", "ps_map_has", [f"ptr {hay}", f"ptr {key}"])
|
|
1561
|
+
flag = self.tmp()
|
|
1562
|
+
self.line(f"{flag} = icmp ne i32 {raw}, 0")
|
|
1563
|
+
else:
|
|
1564
|
+
raise IRError(f"cannot emit containment for {hay_kind}")
|
|
1565
|
+
if not expr["negated"]:
|
|
1566
|
+
return flag
|
|
1567
|
+
reg = self.tmp()
|
|
1568
|
+
self.line(f"{reg} = xor i1 {flag}, true")
|
|
1569
|
+
return reg
|
|
1570
|
+
|
|
1571
|
+
def _field(self, class_name: str, name: str) -> dict[str, Any]:
|
|
1572
|
+
for index, field in enumerate(self.class_index[class_name]["fields"]):
|
|
1573
|
+
if field["name"] == name:
|
|
1574
|
+
return {**field, "index": index}
|
|
1575
|
+
raise IRError(f"{class_name}.{name} is not a field")
|
|
1576
|
+
|
|
1577
|
+
def _field_ptr(self, class_name: str, obj: str, field: dict[str, Any]) -> str:
|
|
1578
|
+
reg = self.tmp()
|
|
1579
|
+
self.line(f"{reg} = getelementptr %cls.{class_name}, ptr {obj}, i32 0, i32 {field['index']}")
|
|
1580
|
+
return reg
|
|
1581
|
+
|
|
1582
|
+
def _stash(self, typ: dict[str, Any], value: str) -> str:
|
|
1583
|
+
slot = self._alloca(_llvm_type(typ))
|
|
1584
|
+
self.line(f"store {_llvm_type(typ)} {value}, ptr {slot}")
|
|
1585
|
+
return slot
|
|
1586
|
+
|
|
1587
|
+
def _alloca(self, llvm_type: str) -> str:
|
|
1588
|
+
slot = self.tmp()
|
|
1589
|
+
self.line(f"{slot} = alloca {llvm_type}")
|
|
1590
|
+
return slot
|
|
1591
|
+
|
|
1592
|
+
def _load(self, llvm_type: str, slot: str) -> str:
|
|
1593
|
+
reg = self.tmp()
|
|
1594
|
+
self.line(f"{reg} = load {llvm_type}, ptr {slot}")
|
|
1595
|
+
return reg
|
|
1596
|
+
|
|
1597
|
+
def _call_ret(self, ret: str, name: str, args: list[str]) -> str:
|
|
1598
|
+
reg = self.tmp()
|
|
1599
|
+
self.line(f"{reg} = call {ret} @{name}({', '.join(args)})")
|
|
1600
|
+
return reg
|
|
1601
|
+
|
|
1602
|
+
def _buf_parts(self, value: str) -> tuple[str, str]:
|
|
1603
|
+
length = self.tmp()
|
|
1604
|
+
ptr = self.tmp()
|
|
1605
|
+
self.line(f"{length} = extractvalue %ps.str {value}, 0")
|
|
1606
|
+
self.line(f"{ptr} = extractvalue %ps.str {value}, 1")
|
|
1607
|
+
return length, ptr
|
|
1608
|
+
|
|
1609
|
+
def _buf_cmp(self, left: str, right: str) -> str:
|
|
1610
|
+
self.needed.add("str")
|
|
1611
|
+
left_len, left_ptr = self._buf_parts(left)
|
|
1612
|
+
right_len, right_ptr = self._buf_parts(right)
|
|
1613
|
+
return self._call_ret(
|
|
1614
|
+
"i32", "ps_str_cmp", [f"i64 {left_len}", f"ptr {left_ptr}", f"i64 {right_len}", f"ptr {right_ptr}"]
|
|
1615
|
+
)
|
|
1616
|
+
|
|
1617
|
+
def call_function(self, fn: dict[str, Any], values: list[str]) -> str:
|
|
1618
|
+
ret = _llvm_type(fn["returnType"])
|
|
1619
|
+
rendered = ", ".join(
|
|
1620
|
+
f"{_llvm_type(param['type'])} {value}" for param, value in zip(fn["params"], values, strict=True)
|
|
1621
|
+
)
|
|
1622
|
+
symbol = _fn_symbol(fn["name"])
|
|
1623
|
+
if ret == "void":
|
|
1624
|
+
self.line(f"call void @{symbol}({rendered})")
|
|
1625
|
+
return ""
|
|
1626
|
+
reg = self.tmp()
|
|
1627
|
+
self.line(f"{reg} = call {ret} @{symbol}({rendered})")
|
|
1628
|
+
return reg
|
|
1629
|
+
|
|
1630
|
+
def helper_call(self, name: str, ret: str, args: list[str], arg_type: str = "i64") -> str:
|
|
1631
|
+
self.needed.add(name)
|
|
1632
|
+
rendered = ", ".join(f"{arg_type} {arg}" for arg in args)
|
|
1633
|
+
reg = self.tmp()
|
|
1634
|
+
self.line(f"{reg} = call {ret} @ps.{name}({rendered})")
|
|
1635
|
+
return reg
|
|
1636
|
+
|
|
1637
|
+
def string_value(self, text: str, binary: bool = False) -> str:
|
|
1638
|
+
data = text.encode("latin1")
|
|
1639
|
+
idx = len(self.string_data)
|
|
1640
|
+
self.string_data.append(data)
|
|
1641
|
+
symbol = f"@ps.str.{idx}"
|
|
1642
|
+
self.globals.append(f'{symbol} = private unnamed_addr constant [{len(data) + 1} x i8] c"{_llvm_bytes(data)}"')
|
|
1643
|
+
first = self.tmp()
|
|
1644
|
+
second = self.tmp()
|
|
1645
|
+
self.line(f"{first} = insertvalue %ps.str poison, i64 {len(data)}, 0")
|
|
1646
|
+
self.line(f"{second} = insertvalue %ps.str {first}, ptr {symbol}, 1")
|
|
1647
|
+
return second
|
|
1648
|
+
|
|
1649
|
+
def materialize_arg(self, typ: dict[str, Any], value: Any) -> str:
|
|
1650
|
+
kind = typ["kind"]
|
|
1651
|
+
if kind == "int":
|
|
1652
|
+
return str(value)
|
|
1653
|
+
if kind == "float":
|
|
1654
|
+
return _llvm_double(value)
|
|
1655
|
+
if kind == "bool":
|
|
1656
|
+
return "true" if value else "false"
|
|
1657
|
+
if kind == "str":
|
|
1658
|
+
return self.string_value(value)
|
|
1659
|
+
raise FitError(f"cannot pass a {kind} argument into main")
|
|
1660
|
+
|
|
1661
|
+
def print_value(self, typ: dict[str, Any], value: str) -> None:
|
|
1662
|
+
kind = typ["kind"]
|
|
1663
|
+
self.needed.add("printf")
|
|
1664
|
+
if kind == "int":
|
|
1665
|
+
fmt = self.format_global("int", b"%lld\n")
|
|
1666
|
+
self.line(f"call i32 (ptr, ...) @printf(ptr {fmt}, i64 {value})")
|
|
1667
|
+
return
|
|
1668
|
+
if kind == "float":
|
|
1669
|
+
fmt = self.format_global("float", b"%.17g\n")
|
|
1670
|
+
self.line(f"call i32 (ptr, ...) @printf(ptr {fmt}, double {value})")
|
|
1671
|
+
return
|
|
1672
|
+
if kind == "str":
|
|
1673
|
+
fmt = self.format_global("str", b"%.*s\n")
|
|
1674
|
+
length = self.tmp()
|
|
1675
|
+
ptr = self.tmp()
|
|
1676
|
+
self.line(f"{length} = extractvalue %ps.str {value}, 0")
|
|
1677
|
+
self.line(f"{ptr} = extractvalue %ps.str {value}, 1")
|
|
1678
|
+
self.line(f"call i32 (ptr, ...) @printf(ptr {fmt}, i64 {length}, ptr {ptr})")
|
|
1679
|
+
return
|
|
1680
|
+
raise IRError(f"cannot print {kind}")
|
|
1681
|
+
|
|
1682
|
+
def _emit_print(self, values: list[str], arg_types: list[dict[str, Any]], expr: dict[str, Any]) -> None:
|
|
1683
|
+
"""`print` with CPython defaults: space separator, newline terminator.
|
|
1684
|
+
|
|
1685
|
+
`sep=`/`end=` str arguments ride on the call dict; when absent the
|
|
1686
|
+
constants hold. Each value writes through `printf`, mirroring
|
|
1687
|
+
`print_value`'s int and str pieces. Floats go through `ps_f64_str` so
|
|
1688
|
+
the text is the CPython `str(float)` spelling (shortest round-trip,
|
|
1689
|
+
`.0`, `1e+16`), which a raw `%g` conversion cannot reproduce. Bools
|
|
1690
|
+
select between the `True` and `False` constants, not the runtime's
|
|
1691
|
+
lowercase literals. Bytes print as their `b'...'` repr.
|
|
1692
|
+
"""
|
|
1693
|
+
self.needed.add("printf")
|
|
1694
|
+
sep = self.emit_expr(expr["sep"]) if expr.get("sep") else None
|
|
1695
|
+
end = self.emit_expr(expr["end"]) if expr.get("end") else None
|
|
1696
|
+
for index, (value, typ) in enumerate(zip(values, arg_types, strict=True)):
|
|
1697
|
+
if index:
|
|
1698
|
+
if sep is None:
|
|
1699
|
+
self._print_fmt("space", b" ")
|
|
1700
|
+
else:
|
|
1701
|
+
slen, sptr = self._buf_parts(sep)
|
|
1702
|
+
self._print_fmt("sep", b"%.*s", [f"i64 {slen}", f"ptr {sptr}"])
|
|
1703
|
+
kind = typ["kind"]
|
|
1704
|
+
if kind == "int":
|
|
1705
|
+
self._print_fmt("int", b"%lld", [f"i64 {value}"])
|
|
1706
|
+
elif kind == "float":
|
|
1707
|
+
out = self._buf_out("ps_f64_str", [f"double {value}"])
|
|
1708
|
+
length, ptr = self._buf_parts(out)
|
|
1709
|
+
self._abi_decl("ps_f64_str", "void", ["double", "ptr"])
|
|
1710
|
+
self._print_fmt("str", b"%.*s", [f"i64 {length}", f"ptr {ptr}"])
|
|
1711
|
+
elif kind == "bool":
|
|
1712
|
+
picked = self.tmp()
|
|
1713
|
+
self.line(
|
|
1714
|
+
f"{picked} = select i1 {value}, ptr {self.format_global('boolT', b'True')}, "
|
|
1715
|
+
f"ptr {self.format_global('boolF', b'False')}"
|
|
1716
|
+
)
|
|
1717
|
+
self._print_fmt("s", b"%s", [f"ptr {picked}"])
|
|
1718
|
+
elif kind == "bytes":
|
|
1719
|
+
self._abi_decl("ps_repr_bytes", "void", ["i64", "ptr", "ptr"])
|
|
1720
|
+
rlen, rptr = self._buf_parts(value)
|
|
1721
|
+
out = self._buf_out("ps_repr_bytes", [f"i64 {rlen}", f"ptr {rptr}"])
|
|
1722
|
+
length, ptr = self._buf_parts(out)
|
|
1723
|
+
self._print_fmt("str", b"%.*s", [f"i64 {length}", f"ptr {ptr}"])
|
|
1724
|
+
else:
|
|
1725
|
+
length, ptr = self._buf_parts(value)
|
|
1726
|
+
self._print_fmt("str", b"%.*s", [f"i64 {length}", f"ptr {ptr}"])
|
|
1727
|
+
if end is None:
|
|
1728
|
+
self._print_fmt("nl", b"\n")
|
|
1729
|
+
else:
|
|
1730
|
+
elen, eptr = self._buf_parts(end)
|
|
1731
|
+
self._print_fmt("end", b"%.*s", [f"i64 {elen}", f"ptr {eptr}"])
|
|
1732
|
+
|
|
1733
|
+
def _print_fmt(self, name: str, data: bytes, args: list[str] | None = None) -> None:
|
|
1734
|
+
fmt = self.format_global(name, data)
|
|
1735
|
+
rendered = ", ".join([f"ptr {fmt}", *(args or [])])
|
|
1736
|
+
self.line(f"call i32 (ptr, ...) @printf({rendered})")
|
|
1737
|
+
|
|
1738
|
+
def format_global(self, name: str, data: bytes) -> str:
|
|
1739
|
+
base = f"@ps.fmt.{name}"
|
|
1740
|
+
body = f'private unnamed_addr constant [{len(data) + 1} x i8] c"{_llvm_bytes(data)}"'
|
|
1741
|
+
defined = {g.split(" = ", 1)[0] for g in self.globals}
|
|
1742
|
+
symbol = base
|
|
1743
|
+
suffix = 0
|
|
1744
|
+
while symbol in defined and f"{symbol} = {body}" not in self.globals:
|
|
1745
|
+
suffix += 1
|
|
1746
|
+
symbol = f"{base}.{suffix}"
|
|
1747
|
+
if symbol not in defined:
|
|
1748
|
+
self.globals.append(f"{symbol} = {body}")
|
|
1749
|
+
return symbol
|
|
1750
|
+
|
|
1751
|
+
def store(self, name: str, value: str) -> None:
|
|
1752
|
+
ptr, typ = self.slots[name]
|
|
1753
|
+
self.line(f"store {_llvm_type(typ)} {value}, ptr {ptr}")
|
|
1754
|
+
|
|
1755
|
+
def load(self, name: str) -> str:
|
|
1756
|
+
ptr, typ = self.slots[name]
|
|
1757
|
+
reg = self.tmp()
|
|
1758
|
+
self.line(f"{reg} = load {_llvm_type(typ)}, ptr {ptr}")
|
|
1759
|
+
return reg
|
|
1760
|
+
|
|
1761
|
+
def block(self, name: str) -> None:
|
|
1762
|
+
self.out.append(f"{name}:")
|
|
1763
|
+
self.terminated = False
|
|
1764
|
+
|
|
1765
|
+
def fresh(self, hint: str) -> str:
|
|
1766
|
+
self.lid += 1
|
|
1767
|
+
return f"L{self.lid}.{hint}"
|
|
1768
|
+
|
|
1769
|
+
def tmp(self) -> str:
|
|
1770
|
+
self.tid += 1
|
|
1771
|
+
return f"%t{self.tid}"
|
|
1772
|
+
|
|
1773
|
+
def line(self, text: str) -> None:
|
|
1774
|
+
self.out.append(f" {text}")
|
|
1775
|
+
|
|
1776
|
+
def _reset_function(self) -> None:
|
|
1777
|
+
self.tid = 0
|
|
1778
|
+
self.lid = 0
|
|
1779
|
+
self.slots = {}
|
|
1780
|
+
self.terminated = False
|
|
1781
|
+
self.try_depth = 0
|
|
1782
|
+
self.outline_depth = 0
|
|
1783
|
+
|
|
1784
|
+
|
|
1785
|
+
def _fn_symbol(name: str) -> str:
|
|
1786
|
+
"""The process entry point is ``@main``. A user function of that name gets another symbol."""
|
|
1787
|
+
if name == "main":
|
|
1788
|
+
return "ps.user.main"
|
|
1789
|
+
return name
|
|
1790
|
+
|
|
1791
|
+
|
|
1792
|
+
def _ir_const_value(typ: dict[str, Any], value: Any) -> Any:
|
|
1793
|
+
kind = typ["kind"]
|
|
1794
|
+
if kind == "int":
|
|
1795
|
+
return int(value)
|
|
1796
|
+
if kind == "float":
|
|
1797
|
+
return float(value)
|
|
1798
|
+
return value
|
|
1799
|
+
|
|
1800
|
+
|
|
1801
|
+
def _prepare_arg(param: dict[str, Any], value: Any) -> Any:
|
|
1802
|
+
kind = param["type"]["kind"]
|
|
1803
|
+
name = param["name"]
|
|
1804
|
+
if kind == "int":
|
|
1805
|
+
if isinstance(value, bool) or not isinstance(value, int):
|
|
1806
|
+
raise FitError(f"argument {name} must be an int")
|
|
1807
|
+
if not fits_i64(value):
|
|
1808
|
+
raise FitError(f"argument {name} does not fit in i64; refusing to truncate")
|
|
1809
|
+
return value
|
|
1810
|
+
if kind == "float":
|
|
1811
|
+
if isinstance(value, bool) or not isinstance(value, float):
|
|
1812
|
+
raise FitError(f"argument {name} must be a float")
|
|
1813
|
+
return value
|
|
1814
|
+
if kind == "bool":
|
|
1815
|
+
if not isinstance(value, bool):
|
|
1816
|
+
raise FitError(f"argument {name} must be a bool")
|
|
1817
|
+
return value
|
|
1818
|
+
if kind == "str":
|
|
1819
|
+
if not isinstance(value, str):
|
|
1820
|
+
raise FitError(f"argument {name} must be a str")
|
|
1821
|
+
if any(ord(ch) > 255 for ch in value):
|
|
1822
|
+
raise FitError(f"argument {name} is outside Latin-1 and is not lowered")
|
|
1823
|
+
return value
|
|
1824
|
+
raise FitError(f"argument {name} has type {kind}, which main cannot pass")
|
|
1825
|
+
|
|
1826
|
+
|
|
1827
|
+
def _llvm_double(value: float) -> str:
|
|
1828
|
+
"""IEEE-754 bits. Clang 18 rejects the 0x1.0p-1 spelling in call arguments."""
|
|
1829
|
+
|
|
1830
|
+
bits = struct.unpack("<Q", struct.pack("<d", value))[0]
|
|
1831
|
+
return f"0x{bits:016X}"
|
|
1832
|
+
|
|
1833
|
+
|
|
1834
|
+
def _llvm_type(typ: dict[str, Any]) -> str:
|
|
1835
|
+
kind = typ["kind"]
|
|
1836
|
+
if kind == "int":
|
|
1837
|
+
return "i64"
|
|
1838
|
+
if kind == "bool":
|
|
1839
|
+
return "i1"
|
|
1840
|
+
if kind == "float":
|
|
1841
|
+
return "double"
|
|
1842
|
+
if kind in {"str", "bytes"}:
|
|
1843
|
+
return "%ps.str"
|
|
1844
|
+
if kind in {"list", "dict", "class"}:
|
|
1845
|
+
return "ptr"
|
|
1846
|
+
if kind == "none":
|
|
1847
|
+
return "void"
|
|
1848
|
+
raise IRError(f"no LLVM type for {kind}")
|
|
1849
|
+
|
|
1850
|
+
|
|
1851
|
+
def _elem_size(typ: dict[str, Any]) -> int:
|
|
1852
|
+
"""Bytes copied into a list or dict slot. Matches the LLVM layout on this ABI."""
|
|
1853
|
+
|
|
1854
|
+
kind = typ["kind"]
|
|
1855
|
+
if kind in {"int", "float"}:
|
|
1856
|
+
return 8
|
|
1857
|
+
if kind in {"str", "bytes"}:
|
|
1858
|
+
return 16
|
|
1859
|
+
if kind in {"list", "dict", "class"}:
|
|
1860
|
+
return 8
|
|
1861
|
+
raise IRError(f"no element size for {kind}")
|
|
1862
|
+
|
|
1863
|
+
|
|
1864
|
+
def _safe(name: str) -> str:
|
|
1865
|
+
return name.replace(".", "_")
|
|
1866
|
+
|
|
1867
|
+
|
|
1868
|
+
def _llvm_bytes(data: bytes) -> str:
|
|
1869
|
+
"""LLVM ``c"..."`` escapes. A quote is ``\\22``; ``\\"`` ends the literal early."""
|
|
1870
|
+
|
|
1871
|
+
parts: list[str] = []
|
|
1872
|
+
for byte in data:
|
|
1873
|
+
if byte == ord("\\"):
|
|
1874
|
+
parts.append("\\\\")
|
|
1875
|
+
elif 32 <= byte < 127 and byte != ord('"'):
|
|
1876
|
+
parts.append(chr(byte))
|
|
1877
|
+
else:
|
|
1878
|
+
parts.append(f"\\{byte:02X}")
|
|
1879
|
+
parts.append("\\00")
|
|
1880
|
+
return "".join(parts)
|
|
1881
|
+
|
|
1882
|
+
|
|
1883
|
+
def _declarations(needed: set[str]) -> list[str]:
|
|
1884
|
+
lines: list[str] = []
|
|
1885
|
+
if "printf" in needed:
|
|
1886
|
+
lines.append("declare i32 @printf(ptr, ...)")
|
|
1887
|
+
overflow = needed & {"iadd", "isub", "imul", "ineg", "strconcat"}
|
|
1888
|
+
if overflow or "floordiv" in needed:
|
|
1889
|
+
lines.append("declare void @llvm.trap() noreturn nounwind")
|
|
1890
|
+
if "iadd" in needed or "strconcat" in needed:
|
|
1891
|
+
lines.append("declare { i64, i1 } @llvm.sadd.with.overflow.i64(i64, i64)")
|
|
1892
|
+
if "isub" in needed or "ineg" in needed:
|
|
1893
|
+
lines.append("declare { i64, i1 } @llvm.ssub.with.overflow.i64(i64, i64)")
|
|
1894
|
+
if "imul" in needed:
|
|
1895
|
+
lines.append("declare { i64, i1 } @llvm.smul.with.overflow.i64(i64, i64)")
|
|
1896
|
+
if "strconcat" in needed:
|
|
1897
|
+
lines.append("declare ptr @malloc(i64)")
|
|
1898
|
+
lines.append("declare void @llvm.memcpy.p0.p0.i64(ptr, ptr, i64, i1)")
|
|
1899
|
+
lines.extend(_runtime_declarations(needed))
|
|
1900
|
+
return lines
|
|
1901
|
+
|
|
1902
|
+
|
|
1903
|
+
def _runtime_declarations(needed: set[str]) -> list[str]:
|
|
1904
|
+
lines: list[str] = []
|
|
1905
|
+
if "exc" in needed:
|
|
1906
|
+
lines.append("declare i32 @ps_try_call(ptr, ptr, ptr)")
|
|
1907
|
+
lines.append("declare void @ps_raise(i32) noreturn")
|
|
1908
|
+
lines.append("declare void @ps_raise_code(i32) noreturn")
|
|
1909
|
+
lines.append("declare void @ps_raise_msg(i32, i64, ptr) noreturn")
|
|
1910
|
+
lines.append("declare void @ps_reraise() noreturn")
|
|
1911
|
+
lines.append("declare i32 @ps_exc_code()")
|
|
1912
|
+
lines.append("declare i32 @ps_exc_isa(i32, i32)")
|
|
1913
|
+
if "alloc" in needed:
|
|
1914
|
+
lines.append("declare ptr @ps_alloc(i64)")
|
|
1915
|
+
if "seq" in needed:
|
|
1916
|
+
lines.extend(
|
|
1917
|
+
[
|
|
1918
|
+
"declare ptr @ps_seq_new(i64, i64)",
|
|
1919
|
+
"declare i64 @ps_seq_len(ptr)",
|
|
1920
|
+
"declare void @ps_seq_get(ptr, i64, ptr)",
|
|
1921
|
+
"declare void @ps_seq_set(ptr, i64, ptr)",
|
|
1922
|
+
"declare i32 @ps_seq_has_i64(ptr, i64)",
|
|
1923
|
+
"declare i32 @ps_seq_has_f64(ptr, double)",
|
|
1924
|
+
"declare i32 @ps_seq_has_buf(ptr, i64, ptr)",
|
|
1925
|
+
]
|
|
1926
|
+
)
|
|
1927
|
+
if "map" in needed:
|
|
1928
|
+
lines.extend(
|
|
1929
|
+
[
|
|
1930
|
+
"declare ptr @ps_map_new(i32, i64, i64, i64)",
|
|
1931
|
+
"declare i64 @ps_map_len(ptr)",
|
|
1932
|
+
"declare void @ps_map_put(ptr, ptr, ptr)",
|
|
1933
|
+
"declare void @ps_map_get(ptr, ptr, ptr)",
|
|
1934
|
+
"declare i32 @ps_map_has(ptr, ptr)",
|
|
1935
|
+
]
|
|
1936
|
+
)
|
|
1937
|
+
if "str" in needed:
|
|
1938
|
+
lines.extend(
|
|
1939
|
+
[
|
|
1940
|
+
"declare void @ps_str_get(i64, ptr, i64, ptr)",
|
|
1941
|
+
"declare i64 @ps_buf_at(i64, ptr, i64)",
|
|
1942
|
+
"declare void @ps_str_strip(i64, ptr, ptr)",
|
|
1943
|
+
"declare void @ps_str_lstrip(i64, ptr, ptr)",
|
|
1944
|
+
"declare void @ps_str_rstrip(i64, ptr, ptr)",
|
|
1945
|
+
"declare i32 @ps_str_startswith(i64, ptr, i64, ptr)",
|
|
1946
|
+
"declare i32 @ps_str_endswith(i64, ptr, i64, ptr)",
|
|
1947
|
+
"declare i64 @ps_str_find(i64, ptr, i64, ptr)",
|
|
1948
|
+
"declare i32 @ps_str_cmp(i64, ptr, i64, ptr)",
|
|
1949
|
+
"declare void @ps_str_upper(i64, ptr, ptr)",
|
|
1950
|
+
"declare void @ps_str_lower(i64, ptr, ptr)",
|
|
1951
|
+
"declare void @ps_str_replace(i64, ptr, i64, ptr, i64, ptr, ptr)",
|
|
1952
|
+
]
|
|
1953
|
+
)
|
|
1954
|
+
if "math" in needed:
|
|
1955
|
+
lines.append("declare i64 @ps_float_to_int(double)")
|
|
1956
|
+
for spec in MATH_FUNCTIONS.values():
|
|
1957
|
+
symbol = spec["symbol"]
|
|
1958
|
+
if not symbol:
|
|
1959
|
+
continue
|
|
1960
|
+
params = ", ".join(["double"] * len(spec["params"]))
|
|
1961
|
+
lines.append(f"declare {spec['llvm']} @{symbol}({params})")
|
|
1962
|
+
if "fabs" in needed:
|
|
1963
|
+
lines.append("declare double @llvm.fabs.f64(double)")
|
|
1964
|
+
if "re" in needed:
|
|
1965
|
+
lines.extend(
|
|
1966
|
+
[
|
|
1967
|
+
"declare ptr @ps_re_findall(i64, ptr, i64, ptr)",
|
|
1968
|
+
"declare void @ps_re_sub(i64, ptr, i64, ptr, i64, ptr, ptr)",
|
|
1969
|
+
"declare ptr @ps_re_split(i64, ptr, i64, ptr)",
|
|
1970
|
+
"declare void @ps_re_escape(i64, ptr, ptr)",
|
|
1971
|
+
]
|
|
1972
|
+
)
|
|
1973
|
+
if "json" in needed:
|
|
1974
|
+
lines.extend(
|
|
1975
|
+
[
|
|
1976
|
+
"declare void @ps_json_loads(i64, ptr, ptr, i32, ptr)",
|
|
1977
|
+
"declare void @ps_json_dumps(ptr, i32, ptr, ptr)",
|
|
1978
|
+
]
|
|
1979
|
+
)
|
|
1980
|
+
if "url" in needed:
|
|
1981
|
+
lines.extend(
|
|
1982
|
+
[
|
|
1983
|
+
"declare void @ps_url_quote(i64, ptr, i64, ptr, ptr)",
|
|
1984
|
+
"declare void @ps_url_quote_plus(i64, ptr, i64, ptr, ptr)",
|
|
1985
|
+
"declare void @ps_url_unquote(i64, ptr, ptr)",
|
|
1986
|
+
"declare void @ps_url_unquote_plus(i64, ptr, ptr)",
|
|
1987
|
+
"declare void @ps_url_encode(ptr, ptr)",
|
|
1988
|
+
"declare ptr @ps_url_parse_qs(i64, ptr)",
|
|
1989
|
+
"declare void @ps_url_split(i64, ptr, ptr)",
|
|
1990
|
+
]
|
|
1991
|
+
)
|
|
1992
|
+
if "b64" in needed:
|
|
1993
|
+
lines.extend(
|
|
1994
|
+
[
|
|
1995
|
+
"declare void @ps_b64_std_encode(i64, ptr, ptr)",
|
|
1996
|
+
"declare void @ps_b64_std_decode(i64, ptr, ptr)",
|
|
1997
|
+
"declare void @ps_b64_url_encode(i64, ptr, ptr)",
|
|
1998
|
+
"declare void @ps_b64_url_decode(i64, ptr, ptr)",
|
|
1999
|
+
]
|
|
2000
|
+
)
|
|
2001
|
+
if "hash" in needed:
|
|
2002
|
+
lines.extend(
|
|
2003
|
+
[
|
|
2004
|
+
"declare ptr @ps_hash_md5(i64, ptr)",
|
|
2005
|
+
"declare ptr @ps_hash_sha1(i64, ptr)",
|
|
2006
|
+
"declare ptr @ps_hash_sha256(i64, ptr)",
|
|
2007
|
+
"declare ptr @ps_hash_sha384(i64, ptr)",
|
|
2008
|
+
"declare ptr @ps_hash_sha512(i64, ptr)",
|
|
2009
|
+
"declare void @ps_hash_hex(ptr, ptr)",
|
|
2010
|
+
"declare void @ps_hash_digest(ptr, ptr)",
|
|
2011
|
+
]
|
|
2012
|
+
)
|
|
2013
|
+
if "struct" in needed:
|
|
2014
|
+
lines.extend(
|
|
2015
|
+
[
|
|
2016
|
+
"declare void @ps_struct_pack(i64, ptr, ptr, i64, ptr)",
|
|
2017
|
+
"declare ptr @ps_struct_unpack(i64, ptr, i64, ptr, i32)",
|
|
2018
|
+
]
|
|
2019
|
+
)
|
|
2020
|
+
return lines
|
|
2021
|
+
|
|
2022
|
+
|
|
2023
|
+
def _helpers(needed: set[str]) -> list[str]:
|
|
2024
|
+
chunks: list[str] = []
|
|
2025
|
+
if "iadd" in needed:
|
|
2026
|
+
chunks.append(_overflow_helper("iadd", "sadd"))
|
|
2027
|
+
if "isub" in needed:
|
|
2028
|
+
chunks.append(_overflow_helper("isub", "ssub"))
|
|
2029
|
+
if "imul" in needed:
|
|
2030
|
+
chunks.append(_overflow_helper("imul", "smul"))
|
|
2031
|
+
if "ineg" in needed:
|
|
2032
|
+
chunks.append(
|
|
2033
|
+
"""define internal i64 @ps.ineg(i64 %a) {
|
|
2034
|
+
entry:
|
|
2035
|
+
%r = call { i64, i1 } @llvm.ssub.with.overflow.i64(i64 0, i64 %a)
|
|
2036
|
+
%ov = extractvalue { i64, i1 } %r, 1
|
|
2037
|
+
br i1 %ov, label %trap, label %ok
|
|
2038
|
+
ok:
|
|
2039
|
+
%v = extractvalue { i64, i1 } %r, 0
|
|
2040
|
+
ret i64 %v
|
|
2041
|
+
trap:
|
|
2042
|
+
call void @llvm.trap()
|
|
2043
|
+
unreachable
|
|
2044
|
+
}
|
|
2045
|
+
"""
|
|
2046
|
+
)
|
|
2047
|
+
if "floordiv" in needed:
|
|
2048
|
+
chunks.append(
|
|
2049
|
+
"""define internal i64 @ps.floordiv(i64 %a, i64 %b) {
|
|
2050
|
+
entry:
|
|
2051
|
+
%zero = icmp eq i64 %b, 0
|
|
2052
|
+
br i1 %zero, label %trap, label %nonzero
|
|
2053
|
+
nonzero:
|
|
2054
|
+
%min = icmp eq i64 %a, -9223372036854775808
|
|
2055
|
+
%neg1 = icmp eq i64 %b, -1
|
|
2056
|
+
%bad = and i1 %min, %neg1
|
|
2057
|
+
br i1 %bad, label %trap, label %divide
|
|
2058
|
+
divide:
|
|
2059
|
+
%q = sdiv i64 %a, %b
|
|
2060
|
+
%r = srem i64 %a, %b
|
|
2061
|
+
%rnz = icmp ne i64 %r, 0
|
|
2062
|
+
%aneg = icmp slt i64 %a, 0
|
|
2063
|
+
%bneg = icmp slt i64 %b, 0
|
|
2064
|
+
%diff = icmp ne i1 %aneg, %bneg
|
|
2065
|
+
%adjust = and i1 %rnz, %diff
|
|
2066
|
+
br i1 %adjust, label %dec, label %keep
|
|
2067
|
+
dec:
|
|
2068
|
+
%q2 = sub i64 %q, 1
|
|
2069
|
+
ret i64 %q2
|
|
2070
|
+
keep:
|
|
2071
|
+
ret i64 %q
|
|
2072
|
+
trap:
|
|
2073
|
+
call void @llvm.trap()
|
|
2074
|
+
unreachable
|
|
2075
|
+
}
|
|
2076
|
+
"""
|
|
2077
|
+
)
|
|
2078
|
+
if "strconcat" in needed:
|
|
2079
|
+
chunks.append(
|
|
2080
|
+
"""define internal %ps.str @ps.strconcat(%ps.str %a, %ps.str %b) {
|
|
2081
|
+
entry:
|
|
2082
|
+
%na = extractvalue %ps.str %a, 0
|
|
2083
|
+
%pa = extractvalue %ps.str %a, 1
|
|
2084
|
+
%nb = extractvalue %ps.str %b, 0
|
|
2085
|
+
%pb = extractvalue %ps.str %b, 1
|
|
2086
|
+
%sum = call { i64, i1 } @llvm.sadd.with.overflow.i64(i64 %na, i64 %nb)
|
|
2087
|
+
%ov = extractvalue { i64, i1 } %sum, 1
|
|
2088
|
+
br i1 %ov, label %trap, label %sized
|
|
2089
|
+
sized:
|
|
2090
|
+
%n = extractvalue { i64, i1 } %sum, 0
|
|
2091
|
+
%atmax = icmp eq i64 %n, 9223372036854775807
|
|
2092
|
+
br i1 %atmax, label %trap, label %alloc
|
|
2093
|
+
alloc:
|
|
2094
|
+
%n1 = add i64 %n, 1
|
|
2095
|
+
%mem = call ptr @malloc(i64 %n1)
|
|
2096
|
+
%null = icmp eq ptr %mem, null
|
|
2097
|
+
br i1 %null, label %trap, label %copy
|
|
2098
|
+
copy:
|
|
2099
|
+
call void @llvm.memcpy.p0.p0.i64(ptr %mem, ptr %pa, i64 %na, i1 false)
|
|
2100
|
+
%dst2 = getelementptr i8, ptr %mem, i64 %na
|
|
2101
|
+
call void @llvm.memcpy.p0.p0.i64(ptr %dst2, ptr %pb, i64 %nb, i1 false)
|
|
2102
|
+
%end = getelementptr i8, ptr %mem, i64 %n
|
|
2103
|
+
store i8 0, ptr %end
|
|
2104
|
+
%s0 = insertvalue %ps.str poison, i64 %n, 0
|
|
2105
|
+
%s1 = insertvalue %ps.str %s0, ptr %mem, 1
|
|
2106
|
+
ret %ps.str %s1
|
|
2107
|
+
trap:
|
|
2108
|
+
call void @llvm.trap()
|
|
2109
|
+
unreachable
|
|
2110
|
+
}
|
|
2111
|
+
"""
|
|
2112
|
+
)
|
|
2113
|
+
return chunks
|
|
2114
|
+
|
|
2115
|
+
|
|
2116
|
+
def _overflow_helper(name: str, intrinsic: str) -> str:
|
|
2117
|
+
return f"""define internal i64 @ps.{name}(i64 %a, i64 %b) {{
|
|
2118
|
+
entry:
|
|
2119
|
+
%r = call {{ i64, i1 }} @llvm.{intrinsic}.with.overflow.i64(i64 %a, i64 %b)
|
|
2120
|
+
%ov = extractvalue {{ i64, i1 }} %r, 1
|
|
2121
|
+
br i1 %ov, label %trap, label %ok
|
|
2122
|
+
ok:
|
|
2123
|
+
%v = extractvalue {{ i64, i1 }} %r, 0
|
|
2124
|
+
ret i64 %v
|
|
2125
|
+
trap:
|
|
2126
|
+
call void @llvm.trap()
|
|
2127
|
+
unreachable
|
|
2128
|
+
}}
|
|
2129
|
+
"""
|