|
Forum Index : Microcontroller and PC projects : New in V6.03.02b9 : create a CSUB from Basic without writing any C
| Author | Message | ||||
| matherp Guru Joined: 11/12/2012 Location: United KingdomPosts: 11879 |
mmb2csub.pdf Every PicoMite program has at least one routine doing the real work, and most of the time the interpreter spends there goes on *reading* the code rather than running it. `mmb2csub` compiles that routine to machine code and puts it back into your program as a CSUB: ``` :: assuming I am in the user-tools directory after unpacking the zip file python mmb2csub.py ../examples/julia.bas PlotJulia ``` That rewrites the program: the original Basic routine is commented out, the CSUB is appended, and **your call sites do not change** — MMBasic calls a CSUB exactly as it calls a SUB. The Julia set demo included with the tool renders in 5.9 seconds instead of 119 on an RP2040, and 2.5 instead of 86 on an RP2350 - 20x and 35x - and draws a byte-identical image either way. **How it works.** The translation from MMBasic to C is done by `mmb2c.py`, a complete MMBasic-to-C translator — the same one that, converted to C, runs as a native application under the Fuzix port. It already understood MMBasic's scope rules, string semantics and array layouts, which is the hard part. What `mmb2csub` adds is the other half: a driver that picks one routine out of your program and works out what it needs, and a runtime that maps the translated C onto the firmware's *own* routines through the CallTable defined in PicoCFunctions.h. That second part is what makes the result trustworthy. `SIN`, `MID$`, `STR$`, `RGB` and the drawing commands inside a CSUB call the same firmware code the interpreter calls, so a converted routine cannot quietly disagree with the original about what `MID$` means — and the compiled blob stays small, because none of it is duplicated. **One converted program runs on every PicoMite.** The same file — same bytes — works on the RP2040 and the RP2350 and on every firmware variant, because the code is built for the Cortex-M0+, is position-independent, and finds the firmware's routines through a table it locates at run time rather than an address fixed when it was compiled. Tested both ways round: byte-identical output from one file on a PicoMiteVGA (RP2040) and a PicoMiteHDMIWEB (RP2350B). So a converted program can be posted or shipped exactly like any other `.bas`. **What to expect.** Loops, array work and arithmetic run 10–20x faster. Routines that mostly call the firmware already — graphics, `SIN`, string formatting — gain much less, because only the interpreting overhead goes away. The tool tells you which yours is before you commit to anything: `--list` compiles and links every routine in your program and reports what each would cost, without changing a thing. It is also honest about what it cannot do. Anything unsupported is refused by name, before anything is written, rather than producing a CSUB that is subtly wrong. **Getting it:** `mmb2csub-6.03.02b9.zip`, attached to this release. It needs Python, the Arm GNU toolchain (`arm-none-eabi-gcc`) — the same compiler used to build the firmware — and one Python package (`pip install pyelftools`). The manual inside covers setting both up on Windows and Linux, and the recommended workflow. Firmware 6.03.02b9 or later is required on the board. Julia.bas as originally written ' julia.bas - Julia set, with plotjulia taking everything it needs as ' parameters so it is a self-contained routine (the first mmb2csub target). Mode 2 CLS map maximite 'Specify initial values RealOffset = -1.30 ImaginOffset = -1.22 '------------------------------------------------* 'Set the Julia set constant [eg C = -1.2 + 0.8i] CRealVal = -0.78 CImagVal = -0.20 '------------------------------------------------* MAXIT=80 'max iterations PixelWidth = MM.HRes PixelHeight = MM.VRes GAP = PixelHeight / PixelWidth SIZE = 2.50 XDelta = SIZE / PixelWidth YDelta = (SIZE * GAP) / PixelHeight ' MAP() has no CSUB CallTable slot, and in a fixed mode its sixteen values are ' constants - so look them up once here and pass the table in. Dim mp%(15) For i% = 0 To 15 : mp%(i%) = map(i%) : Next i% plotjulia PixelWidth, PixelHeight, XDelta, YDelta, RealOffset, ImaginOffset, CRealVal, CImagVal, MAXIT, mp%() ' the rendered image, for comparing this against the CSUB version byte for byte. ' SAVE IMAGE records the framebuffer and ignores the colour map, so what lands in ' the file is exactly what plotjulia computed. Save Image "julia.bmp" Do a$ = Inkey$ Loop While a$ = "" end ' ' w, h picture size in pixels ' xd, yd the step in the complex plane per pixel ' rOfs, iOfs top-left corner of the view ' cRe, cIm the Julia constant C ' mit iteration limit ' mp%() the sixteen colours, already resolved through MAP() sub plotjulia w, h, xd, yd, rOfs, iOfs, cRe, cIm, mit, mp%() Local X, Y, CX, CY, Zr, Zi, COUNT, new_Zr, new_Zi 'Loop processing - visit every pixel For X = 0 To (w - 1) CX = X * xd + rOfs For Y = 0 To (h - 1) CY = Y * yd + iOfs Zr = CX Zi = CY COUNT = 0 ' Begin Iteration loop Do While (( COUNT <= mit ) And (( Zr * Zr + Zi * Zi ) < 4 )) new_Zr = Zr * Zr - Zi * Zi + cRe new_Zi = 2 * Zr * Zi + cIm Zr = new_Zr Zi = new_Zi COUNT = COUNT + 1 Loop Pixel X,Y,mp%( COUNT Mod 16) Next Y Next X end sub After running the csub generator. Note: the original basic, the C source and comments in the csub are all removed by using the option --lean in the generator command ' julia.bas - Julia set, with plotjulia taking everything it needs as ' parameters so it is a self-contained routine (the first mmb2csub target). Mode 2 CLS map maximite 'Specify initial values RealOffset = -1.30 ImaginOffset = -1.22 '------------------------------------------------* 'Set the Julia set constant [eg C = -1.2 + 0.8i] CRealVal = -0.78 CImagVal = -0.20 '------------------------------------------------* MAXIT=80 'max iterations PixelWidth = MM.HRes PixelHeight = MM.VRes GAP = PixelHeight / PixelWidth SIZE = 2.50 XDelta = SIZE / PixelWidth YDelta = (SIZE * GAP) / PixelHeight ' MAP() has no CSUB CallTable slot, and in a fixed mode its sixteen values are ' constants - so look them up once here and pass the table in. Dim mp%(15) For i% = 0 To 15 : mp%(i%) = map(i%) : Next i% plotjulia PixelWidth, PixelHeight, XDelta, YDelta, RealOffset, ImaginOffset, CRealVal, CImagVal, MAXIT, mp%() ' the rendered image, for comparing this against the CSUB version byte for byte. ' SAVE IMAGE records the framebuffer and ignores the colour map, so what lands in ' the file is exactly what plotjulia computed. Save Image "julia.bmp" Do a$ = Inkey$ Loop While a$ = "" end ' ' w, h picture size in pixels ' xd, yd the step in the complex plane per pixel ' rOfs, iOfs top-left corner of the view ' cRe, cIm the Julia constant C ' mit iteration limit ' mp%() the sixteen colours, already resolved through MAP() /* ' --- mmb2csub: PlotJulia replaced by a CSUB; original follows sub plotjulia w, h, xd, yd, rOfs, iOfs, cRe, cIm, mit, mp%() Local X, Y, CX, CY, Zr, Zi, COUNT, new_Zr, new_Zi 'Loop processing - visit every pixel For X = 0 To (w - 1) CX = X * xd + rOfs For Y = 0 To (h - 1) CY = Y * yd + iOfs Zr = CX Zi = CY COUNT = 0 ' Begin Iteration loop Do While (( COUNT <= mit ) And (( Zr * Zr + Zi * Zi ) < 4 )) new_Zr = Zr * Zr - Zi * Zi + cRe new_Zi = 2 * Zr * Zi + cIm Zr = new_Zr Zi = new_Zi COUNT = COUNT + 1 Loop Pixel X,Y,mp%( COUNT Mod 16) Next Y Next X end sub */ ' --- mmb2csub: generated code for PlotJulia CSUB PlotJulia FLOAT, FLOAT, FLOAT, FLOAT, FLOAT, FLOAT, FLOAT, FLOAT, FLOAT, INTEGER 00000022 '__aeabi_dadd 4C03B510 69E46824 682434A4 BD1047A0 E000ED08 '__aeabi_dsub 4C03B510 69E46824 682434A8 BD1047A0 E000ED08 '__aeabi_dmul 4C03B510 69E46824 682434A0 BD1047A0 E000ED08 '__aeabi_dcmplt 4C04B510 69E46824 682434B0 0FC047A0 46C0BD10 E000ED08 '__aeabi_dcmple 4C05B510 69E46824 682434B0 1E4347A0 0FC04318 46C0BD10 E000ED08 '__aeabi_dcmpge 4C04B510 69E46824 682434B0 43C047A0 BD100FC0 E000ED08 'PlotJulia B097B5F0 22009214 93154C8F 00066823 911369DB 605A6FDB 6823601A 6FDF69DB 4295683D 2080D111 01406B1B 21804798 60386823 014969DB 605D6FDB 69DB6823 685A6FDB D900428A 6823605D 68302200 69DB6871 685B6FDB 4B7C9312 FF96F7FF 23002200 910D900C 93019200 990D980C 69DB6823 9A006FDD F7FF9B01 2800FFB5 2000D105 9B122100 B017606B 68ABBDF0 60AB3301 D103059B 69DB6823 47986A9B 99019800 681A9B14 F7FF685B 9B1CFF79 685B681A FF60F7FF 910F900E 22009913 68496808 F7FF4B61 2200FF61 90102300 92029111 98109303 68239911 6FDD69DB 9B039A02 FF80F7FF D1082800 99019800 4B562200 FF40F7FF 91019000 68ABE7B6 60AB3301 D103059B 69DB6823 47986A9B 99039802 681A9B15 F7FF685B 9B1DFF41 685B681A FF28F7FF 9F0F9E0E 23002200 91059004 93079206 003B0032 00390030 FF2EF7FF 9B059A04 91099008 00190010 FF26F7FF 900A6823 9806910B 69DD9907 681A9B20 F7FF685B 2800FF31 9A0AD00B 98089B0B F7FF9909 2200FEFF F7FF4B34 2800FF19 9800D12D 002B9901 681B33EC 681E3588 4798682B 00056823 99039802 338869DB 4798681B 90046823 980669DB 001A9907 32FC3388 6CD7681B 22104798 47B82300 00C09B21 9904581A 47B00028 99039802 4B1D2200 FECEF7FF 91039002 6FEAE779 33016893 059B6093 6823D103 6A9B69DB 9A0A4798 98089B0B F7FF9909 9B1EFEC5 685B681A FEB6F7FF 003B0032 91099008 00390030 FEAEF7FF 9B059A04 FEBEF7FF 681A9B1F F7FF685B 2200FEA5 91059004 99079806 F7FF4B04 9E08FE9D 90069F09 E7779107 E000ED08 3FF00000 40100000 End CSUB ' The C this was compiled from, for reference. Regenerate with: ' python mmb2csub.py julia.bas PlotJulia ' '/* Generated by mmb2csub.py from ../examples/julia.bas, PlotJulia. ' * Do not edit - regenerate instead. */ '#include "mmcsub.h" ' ' 'static void f_plotjulia(MMFLOAT *p_w, MMFLOAT *p_h, MMFLOAT *p_xd, MMFLOAT *p_yd, MMFLOAT *p_rofs, MMFLOAT *p_iofs, MMFLOAT *p_cre, MMFLOAT *p_cim, MMFLOAT *p_mit, MMINTEGER *p_mp, const MMINTEGER *__b_mp) { ' unsigned __mark = mm_mark(); (void)__mark; ' MMFLOAT v_x = 0; ' MMFLOAT v_y = 0; ' MMFLOAT v_cx = 0; ' MMFLOAT v_cy = 0; ' MMFLOAT v_zr = 0; ' MMFLOAT v_zi = 0; ' MMFLOAT v_count = 0; ' MMFLOAT v_new_zr = 0; ' MMFLOAT v_new_zi = 0; ' { ' MMFLOAT __lim2 = ((((*p_w)) - (1.0))); ' for (v_x = 0.0; v_x <= __lim2; v_x += 1) { ' mm_poll(); ' v_cx = ((((v_x) * ((*p_xd)))) + ((*p_rofs))); ' { ' MMFLOAT __lim3 = ((((*p_h)) - (1.0))); ' for (v_y = 0.0; v_y <= __lim3; v_y += 1) { ' mm_poll(); ' v_cy = ((((v_y) * ((*p_yd)))) + ((*p_iofs))); ' v_zr = v_cx; ' v_zi = v_cy; ' v_count = 0.0; ' while (((((((v_count) <= ((*p_mit)))) & ((((((((v_zr) * (v_zr))) + (((v_zi) * (v_zi)))))) < (4LL)))))) != 0) { ' mm_poll(); ' v_new_zr = ((((((v_zr) * (v_zr))) - (((v_zi) * (v_zi))))) + ((*p_cre))); ' v_new_zi = ((((((2.0) * (v_zr))) * (v_zi))) + ((*p_cim))); ' v_zr = v_new_zr; ' v_zi = v_new_zi; ' v_count = ((v_count) + (1.0)); ' } ' mm_pixel(mm_toint(v_x), mm_toint(v_y), p_mp[(int)(mm_mod(mm_toint(v_count), 16LL))]); ' } ' } ' } ' } ' mm_release(__mark); '} ' '/* CSUB entry - the interpreter hands us one pointer per argument. */ 'long long PlotJulia(void *a0, void *a1, void *a2, void *a3, void *a4, void *a5, void *a6, void *a7, void *a8, void *a9) '{ ' mm_scratch_reset(); ' f_plotjulia((MMFLOAT *)a0, (MMFLOAT *)a1, (MMFLOAT *)a2, (MMFLOAT *)a3, (MMFLOAT *)a4, (MMFLOAT *)a5, (MMFLOAT *)a6, (MMFLOAT *)a7, (MMFLOAT *)a8, (MMINTEGER *)a9, 0); ' return 0; '} Edited 2026-09-17 23:14 by matherp |
||||
| tom_g Newbie Joined: 21/02/2023 Location: SwitzerlandPosts: 26 |
Hi Peter, this is a quantum leap for fast execution of MMBasic programs. Thank you indeed for the new Py tool ! I am not into Unix - as I understand, except for preemptive multitasking and for C programmers, what are the benefits of Unix in comparison to the latest Basic version ? With my respect, best regards Thomas |
||||
| The Back Shed's forum code is written, and hosted, in Australia. | © JAQ Software 2026 |