Compare commits

..

No commits in common. "058191de55e38205b0b474995c546443e793376c" and "9939a226c8196a68058cfe99e6ed0e9f50e935d7" have entirely different histories.

7 changed files with 135 additions and 300 deletions

View File

@ -98,8 +98,7 @@
<button onclick="save_code('%SAVECODEURI%')"><span class="text">Save (ctrl-s)</span></button> <button onclick="save_code('%SAVECODEURI%')"><span class="text">Save (ctrl-s)</span></button>
<button onclick="save_code('%SAVEASURI%')"><span class="text">Save as new</span></button> <button onclick="save_code('%SAVEASURI%')"><span class="text">Save as new</span></button>
Sim: Sim:
<button onclick="send_to_sim('%IVERILOGPOSTURI%')"><span class="text">Icarus</span></button> <button onclick="send_to_icarus()"><span class="text">Icarus</span></button>
<button onclick="send_to_sim('%VERILATORPOSTURI%')"><span class="text">Verilator</span></button>
<!-- button><span class="text">Verilator</span></button --> <!-- button><span class="text">Verilator</span></button -->
</div> </div>
@ -124,9 +123,9 @@
enableLiveAutocompletion : true enableLiveAutocompletion : true
}); });
const log_area = document.getElementById('log'); const log_area = document.getElementById('log');
function send_to_sim(uri) { function send_to_icarus() {
log_area.innerHTML = "Please wait..."; log_area.innerHTML = "Please wait...";
fetch(uri, fetch('%IVERILOGPOSTURI%',
{ {
method: 'POST', method: 'POST',
headers: { headers: {

1
_web_server/server/iverilog Symbolic link
View File

@ -0,0 +1 @@
run-restricted

View File

@ -24,17 +24,22 @@
(define INDEX-FILE "index.html") (define INDEX-FILE "index.html")
(define DELETE-WORK-DIR #t) (define DELETE-WORK-DIR #t)
(define TOP-MODULE "testbench") (define DEFAULT-CODE
(define SNIPPET-FILE "code.sv") (string-append
(define IVERILOG-METATOP-FILE "top_iverilog.sv") "`timescale 10ps/10ps\n\n"
(define VERILATOR-CPP-FILE "top_verilator.cpp") "module test;\n"
" initial begin\n"
" $display(\"Hello world!\");\n"
" $finish();\n"
" end\n"
"endmodule\n"))
(define IVERILOG-EXE "iverilog") (define SIM-SV-FILE "testbench.sv")
(define VVP-EXE "vvp") (define SIM-VC-FILE "testbench.vc")
(define VERILATR-EXE "verilator") (define SIM-EXE-FILE "testbench.out")
(define SNIPPET-FILE "code.sv")
(define URI-IVERILOG "iverilog") (define URI-IVERILOG "iverilog")
(define URI-VERILATOR "verilator")
(define URI-SAVE-CODE "save") (define URI-SAVE-CODE "save")
(define URI-SAVEAS-CODE "saveas") (define URI-SAVEAS-CODE "saveas")
@ -45,16 +50,6 @@
(define LOG-LEVEL LOG-VERBOSE) (define LOG-LEVEL LOG-VERBOSE)
(define DEFAULT-CODE
(string-append
"`timescale 1ps/1ps\n\n"
(format "module ~a (input clock);\n" TOP-MODULE)
" initial begin\n"
" $display(\"Hello world!\");\n"
" $finish();\n"
" end\n"
"endmodule\n"))
(define (multistring . strings) (define (multistring . strings)
(apply string-append (apply string-append
(insert-between strings "\n"))) (insert-between strings "\n")))
@ -240,6 +235,21 @@
(code (regexp-substitute/global #f "\\$dump[a-z]*" code 'pre "$error" 'post))) (code (regexp-substitute/global #f "\\$dump[a-z]*" code 'pre "$error" 'post)))
code)) code))
;;;
;;; Get module name
;;;
(define (module-name code)
(let ((rx (make-regexp
"(^|\\s)module\\s*(\\s|(#\\(.*\\)\\s*))[a-zA-Z0-9_]+")))
(let loop ((pos 0)
(modname #f))
(let ((m (regexp-exec rx code pos)))
(if m
(loop (match:end m) (match:substring m))
(if modname
(match:substring (string-match "[a-zA-Z0-9_]+$" modname))
#f))))))
;;; ;;;
;;; Concatenate path elements and remove duplicate slashes ;;; Concatenate path elements and remove duplicate slashes
;;; ;;;
@ -257,86 +267,52 @@
(map string-trim-both paths)) (map string-trim-both paths))
"/"))))) "/")))))
(define (wrap-exe exe wrapper) ;;;
(format "~a~a" (if wrapper (format "~a " wrapper) "") exe)) ;;; Create dump module
;;;
(define (create-dump-module path modname top)
(define (-> . fmt) (display (apply format fmt)) (newline))
(let ((filename (path+ path (format "~a.v" modname))))
(with-output-to-file filename
(lambda ()
(-> "`timescale 1ps/1ps")
(-> "module ~a();" modname)
(-> " initial begin")
(-> " $dumpfile(\"~a/~a.vcd\");" path modname)
(-> " $dumpvars(0, ~a);" top)
(-> " end")
(-> "endmodule")))
filename))
;;; ;;;
;;; Make workdir with sources and command file. Common part ;;; Make workdir with sources and command file
;;; Returns work directory path string, verilog file name ;;; Returns work directory path string
;;; and command file name.
;;; ;;;
(define* (make-sim-workdir code base top) (define (make-sim-workdir simulator code top base)
(let* ((work-dir (mkdtemp (path+ base (format "work-~a-XXXXXX" (current-time))))) (let* ((work-dir (mkdtemp (path+ base (format "work-~a-XXXXXX" (current-time)))))
(verilog-file (path+ work-dir (format "~a.sv" top))) (verilog-file (path+ work-dir SIM-SV-FILE))
(command-file (path+ work-dir (format "~a.vc" top)))) (command-file (path+ work-dir SIM-VC-FILE))
(dump-file (create-dump-module work-dir "dump" top)))
(with-output-to-file verilog-file (cut display code)) (with-output-to-file verilog-file (cut display code))
(values work-dir verilog-file command-file)))
;;;
;;; Create workdir for Icarus Verilog
;;; Returns directory path
;;;
(define* (make-iverilog-workdir code metatop base top)
(let-values (((work-dir verilog-file command-file)
(make-sim-workdir code base top)))
(let ((metatop-file (path+ work-dir (format "__~a__.sv" top))))
(with-output-to-file metatop-file
(cut display (substitute metatop "@~a@"
`((WORKDIR ,work-dir)
(TOPMODULE ,top)))))
(with-output-to-file command-file (with-output-to-file command-file
(lambda () (lambda ()
(cond
((eq? simulator 'iverilog)
(println "~a" verilog-file) (println "~a" verilog-file)
(println "~a" metatop-file) (println "~a" dump-file)
(println "+define+TESTBENCH") (println "+define+TESTBENCH")
(println "+timescale+1ps/1ps")))) (println "+timescale+1ps/1ps")))))
work-dir))
;;;
;;; Create workdir for Verilator
;;; Returns directory path
;;;
(define* (make-verilator-workdir code cpp base top)
(let-values (((work-dir verilog-file command-file)
(make-sim-workdir code base top)))
(let ((cpp-file (path+ work-dir (format "~a.cpp" top))))
(with-output-to-file cpp-file
(cut display (substitute cpp "@~a@" `((WORKDIR ,work-dir)
(TOPMODULE ,top)))))
(with-output-to-file command-file
(lambda ()
(println "+define+TESTBENCH")
(println "--timescale 1ps/1ps")
(println "--top-module ~a" top)
(println "--Mdir ~a" (path+ work-dir top))
(println "-cc")
(println "-O2")
(println "-o ~a" top)
(println "--exe")
(println "--build")
(println "-sv")
(println "-Wno-WIDTH")
(println "+1800-2017ext+sv")
(println "--timing")
(println "--trace")
(println "--quiet-exit")
(println "~a" verilog-file)
(println "~a.cpp" top))))
work-dir)) work-dir))
;;; ;;;
;;; Compile sources and execute simulation with Icarus Verilog ;;; Compile sources and execute simulation with Icarus Verilog
;;; Returns (values status log) ;;; Returns (values status log)
;;; ;;;
(define (exec-sim-iverilog top work-dir iverilog-wrap vvp-wrap) (define (exec-sim-iverilog work-dir vvp-exe iverilog-exe)
(let ((command-file (path+ work-dir (format "~a.vc" top))) (let ((exe-file (path+ work-dir SIM-EXE-FILE))
(exe-file (path+ work-dir (format "~a.out" top)))) (command-file (path+ work-dir SIM-VC-FILE)))
;; Compile ;; Compile
(let ((cmdline (format "~a -g2012 -s __~a__ -o ~a -c~a" (let ((cmdline (format "~a -g2012 -o ~a -c~a" iverilog-exe exe-file command-file)))
(wrap-exe IVERILOG-EXE iverilog-wrap)
top exe-file command-file)))
(let-values (((status out) (let-values (((status out)
(system-to-string cmdline))) (system-to-string cmdline)))
(let ((compile-log (let ((compile-log
@ -345,7 +321,7 @@
(values status compile-log) (values status compile-log)
;; Execute ;; Execute
(let ((cmdline (format "~a -N ~a" (wrap-exe VVP-EXE vvp-wrap) exe-file))) (let ((cmdline (format "~a -N ~a" vvp-exe exe-file)))
(let-values (((status out) (let-values (((status out)
(system-to-string cmdline))) (system-to-string cmdline)))
(let ((execution-log (let ((execution-log
@ -353,61 +329,37 @@
(values status (string-append compile-log execution-log))))))))))) (values status (string-append compile-log execution-log)))))))))))
;;; ;;;
;;; Compile sources and execute simulation with Verilator ;;; Get iverilog version
;;; Returns (values status log)
;;; ;;;
(define (exec-sim-verilator top work-dir verilator-wrap verilator-sim-wrap) (define (iverilog-version iverilog-exe)
;; Compile
(let* ((command-file (path+ work-dir (format "~a.vc" top)))
(cmdline (format "~a -f ~a"
(wrap-exe VERILATR-EXE verilator-wrap)
command-file)))
(let-values (((status out) (let-values (((status out)
(system-to-string cmdline))) (system-to-string-list
(let ((compile-log (format "~a -V" iverilog-exe))))
(exe-log-pretty cmdline status out))) (if (and (zero? status)
(if (not (zero? status)) (not (null? out)))
(values status compile-log) (car out)
"Unknown")))
;; Execute
(let ((cmdline (wrap-exe (path+ work-dir (format "~a/~a" top top))
verilator-sim-wrap)))
(let-values (((status out)
(system-to-string cmdline)))
(let ((execution-log
(exe-log-pretty cmdline status out)))
(values status (string-append compile-log execution-log))))))))))
;;; ;;;
;;; Execute simulation ;;; Execute simulation
;;; ;;;
(define* (exec-sim simulator code base top #:key (define* (exec-sim simulator code base
(vvp-wrap "") (iverilog-wrap "") (metatop "") #:key
(verilator-wrap "") (verilator-sim-wrap "") (verilator-cpp "")) (vvp-exe "vvp")
(iverilog-exe "iverilog"))
(let ((top (module-name code)))
(if (not top)
"Error: No module declaration\n"
(let ((work-dir (make-sim-workdir simulator code top base)))
(let-values (let-values
(((work-dir status log) (((status log)
(cond (cond
;; Run Icarus Verilog
((eq? simulator 'iverilog) ((eq? simulator 'iverilog)
(let ((work-dir (make-iverilog-workdir code metatop base top))) (exec-sim-iverilog work-dir vvp-exe iverilog-exe))
(let-values (((status log)
(exec-sim-iverilog top work-dir iverilog-wrap vvp-wrap)))
(values work-dir status log))))
;; Run Verilator
((eq? simulator 'verilator)
(let ((work-dir (make-verilator-workdir code verilator-cpp base top)))
(let-values (((status log)
(exec-sim-verilator top work-dir verilator-wrap verilator-sim-wrap)))
(values work-dir status log))))
;; Inknown simulator
(else (else
(values #f #f #f))))) (values -1 "No simulator found!\n")))))
(if (not work-dir)
("ERROR: Unknown simulator")
(begin
;; Delete work dir ;; Delete work dir
(when DELETE-WORK-DIR (when DELETE-WORK-DIR
(delete-recursive work-dir)) (delete-recursive work-dir))
@ -416,19 +368,9 @@
(string-append (string-append
log log
(format "-----------------\nSimulation complete~a\n" (format "-----------------\nSimulation complete~a\n"
(if (zero? status) " succesfully"" with errors"))))))) (if (zero? status)
" succesfully"
;;; " with errors"))))))))
;;; Get app version
;;;
(define* (app-version exe #:optional (option "--version"))
(let-values (((status out)
(system-to-string-list
(format "~a ~a" exe option))))
(if (and (zero? status)
(not (null? out)))
(car out)
"Unknown")))
;;; ;;;
;;; Get storage dir from URI ;;; Get storage dir from URI
@ -486,43 +428,38 @@
(define (make-page-handler host root index-file (define (make-page-handler host root index-file
work-base stor-base work-base stor-base
max-code-size max-code-size
iverilog-wrap vvp-wrap vvp-exe iverilog-exe)
verilator-wrap verilator-sim-wrap)
(let* ((root-path (split-and-decode-uri-path root)) (let* ((root-path (split-and-decode-uri-path root))
(root (encode-and-join-uri-path root-path)) (root (encode-and-join-uri-path root-path))
(iverilog-path (append root-path `(,URI-IVERILOG))) (iverilog-path (append root-path `(,URI-IVERILOG)))
(verilator-path (append root-path `(,URI-VERILATOR)))
(savecode-path (append root-path `(,URI-SAVE-CODE))) (savecode-path (append root-path `(,URI-SAVE-CODE)))
(saveas-path (append root-path `(,URI-SAVEAS-CODE))) (saveas-path (append root-path `(,URI-SAVEAS-CODE)))
(iverilog-post-uri (encode-and-join-uri-path iverilog-path))
(savecode-post-uri (encode-and-join-uri-path savecode-path))
(saveas-post-uri (encode-and-join-uri-path saveas-path))
(index-html (index-html
(read-template-text (read-template-text
index-file index-file
`(("IVERILOGPOSTURI" ,(encode-and-join-uri-path iverilog-path)) `(("IVERILOGPOSTURI" ,iverilog-post-uri)
("VERILATORPOSTURI" ,(encode-and-join-uri-path verilator-path)) ("SAVECODEURI" ,savecode-post-uri)
("SAVECODEURI" ,(encode-and-join-uri-path savecode-path)) ("SAVEASURI" ,saveas-post-uri)
("SAVEASURI" ,(encode-and-join-uri-path saveas-path))
("HELPSTRING", ("HELPSTRING",
(string-concatenate (string-concatenate
(insert-between (insert-between
`("Verilog Playground by Punzik (c) 2022" `("Verilog Playground by Punzik (c) 2022"
"" ""
,(format "Icarus: ~a" ,(format "Icarus: ~a"
(app-version (wrap-exe IVERILOG-EXE iverilog-wrap) "-V")) (iverilog-version iverilog-exe))
,(format "Verilator: ~a" ,(format "Verilator: ~a" "TODO")
(app-version (wrap-exe VERILATR-EXE verilator-wrap)))
"" ""
"Rules:" "Rules:"
"0. Don't fool around ;)" "0. Don't fool around ;)"
"1. The top module must be named 'testbench'." "1. (TODO) The top module must be named 'testbench'."
"2. The top module for the Verilator must have an input clock signal." "2. (TODO) The top module for the Verilator must have an input clock signal."
"3. Code size should not exceed 10000 characters." "3. Code size should not exceed 10000 characters."
"4. Code execution time no longer than 5 seconds.") "4. Code execution time no longer than 5 seconds.")
"\\n")))))) "\\n")))))))
(iverilog-metatop
(call-with-input-file IVERILOG-METATOP-FILE get-string-all))
(verilator-cpp
(call-with-input-file VERILATOR-CPP-FILE get-string-all)))
(lambda (request request-body) (lambda (request request-body)
(let (;; Requested resource path (let (;; Requested resource path
@ -612,27 +549,8 @@
(exec-sim 'iverilog (exec-sim 'iverilog
(sanitize-verilog code) (sanitize-verilog code)
work-base work-base
TOP-MODULE #:vvp-exe vvp-exe
#:metatop iverilog-metatop #:iverilog-exe iverilog-exe)
#:vvp-wrap vvp-wrap
#:iverilog-wrap iverilog-wrap)
#:content-type 'text/plain))
;; Run verilator simulation
((equal? path verilator-path)
(logger LOG-DBG "Request verilator simulation")
(when ref-stor-dir
(save-to-storage (path+ stor-base ref-stor-dir) code))
(make-response
(exec-sim 'verilator
(sanitize-verilog code)
work-base
TOP-MODULE
#:verilator-wrap verilator-wrap
#:verilator-sim-wrap verilator-sim-wrap
#:verilator-cpp verilator-cpp)
#:content-type 'text/plain)) #:content-type 'text/plain))
;; Save snippet ;; Save snippet
@ -690,10 +608,8 @@
(-> " -p, --port PORT Listen on PORT port. Default: 8080") (-> " -p, --port PORT Listen on PORT port. Default: 8080")
(-> " -s, --host URL Run on URL hostname. Default: http://127.0.0.1:8080") (-> " -s, --host URL Run on URL hostname. Default: http://127.0.0.1:8080")
(-> " -r, --root URN Service location root. Default: ''") (-> " -r, --root URN Service location root. Default: ''")
(-> " --iverilog-wrap PATH Icarus compiler wrapper.") (-> " --ivverilog-exe PATH Set Icarus Verilog compiler executable. Default: iverilog")
(-> " --vvp-wrap PATH Icarus Verilog interpreter wrapper.") (-> " --vvp-exe PATH Set Icarus Verilog interpreter executable. Default: vvp")
(-> " --verilator-wrap PATH Verilator compiler wrapper.")
(-> " --verilator-sim-wrap PATH Verilator simulation executable wrapper.")
(-> " --max-len LEN Set maximum code size in symbols. Default: 0 (infinite)") (-> " --max-len LEN Set maximum code size in symbols. Default: 0 (infinite)")
(-> " --work-base PATH Set work base path. Default: ./") (-> " --work-base PATH Set work base path. Default: ./")
(-> " --stor-base PATH Set snippets storage path. Default: ./") (-> " --stor-base PATH Set snippets storage path. Default: ./")
@ -702,11 +618,6 @@
(-> "") (-> "")
(-> "Source code and issue tracker: <https://github.com/punzik/>"))))) (-> "Source code and issue tracker: <https://github.com/punzik/>")))))
(define (string-trim-if-string str)
(if (string? str)
(string-trim str)
str))
(define (main args) (define (main args)
(debug-disable 'backtrace) (debug-disable 'backtrace)
(let-values (let-values
@ -716,10 +627,8 @@
'(("port" #\p) required) '(("port" #\p) required)
'(("host" #\s) required) '(("host" #\s) required)
'(("root" #\r) required) '(("root" #\r) required)
'(("vvp-wrap") required) '(("vvp-exe") required)
'(("iverilog-wrap") required) '(("iverilog-exe") required)
'(("verilator-wrap") required)
'(("verilator-sim-wrap") required)
'(("max-len") required) '(("max-len") required)
'(("work-base") required) '(("work-base") required)
'(("stor-base") required) '(("stor-base") required)
@ -730,10 +639,8 @@
(port (string->number (string-trim (or (option-get opts "port") "8080")))) (port (string->number (string-trim (or (option-get opts "port") "8080"))))
(host (string-trim (or (option-get opts "host") "http://127.0.0.1:8080"))) (host (string-trim (or (option-get opts "host") "http://127.0.0.1:8080")))
(root (string-trim (or (option-get opts "root") ""))) (root (string-trim (or (option-get opts "root") "")))
(vvp-wrap (string-trim-if-string (option-get opts "vvp-wrap"))) (vvp (string-trim (or (option-get opts "vvp-exe") "vvp")))
(iverilog-wrap (string-trim-if-string (option-get opts "iverilog-wrap"))) (iverilog (string-trim (or (option-get opts "iverilog-exe") "iverilog")))
(verilator-wrap (string-trim-if-string (option-get opts "verilator-wrap")))
(verilator-sim-wrap (string-trim-if-string (option-get opts "verilator-sim-wrap")))
(max-code-size (string->number (string-trim (or (option-get opts "max-len") "0")))) (max-code-size (string->number (string-trim (or (option-get opts "max-len") "0"))))
(work-base (string-trim (or (option-get opts "work-base") "./"))) (work-base (string-trim (or (option-get opts "work-base") "./")))
(stor-base (string-trim (or (option-get opts "stor-base") "./"))) (stor-base (string-trim (or (option-get opts "stor-base") "./")))
@ -753,10 +660,6 @@
(set! LOG-LEVEL log-level) (set! LOG-LEVEL log-level)
(logger LOG-INFO "Listen on '~a' port '~a'" addr port) (logger LOG-INFO "Listen on '~a' port '~a'" addr port)
(logger LOG-INFO "Server URL: '~a/~a'" host root) (logger LOG-INFO "Server URL: '~a/~a'" host root)
(logger LOG-INFO "iverilog wrapper: '~a'" iverilog-wrap)
(logger LOG-INFO "vvp wrapper: '~a'" vvp-wrap)
(logger LOG-INFO "verilator compiler wrapper: '~a'" verilator-wrap)
(logger LOG-INFO "verilator simulator wrapper: '~a'" verilator-sim-wrap)
(logger LOG-INFO "Max code size: ~a" max-code-size) (logger LOG-INFO "Max code size: ~a" max-code-size)
(logger LOG-INFO "Work base path: '~a'" work-base) (logger LOG-INFO "Work base path: '~a'" work-base)
(logger LOG-INFO "Storage base path: '~a'" stor-base) (logger LOG-INFO "Storage base path: '~a'" stor-base)
@ -765,7 +668,5 @@
(run-server (run-server
(make-page-handler host root INDEX-FILE (make-page-handler host root INDEX-FILE
work-base stor-base work-base stor-base
max-code-size max-code-size vvp iverilog)
iverilog-wrap vvp-wrap
verilator-wrap verilator-sim-wrap)
'http `(#:host ,addr #:port ,port))))))) 'http `(#:host ,addr #:port ,port)))))))

View File

@ -1,12 +1,14 @@
#!/usr/bin/env bash #!/usr/bin/env bash
exe=$(basename $0)
if [ -z "$DONOTUSEFIREJAIL" ]; then if [ -z "$DONOTUSEFIREJAIL" ]; then
exec firejail \ exec firejail \
--quiet --noprofile \ --quiet --noprofile \
--rlimit-cpu=5 \ --rlimit-cpu=5 \
--rlimit-as=250m \ --rlimit-as=250m \
--rlimit-fsize=250k \ --rlimit-fsize=250k \
"$@" "$exe" "$@"
else else
exec timeout -v -s KILL 5 "$@" exec timeout -v -s INT 5 "$exe" "$@"
fi fi

View File

@ -1,11 +0,0 @@
`timescale 1ps/1ps
module __@TOPMODULE@__;
logic clock = 1'b0;
initial forever #(5ns) clock = ~clock;
@TOPMODULE@ @TOPMODULE@ (clock);
initial begin
$dumpfile("@WORKDIR@/@TOPMODULE@.vcd");
$dumpvars(0, @TOPMODULE@);
end
endmodule

View File

@ -1,58 +0,0 @@
#include "V@TOPMODULE@.h"
#include <cstdint>
#include <verilated.h>
#include <verilated_vcd_c.h>
#define DUMPFILE "@WORKDIR@/@TOPMODULE@.vcd"
#define CLOCK_HALF_PERIOD 5000
int main(int argc, char **argv)
{
VerilatedContext *ctx = new VerilatedContext;
ctx->commandArgs(argc, argv);
/* Create model instance */
V@TOPMODULE@ *top = new V@TOPMODULE@(ctx);
#if (VM_TRACE == 1)
VerilatedVcdC *vcd = new VerilatedVcdC;
ctx->traceEverOn(true);
top->trace(vcd, 99);
vcd->open(DUMPFILE);
#endif
top->clock = 0;
/* ---- Evaluation loop ---- */
for (;;) {
/* Eval */
top->eval();
/* Trace steady-state values */
#if (VM_TRACE == 1)
if (vcd) vcd->dump(ctx->time());
#endif
/* Break exactly after calling $finish */
if (ctx->gotFinish()) break;
/* Clock event */
ctx->timeInc(CLOCK_HALF_PERIOD);
top->clock = top->clock ? 0 : 1;
}
top->final();
printf("[%lu] Stop simulation\n", ctx->time());
#if (VM_TRACE == 1)
if (vcd) {
vcd->close();
delete vcd;
}
#endif
delete top;
delete ctx;
return 0;
}

1
_web_server/server/vvp Symbolic link
View File

@ -0,0 +1 @@
run-restricted