lcpu / app.py
arudradey's picture
REDEPLOY: clean emcc resolver, auto-install, /bin/sh noexec bypass
4248548 verified
Raw
History Blame Contribute Delete
6.78 kB
import gradio as gr
import os
import subprocess
import shutil
import numpy as np
from PIL import Image
import pywasm
SAVE_DIR = "/data"
EMSDK = "/data/emsdk"
if not os.path.exists(SAVE_DIR):
os.makedirs(SAVE_DIR, exist_ok=True)
_emcc_cached = None
def _find_emcc():
global _emcc_cached
if _emcc_cached is not None:
return _emcc_cached
emcc = shutil.which("emcc")
if emcc:
_emcc_cached = [emcc]
return _emcc_cached
emcc_sh = os.path.join(EMSDK, "upstream/emscripten/emcc")
if os.path.exists(emcc_sh):
_emcc_cached = ["/bin/sh", emcc_sh]
return _emcc_cached
return None
def _install_emsdk():
try:
if os.path.exists(EMSDK):
shutil.rmtree(EMSDK)
subprocess.run(["git", "clone", "https://github.com/emscripten-core/emsdk.git", EMSDK],
check=True, capture_output=True, timeout=120)
subprocess.run([os.path.join(EMSDK, "emsdk"), "install", "latest"],
check=True, capture_output=True, timeout=180)
subprocess.run([os.path.join(EMSDK, "emsdk"), "activate", "latest"],
check=True, capture_output=True, timeout=60)
cfg = f"""import os
LLVM_ROOT = '{EMSDK}/upstream/bin'
NODE_JS = '{EMSDK}/node/22.16.0_64bit/bin/node'
BINARYEN_ROOT = '{EMSDK}/upstream'
EMSCRIPTEN_ROOT = '{EMSDK}/upstream/emscripten'
CACHE = '/tmp/.emscripten_cache'
"""
with open(os.path.join(EMSDK, ".emscripten"), "w") as f:
f.write(cfg)
return True
except Exception as e:
return str(e)
def get_wasm_files():
if not os.path.exists(SAVE_DIR):
return []
return [f for f in os.listdir(SAVE_DIR) if f.endswith(".wasm")]
def compile_c(c_code, filename):
if not c_code or not filename:
return "Error: Provide C code and filename."
emcc = _find_emcc()
if not emcc:
result = _install_emsdk()
if result is not True:
return f"Error: emsdk install failed: {result}"
_emcc_cached = None
emcc = _find_emcc()
if not emcc:
return "Error: emcc not found after install."
base = filename.split(".")[0]
c_file = f"/tmp/{base}.c"
wasm_tmp = f"/tmp/{base}.wasm"
wasm_out = os.path.join(SAVE_DIR, f"{base}.wasm")
with open(c_file, "w") as f:
f.write(c_code)
env = os.environ.copy()
env["EM_CONFIG"] = os.path.join(EMSDK, ".emscripten")
env["PATH"] = f"{EMSDK}/upstream/bin:{EMSDK}/upstream/emscripten:{EMSDK}/node/22.16.0_64bit/bin:" + env.get("PATH", "")
try:
res = subprocess.run(
emcc + [c_file, "-O3", "-s", "WASM=1", "-s", "SIDE_MODULE=1", "-o", wasm_tmp],
capture_output=True, text=True, timeout=60, env=env,
)
if res.returncode != 0:
return f"Compiler Error:\n{res.stderr[-1500:]}"
if os.path.exists(wasm_tmp):
if os.path.exists(wasm_out):
os.remove(wasm_out)
shutil.move(wasm_tmp, wasm_out)
try:
rt = pywasm.Runtime()
mod = rt.instance_from_file(wasm_out)
exports = [e.name for e in mod.exps if not e.name.startswith("__")]
return f"Forged: {base}.wasm\n\nExports: {', '.join(exports) if exports else 'none'}"
except Exception:
return f"Forged: {base}.wasm"
return "Error: WASM not generated."
except subprocess.TimeoutExpired:
return "Error: Compilation timed out."
except Exception as e:
return f"Error: {e}"
def run_wasm(wasm_file, func_name, args_str):
if not wasm_file:
return "Error: Select a WASM file."
if not func_name:
return "Error: Provide a function name."
path = os.path.join(SAVE_DIR, wasm_file)
if not os.path.exists(path):
return f"Error: {wasm_file} not found."
try:
args = [int(x.strip()) for x in args_str.split(",") if x.strip()] if args_str else []
rt = pywasm.Runtime()
mod = rt.instance_from_file(path)
for name in [func_name, f"_{func_name}"]:
try:
result = rt.invocate(mod, name, args)
return f"Result: {result}"
except (AssertionError, Exception):
continue
exports = [e.name for e in mod.exps if not e.name.startswith("__")]
return f"Error: function not found.\nExports: {', '.join(exports) if exports else 'none'}"
except Exception as e:
return f"Error: {type(e).__name__}: {e}"
def render_wasm(wasm_file):
if not wasm_file:
return None, "Select a file."
path = os.path.join(SAVE_DIR, wasm_file)
if not os.path.exists(path):
return None, f"{wasm_file} not found."
try:
with open(path, "rb") as f:
data = f.read()
if len(data) == 0:
return None, "Empty file."
arr = np.frombuffer(data, dtype=np.uint8).copy()
if arr.max() > arr.min():
arr = ((arr - arr.min()) * (255.0 / (arr.max() - arr.min()))).astype(np.uint8)
canvas = 1920 * 1080
c = np.tile(arr, (canvas // len(arr)) + 1)[:canvas] if len(arr) > 0 else np.zeros(canvas, dtype=np.uint8)
img = Image.fromarray(c.reshape((1080, 1920)), mode="L")
return img, f"{len(data)} bytes visualized"
except Exception as e:
return None, f"Error: {e}"
with gr.Blocks() as demo:
gr.Markdown("# LCPU: C to WASM Engine")
with gr.Tab("Forge"):
fn = gr.Textbox(label="Filename", value="logic")
code = gr.TextArea(label="C Code", value="int add(int a, int b) {\n return a + b;\n}", lines=8)
btn = gr.Button("Build", variant="primary")
out = gr.Textbox(label="Status", interactive=False)
with gr.Tab("Runner"):
sel = gr.Dropdown(label="WASM", choices=get_wasm_files())
rf = gr.Button("Refresh")
func = gr.Textbox(label="Function", value="add")
args = gr.Textbox(label="Args (comma)", placeholder="10, 20")
run = gr.Button("Run", variant="primary")
rout = gr.Textbox(label="Result", interactive=False)
with gr.Tab("Visualizer"):
vsel = gr.Dropdown(label="WASM", choices=get_wasm_files())
vrf = gr.Button("Refresh")
viz = gr.Button("Visualize", variant="primary")
img = gr.Image(label="Texture")
vout = gr.Textbox(label="Status", interactive=False)
btn.click(compile_c, [code, fn], out)
rf.click(lambda: gr.Dropdown(choices=get_wasm_files()), outputs=sel)
run.click(run_wasm, [sel, func, args], rout)
vrf.click(lambda: gr.Dropdown(choices=get_wasm_files()), outputs=vsel)
viz.click(render_wasm, vsel, [img, vout])
if __name__ == "__main__":
demo.launch()