browser-py 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.
- browser_compile.py +27 -0
- browser_py-0.1.0.dist-info/METADATA +53 -0
- browser_py-0.1.0.dist-info/RECORD +12 -0
- browser_py-0.1.0.dist-info/WHEEL +5 -0
- browser_py-0.1.0.dist-info/entry_points.txt +2 -0
- browser_py-0.1.0.dist-info/top_level.txt +7 -0
- browser_source.py +598 -0
- compiler_command.py +53 -0
- javascript_output.py +9 -0
- numeric_ir.py +315 -0
- numeric_source.py +266 -0
- webgpu_bindings.py +308 -0
webgpu_bindings.py
ADDED
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
from ast import (
|
|
2
|
+
Assign,
|
|
3
|
+
Attribute,
|
|
4
|
+
Call,
|
|
5
|
+
Constant,
|
|
6
|
+
Dict,
|
|
7
|
+
Expr,
|
|
8
|
+
FunctionDef,
|
|
9
|
+
ImportFrom,
|
|
10
|
+
List,
|
|
11
|
+
Load,
|
|
12
|
+
Module,
|
|
13
|
+
Name,
|
|
14
|
+
Return,
|
|
15
|
+
Subscript,
|
|
16
|
+
Tuple,
|
|
17
|
+
arg,
|
|
18
|
+
arguments,
|
|
19
|
+
expr,
|
|
20
|
+
fix_missing_locations,
|
|
21
|
+
parse,
|
|
22
|
+
stmt,
|
|
23
|
+
unparse,
|
|
24
|
+
walk,
|
|
25
|
+
)
|
|
26
|
+
from pathlib import Path
|
|
27
|
+
|
|
28
|
+
# wgpu structs define field types; the lowerer validates this subset and emits browser field names.
|
|
29
|
+
DESCRIPTORS = (
|
|
30
|
+
("ShaderModuleDescriptor", (("code", "code", "str"),)),
|
|
31
|
+
(
|
|
32
|
+
"RenderPipelineDescriptor",
|
|
33
|
+
(
|
|
34
|
+
("layout", "layout", "str"),
|
|
35
|
+
("vertex", "vertex", "VertexState"),
|
|
36
|
+
("fragment", "fragment", "FragmentState"),
|
|
37
|
+
("primitive", "primitive", "PrimitiveState"),
|
|
38
|
+
),
|
|
39
|
+
),
|
|
40
|
+
("VertexState", (("module", "module", "GPUShaderModule"), ("entry_point", "entryPoint", "str"))),
|
|
41
|
+
(
|
|
42
|
+
"FragmentState",
|
|
43
|
+
(
|
|
44
|
+
("module", "module", "GPUShaderModule"),
|
|
45
|
+
("entry_point", "entryPoint", "str"),
|
|
46
|
+
("targets", "targets", "ColorTargetState[]"),
|
|
47
|
+
),
|
|
48
|
+
),
|
|
49
|
+
("PrimitiveState", (("topology", "topology", "str"),)),
|
|
50
|
+
("ColorTargetState", (("format", "format", "str"),)),
|
|
51
|
+
("RenderPassDescriptor", (("color_attachments", "colorAttachments", "RenderPassColorAttachment[]"),)),
|
|
52
|
+
(
|
|
53
|
+
"RenderPassColorAttachment",
|
|
54
|
+
(
|
|
55
|
+
("view", "view", "GPUTextureView"),
|
|
56
|
+
("clear_value", "clearValue", "Color"),
|
|
57
|
+
("load_op", "loadOp", "str"),
|
|
58
|
+
("store_op", "storeOp", "str"),
|
|
59
|
+
),
|
|
60
|
+
),
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def method_binding(owner: str, name: str) -> tuple[str, str, str | None, tuple[str, ...]]:
|
|
65
|
+
if owner == "GPUDevice":
|
|
66
|
+
if name == "create_shader_module":
|
|
67
|
+
return "createShaderModule", "GPUShaderModule", "ShaderModuleDescriptor", ()
|
|
68
|
+
if name == "create_render_pipeline":
|
|
69
|
+
return "createRenderPipeline", "GPURenderPipeline", "RenderPipelineDescriptor", ()
|
|
70
|
+
if name == "create_command_encoder":
|
|
71
|
+
return "createCommandEncoder", "GPUCommandEncoder", None, ()
|
|
72
|
+
elif owner == "GPUCommandEncoder":
|
|
73
|
+
if name == "begin_render_pass":
|
|
74
|
+
return "beginRenderPass", "GPURenderPassEncoder", "RenderPassDescriptor", ()
|
|
75
|
+
if name == "finish":
|
|
76
|
+
return "finish", "GPUCommandBuffer", None, ()
|
|
77
|
+
elif owner == "GPUTexture" and name == "create_view":
|
|
78
|
+
return "createView", "GPUTextureView", None, ()
|
|
79
|
+
elif owner == "GPURenderPassEncoder":
|
|
80
|
+
if name == "set_pipeline":
|
|
81
|
+
return "setPipeline", "None", None, ("GPURenderPipeline",)
|
|
82
|
+
if name == "draw":
|
|
83
|
+
return "draw", "None", None, ("int", "int", "int", "int")
|
|
84
|
+
if name == "end":
|
|
85
|
+
return "end", "None", None, ()
|
|
86
|
+
elif owner == "GPUQueue" and name == "submit":
|
|
87
|
+
return "submit", "None", None, ("GPUCommandBuffer[]",)
|
|
88
|
+
raise NotImplementedError(f"Unsupported WebGPU method: {owner}.{name}")
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def compatible_type(actual: str, expected: str) -> bool:
|
|
92
|
+
return actual == expected or (actual == "empty_sequence" and expected.endswith("[]"))
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def descriptor_binding(
|
|
96
|
+
names: tuple[str | None, ...], types: tuple[str, ...], expected: str | None
|
|
97
|
+
) -> tuple[str, tuple[str, ...]]:
|
|
98
|
+
for descriptor in DESCRIPTORS:
|
|
99
|
+
fields = descriptor[1]
|
|
100
|
+
if (
|
|
101
|
+
(expected is None or descriptor[0] == expected)
|
|
102
|
+
and len(names) == len(fields)
|
|
103
|
+
and set(names) == {field[0] for field in fields}
|
|
104
|
+
):
|
|
105
|
+
browser_names = []
|
|
106
|
+
for i in range(len(names)):
|
|
107
|
+
for field in fields:
|
|
108
|
+
if names[i] == field[0]:
|
|
109
|
+
if compatible_type(types[i], field[2]):
|
|
110
|
+
browser_names.append(field[1])
|
|
111
|
+
break
|
|
112
|
+
raise NotImplementedError(
|
|
113
|
+
f"Expected {field[2]} for {descriptor[0]}.{field[0]}, got {types[i]}"
|
|
114
|
+
)
|
|
115
|
+
return descriptor[0], tuple(browser_names)
|
|
116
|
+
subject = expected if expected is not None else "supported wgpu struct"
|
|
117
|
+
raise NotImplementedError(f"Unsupported WebGPU descriptor fields for {subject}: {names}")
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def sequence_type(types: tuple[str, ...]) -> str:
|
|
121
|
+
if len(types) == 0:
|
|
122
|
+
return "empty_sequence"
|
|
123
|
+
if (
|
|
124
|
+
len(types) == 4
|
|
125
|
+
and types[0] in ("int", "float")
|
|
126
|
+
and types[1] in ("int", "float")
|
|
127
|
+
and types[2] in ("int", "float")
|
|
128
|
+
and types[3] in ("int", "float")
|
|
129
|
+
):
|
|
130
|
+
return "Color"
|
|
131
|
+
if types[0] in ("GPUCommandBuffer", "ColorTargetState", "RenderPassColorAttachment"):
|
|
132
|
+
for kind in types:
|
|
133
|
+
if kind == types[0]:
|
|
134
|
+
continue
|
|
135
|
+
raise NotImplementedError("WebGPU sequences require one element type")
|
|
136
|
+
return types[0] + "[]"
|
|
137
|
+
raise NotImplementedError("Unsupported WebGPU sequence element types: " + str(types))
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def lower_expression(value: expr, variables: dict[str, str]) -> tuple[expr, str]:
|
|
141
|
+
lowered: dict[expr, expr] = {}
|
|
142
|
+
types: dict[expr, str] = {}
|
|
143
|
+
nodes = list(walk(value))
|
|
144
|
+
targets = {node.func for node in nodes if isinstance(node, Call)}
|
|
145
|
+
for node in reversed(nodes):
|
|
146
|
+
if not isinstance(node, expr):
|
|
147
|
+
continue
|
|
148
|
+
if isinstance(node, Name):
|
|
149
|
+
if node.id in variables:
|
|
150
|
+
lowered[node] = Name(id=node.id, ctx=Load())
|
|
151
|
+
types[node] = variables[node.id]
|
|
152
|
+
else:
|
|
153
|
+
raise NotImplementedError("Unknown WebGPU value: " + node.id)
|
|
154
|
+
elif isinstance(node, Constant):
|
|
155
|
+
lowered[node] = node
|
|
156
|
+
types[node] = "None" if node.value is None else type(node.value).__name__
|
|
157
|
+
elif isinstance(node, (List, Tuple)):
|
|
158
|
+
lowered[node] = List(elts=[lowered[item] for item in node.elts], ctx=Load())
|
|
159
|
+
types[node] = sequence_type(tuple(types[item] for item in node.elts))
|
|
160
|
+
elif isinstance(node, Dict):
|
|
161
|
+
names = []
|
|
162
|
+
for key in node.keys:
|
|
163
|
+
if isinstance(key, Constant) and isinstance(key.value, str):
|
|
164
|
+
names.append(key.value)
|
|
165
|
+
else:
|
|
166
|
+
raise NotImplementedError("WebGPU descriptors require literal field names")
|
|
167
|
+
descriptor = descriptor_binding(tuple(names), tuple(types[item] for item in node.values), None)
|
|
168
|
+
keys: list[expr | None] = [Constant(value=name) for name in descriptor[1]]
|
|
169
|
+
lowered[node] = Dict(keys=keys, values=[lowered[item] for item in node.values])
|
|
170
|
+
types[node] = descriptor[0]
|
|
171
|
+
elif isinstance(node, Attribute):
|
|
172
|
+
owner = types[node.value]
|
|
173
|
+
if owner == "GPUDevice" and node.attr == "queue":
|
|
174
|
+
lowered[node] = Attribute(value=lowered[node.value], attr="queue", ctx=Load())
|
|
175
|
+
types[node] = "GPUQueue"
|
|
176
|
+
elif node in targets:
|
|
177
|
+
binding = method_binding(owner, node.attr)
|
|
178
|
+
# The lowerer emits native method keys; the browser emitter preserves them without runtime bindings.
|
|
179
|
+
lowered[node] = Subscript(value=lowered[node.value], slice=Constant(value=binding[0]), ctx=Load())
|
|
180
|
+
types[node] = "method"
|
|
181
|
+
else:
|
|
182
|
+
raise NotImplementedError("WebGPU methods must be called directly")
|
|
183
|
+
elif isinstance(node, Call) and isinstance(node.func, Attribute):
|
|
184
|
+
owner = types[node.func.value]
|
|
185
|
+
binding = method_binding(owner, node.func.attr)
|
|
186
|
+
if binding[2] is not None:
|
|
187
|
+
if node.args:
|
|
188
|
+
raise NotImplementedError(f"Expected keyword descriptor fields for {owner}.{node.func.attr}")
|
|
189
|
+
descriptor = descriptor_binding(
|
|
190
|
+
tuple(item.arg for item in node.keywords),
|
|
191
|
+
tuple(types[item.value] for item in node.keywords),
|
|
192
|
+
binding[2],
|
|
193
|
+
)
|
|
194
|
+
keys = [Constant(value=name) for name in descriptor[1]]
|
|
195
|
+
args: list[expr] = [Dict(keys=keys, values=[lowered[item.value] for item in node.keywords])]
|
|
196
|
+
elif len(node.args) == len(binding[3]) and not node.keywords:
|
|
197
|
+
for i in range(len(node.args)):
|
|
198
|
+
actual = types[node.args[i]]
|
|
199
|
+
if compatible_type(actual, binding[3][i]):
|
|
200
|
+
continue
|
|
201
|
+
raise NotImplementedError(
|
|
202
|
+
f"Expected {binding[3][i]} for {owner}.{node.func.attr} argument {i + 1}, got {actual}"
|
|
203
|
+
)
|
|
204
|
+
args = [lowered[item] for item in node.args]
|
|
205
|
+
else:
|
|
206
|
+
raise NotImplementedError(
|
|
207
|
+
f"Expected {len(binding[3])} positional arguments for {owner}.{node.func.attr}"
|
|
208
|
+
)
|
|
209
|
+
lowered[node] = Call(func=lowered[node.func], args=args, keywords=[])
|
|
210
|
+
types[node] = binding[1]
|
|
211
|
+
else:
|
|
212
|
+
raise NotImplementedError("Unsupported WebGPU expression: " + type(node).__name__)
|
|
213
|
+
return lowered[value], types[value]
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
def lower_webgpu(source: Path) -> str:
|
|
217
|
+
tree = parse(source.read_text())
|
|
218
|
+
gpu_types = (
|
|
219
|
+
"GPUDevice",
|
|
220
|
+
"GPUShaderModule",
|
|
221
|
+
"GPURenderPipeline",
|
|
222
|
+
"GPUCommandEncoder",
|
|
223
|
+
"GPURenderPassEncoder",
|
|
224
|
+
"GPUTexture",
|
|
225
|
+
"GPUTextureView",
|
|
226
|
+
"GPUCommandBuffer",
|
|
227
|
+
"GPUQueue",
|
|
228
|
+
)
|
|
229
|
+
imported = set()
|
|
230
|
+
functions: list[stmt] = []
|
|
231
|
+
imports_open = True
|
|
232
|
+
for node in tree.body:
|
|
233
|
+
if isinstance(node, ImportFrom) and imports_open:
|
|
234
|
+
if node.level or node.module != "wgpu.classes":
|
|
235
|
+
raise NotImplementedError("WebGPU bindings accept only top-level wgpu type imports")
|
|
236
|
+
for alias in node.names:
|
|
237
|
+
if alias.asname or alias.name not in gpu_types:
|
|
238
|
+
raise NotImplementedError("Unsupported WebGPU import: " + alias.name)
|
|
239
|
+
imported.add(alias.name)
|
|
240
|
+
continue
|
|
241
|
+
imports_open = False
|
|
242
|
+
if not isinstance(node, FunctionDef):
|
|
243
|
+
raise NotImplementedError("WebGPU modules contain only typed functions")
|
|
244
|
+
if (
|
|
245
|
+
node.decorator_list
|
|
246
|
+
or node.type_params
|
|
247
|
+
or node.args.defaults
|
|
248
|
+
or node.args.kw_defaults
|
|
249
|
+
or node.args.vararg
|
|
250
|
+
or node.args.kwarg
|
|
251
|
+
or node.args.kwonlyargs
|
|
252
|
+
or node.args.posonlyargs
|
|
253
|
+
):
|
|
254
|
+
raise NotImplementedError("WebGPU functions require plain positional parameters")
|
|
255
|
+
variables: dict[str, str] = {}
|
|
256
|
+
for parameter in node.args.args:
|
|
257
|
+
if isinstance(parameter.annotation, Name) and parameter.annotation.id in imported | {"str"}:
|
|
258
|
+
variables[parameter.arg] = parameter.annotation.id
|
|
259
|
+
else:
|
|
260
|
+
raise NotImplementedError("Explicit WebGPU parameter types required")
|
|
261
|
+
if isinstance(node.returns, Name) and node.returns.id in imported:
|
|
262
|
+
result_type = node.returns.id
|
|
263
|
+
elif isinstance(node.returns, Constant) and node.returns.value is None:
|
|
264
|
+
result_type = "None"
|
|
265
|
+
else:
|
|
266
|
+
raise NotImplementedError("Explicit WebGPU return type required")
|
|
267
|
+
body: list[stmt] = []
|
|
268
|
+
returned = False
|
|
269
|
+
for operation in node.body:
|
|
270
|
+
if returned:
|
|
271
|
+
raise NotImplementedError("Unreachable WebGPU statement")
|
|
272
|
+
if (
|
|
273
|
+
isinstance(operation, Assign)
|
|
274
|
+
and len(operation.targets) == 1
|
|
275
|
+
and isinstance(operation.targets[0], Name)
|
|
276
|
+
):
|
|
277
|
+
value = lower_expression(operation.value, variables)
|
|
278
|
+
variables[operation.targets[0].id] = value[1]
|
|
279
|
+
body.append(Assign(targets=operation.targets, value=value[0]))
|
|
280
|
+
elif isinstance(operation, Expr) and isinstance(operation.value, Call):
|
|
281
|
+
value = lower_expression(operation.value, variables)
|
|
282
|
+
body.append(Expr(value=value[0]))
|
|
283
|
+
elif isinstance(operation, Return) and operation.value is not None:
|
|
284
|
+
value = lower_expression(operation.value, variables)
|
|
285
|
+
if value[1] != result_type:
|
|
286
|
+
raise NotImplementedError("WebGPU return type does not match its value")
|
|
287
|
+
body.append(Return(value=value[0]))
|
|
288
|
+
returned = True
|
|
289
|
+
else:
|
|
290
|
+
raise NotImplementedError("Unsupported WebGPU statement: " + type(operation).__name__)
|
|
291
|
+
if result_type != "None" and not returned:
|
|
292
|
+
raise NotImplementedError("WebGPU function must return its declared result")
|
|
293
|
+
functions.append(
|
|
294
|
+
FunctionDef(
|
|
295
|
+
name=node.name,
|
|
296
|
+
args=arguments(
|
|
297
|
+
posonlyargs=[],
|
|
298
|
+
args=[arg(arg=item.arg) for item in node.args.args],
|
|
299
|
+
kwonlyargs=[],
|
|
300
|
+
kw_defaults=[],
|
|
301
|
+
defaults=[],
|
|
302
|
+
),
|
|
303
|
+
body=body,
|
|
304
|
+
decorator_list=[],
|
|
305
|
+
type_params=[],
|
|
306
|
+
)
|
|
307
|
+
)
|
|
308
|
+
return unparse(fix_missing_locations(Module(body=functions, type_ignores=[]))) + "\n"
|