From db9404ca1a6ac67ab1c42791a44deb4c88546bda Mon Sep 17 00:00:00 2001 From: Jan Larres Date: Thu, 28 Mar 2013 00:16:03 +1300 Subject: [PATCH] Add tests to repository --- .gitattributes | 1 + tests/actionscript/RayTracer.as | 1113 ++ tests/ant/example.build.xml | 38 + tests/asm/gameport.asm | 39 + tests/asm/queueservers.asm | 164 + tests/asp/serialnumber.asp | 14 + tests/awk/ctime.awk | 11 + tests/basic/threads.bas | 30 + tests/cobol/Acme99.cbl | 200 + tests/cobol/DriverProg.cbl | 132 + tests/coffee/campfire.coffee | 251 + tests/cpp/DeadlockDetector.h | 588 + tests/cpp/ceded-test.cpp | 597 + tests/cpp/issue82.cpp | 8 + tests/cpp/jstracer.cpp | 14392 ++++++++++++++++ tests/cpp/jstracer_part.cpp | 195 + tests/cpp/mlprototype.cpp | 15 + tests/cpp/multiline.cpp | 6 + tests/cpp/nsCycleCollector.cpp | 3728 ++++ tests/cpp/nsIOThreadPool.cpp | 310 + tests/cpp/nsTextFrameThebes.cpp | 6873 ++++++++ tests/cpp/nsThread.cpp | 702 + tests/cpp/nsThread.h | 170 + tests/cpp/nsThread_part.cpp | 46 + tests/cpp/nsXPComInit.cpp | 1023 ++ tests/cpp/proxytests.cpp | 558 + tests/cpp/test.cpp | 33 + tests/cpp/testsubclass.cpp | 12 + tests/cs/ICoder.cs | 157 + tests/cs/RangeCoder.cs | 234 + tests/cs/RangeCoderBitTree.cs | 157 + tests/eiffel/poly.e | 64 + tests/erlang/examples-2.0/ebnf.ecc | 34 + tests/erlang/examples-2.0/ecc.xrl | 117 + tests/erlang/examples-2.0/ecc.yrl | 105 + tests/erlang/examples-2.0/ecc_parse.erl | 58 + tests/erlang/examples-2.0/ermake.erl | 236 + .../examples-2.0/ermake_line_reader.erl | 246 + tests/erlang/examples-2.0/ermake_parse.erl | 73 + tests/erlang/examples-2.0/error_handler.erl | 30 + tests/erlang/examples-2.0/find.erl | 170 + tests/erlang/examples-2.0/ftp_client.erl | 52 + tests/erlang/examples-2.0/ftp_server.erl | 123 + tests/erlang/examples-2.0/leex.erl | 591 + tests/erlang/examples-2.0/leex.hrl | 200 + tests/erlang/examples-2.0/lin.erl | 95 + tests/erlang/examples-2.0/primes.erl | 92 + tests/erlang/examples-2.0/rsa_key.erl | 56 + tests/erlang/examples-2.0/sos | 2 + tests/erlang/examples-2.0/sos.erl | 344 + tests/erlang/examples-2.0/sos_err1.erl | 7 + tests/erlang/examples-2.0/sos_err2.erl | 7 + tests/erlang/examples-2.0/sos_test1.erl | 6 + tests/erlang/examples-2.0/sos_test2.erl | 7 + tests/erlang/examples-2.0/sos_test3.erl | 16 + tests/erlang/examples-2.0/sos_test4.erl | 8 + tests/erlang/examples-2.0/suffix_rules | 8 + tests/erlang/examples-2.0/test1 | 17 + tests/erlang/examples-2.0/test2 | 13 + tests/erlang/examples-2.0/test3 | 52 + tests/erlang/examples-2.0/test4 | 7 + tests/erlang/examples-2.0/title_page.tex | 22 + .../erlang/examples-2.0/topological_sort.erl | 66 + tests/erlang/examples-2.0/transitive.erl | 39 + tests/erlang/examples-2.0/users | 3 + tests/fortran/block.f | 7 + tests/fortran/fall1.f | 150 + tests/fortran/htcoef.f | 55 + tests/fortran/linint1.f | 22 + tests/fortran/plot2.f | 209 + tests/fortran/thcl.f | 73 + tests/java/FlowSet.java | 46 + tests/java/JilBuilder.java | 1933 +++ tests/java/ShipSquare.java | 40 + tests/javascript/backboje.js | 16 + tests/javascript/class.js | 83 + tests/javascript/jquery.ui.progressbar.js | 107 + tests/javascript/namespaces.js | 27 + tests/javascript/oreilly1.js | 42 + tests/javascript/prototype.js | 6081 +++++++ tests/javascript/small.js | 9 + tests/jptest 天使のたまご水に棲む/test.cpp | 597 + tests/mxml/main.mxml | 65 + tests/ocaml/fifteen.ml | 98 + tests/ocaml/heap.ml | 153 + tests/ocaml/kb.ml | 294 + tests/ocaml/xmlParser.mli | 82 + tests/php/Crawler.php | 721 + tests/php/Link.php | 157 + tests/php/gda-clean.php | 23 + tests/python/bcontroller.py | 238 + tests/python/functions.py | 697 + tests/python/mlstring.py | 13 + tests/python/models.py | 50 + tests/python/multiline.py | 3 + tests/python/tabindent.py | 3 + tests/python/test.py | 23 + tests/python/test2.py | 3 + tests/python/vis.py | 225 + tests/ruby/cache_key.rb | 10 + tests/ruby/singleton.rb | 13 + tests/ruby/testcase.rb | 10 + tests/sql/backup.sql | 27 + tests/sql/ex.sql | 17 + tests/tex/nonascii.c | 4 + tests/tex/nonascii.tex | 24 + tests/tex/outputtest.txt | 6 + tests/tex/test.txt | 4 + tests/vala/closures.vala | 23 + tests/vala/generics.vala | 22 + tests/vala/gnomine.vala | 874 + tests/vala/hashset.vala | 206 + tests/vala/namespaces.vala | 35 + tests/vala/structs.vala | 86 + tests/vala/valacodewriter.vala | 1605 ++ tests/vala/valaparser.vala | 3595 ++++ tests/vera/arb.if.vri | 6 + tests/vera/class_a.vr | 6 + tests/vera/class_extension.vr | 99 + tests/vera/copy_object.vr | 29 + tests/vera/enum_t.vr | 13 + tests/vera/properties.vr | 60 + tests/vera/this_ex.vr | 14 + tests/vera/virtual_port.vr | 45 + tests/vhdl/gcd2.vhd | 306 + tests/vhdl/test_multpipe.vhdl | 91 + tests/vhdl/test_parity.vhdl | 67 + tests/vhdl/testchip_core.vhdl | 219 + 128 files changed, 54624 insertions(+) create mode 100644 tests/actionscript/RayTracer.as create mode 100644 tests/ant/example.build.xml create mode 100644 tests/asm/gameport.asm create mode 100644 tests/asm/queueservers.asm create mode 100644 tests/asp/serialnumber.asp create mode 100644 tests/awk/ctime.awk create mode 100644 tests/basic/threads.bas create mode 100644 tests/cobol/Acme99.cbl create mode 100644 tests/cobol/DriverProg.cbl create mode 100644 tests/coffee/campfire.coffee create mode 100644 tests/cpp/DeadlockDetector.h create mode 100644 tests/cpp/ceded-test.cpp create mode 100644 tests/cpp/issue82.cpp create mode 100644 tests/cpp/jstracer.cpp create mode 100644 tests/cpp/jstracer_part.cpp create mode 100644 tests/cpp/mlprototype.cpp create mode 100644 tests/cpp/multiline.cpp create mode 100644 tests/cpp/nsCycleCollector.cpp create mode 100644 tests/cpp/nsIOThreadPool.cpp create mode 100644 tests/cpp/nsTextFrameThebes.cpp create mode 100644 tests/cpp/nsThread.cpp create mode 100644 tests/cpp/nsThread.h create mode 100644 tests/cpp/nsThread_part.cpp create mode 100644 tests/cpp/nsXPComInit.cpp create mode 100644 tests/cpp/proxytests.cpp create mode 100644 tests/cpp/test.cpp create mode 100644 tests/cpp/testsubclass.cpp create mode 100644 tests/cs/ICoder.cs create mode 100644 tests/cs/RangeCoder.cs create mode 100644 tests/cs/RangeCoderBitTree.cs create mode 100644 tests/eiffel/poly.e create mode 100644 tests/erlang/examples-2.0/ebnf.ecc create mode 100644 tests/erlang/examples-2.0/ecc.xrl create mode 100644 tests/erlang/examples-2.0/ecc.yrl create mode 100644 tests/erlang/examples-2.0/ecc_parse.erl create mode 100644 tests/erlang/examples-2.0/ermake.erl create mode 100644 tests/erlang/examples-2.0/ermake_line_reader.erl create mode 100644 tests/erlang/examples-2.0/ermake_parse.erl create mode 100644 tests/erlang/examples-2.0/error_handler.erl create mode 100644 tests/erlang/examples-2.0/find.erl create mode 100644 tests/erlang/examples-2.0/ftp_client.erl create mode 100644 tests/erlang/examples-2.0/ftp_server.erl create mode 100644 tests/erlang/examples-2.0/leex.erl create mode 100644 tests/erlang/examples-2.0/leex.hrl create mode 100644 tests/erlang/examples-2.0/lin.erl create mode 100644 tests/erlang/examples-2.0/primes.erl create mode 100644 tests/erlang/examples-2.0/rsa_key.erl create mode 100755 tests/erlang/examples-2.0/sos create mode 100644 tests/erlang/examples-2.0/sos.erl create mode 100644 tests/erlang/examples-2.0/sos_err1.erl create mode 100644 tests/erlang/examples-2.0/sos_err2.erl create mode 100644 tests/erlang/examples-2.0/sos_test1.erl create mode 100644 tests/erlang/examples-2.0/sos_test2.erl create mode 100644 tests/erlang/examples-2.0/sos_test3.erl create mode 100644 tests/erlang/examples-2.0/sos_test4.erl create mode 100644 tests/erlang/examples-2.0/suffix_rules create mode 100644 tests/erlang/examples-2.0/test1 create mode 100644 tests/erlang/examples-2.0/test2 create mode 100644 tests/erlang/examples-2.0/test3 create mode 100644 tests/erlang/examples-2.0/test4 create mode 100644 tests/erlang/examples-2.0/title_page.tex create mode 100644 tests/erlang/examples-2.0/topological_sort.erl create mode 100644 tests/erlang/examples-2.0/transitive.erl create mode 100644 tests/erlang/examples-2.0/users create mode 100644 tests/fortran/block.f create mode 100644 tests/fortran/fall1.f create mode 100644 tests/fortran/htcoef.f create mode 100644 tests/fortran/linint1.f create mode 100644 tests/fortran/plot2.f create mode 100644 tests/fortran/thcl.f create mode 100644 tests/java/FlowSet.java create mode 100644 tests/java/JilBuilder.java create mode 100755 tests/java/ShipSquare.java create mode 100644 tests/javascript/backboje.js create mode 100644 tests/javascript/class.js create mode 100644 tests/javascript/jquery.ui.progressbar.js create mode 100644 tests/javascript/namespaces.js create mode 100644 tests/javascript/oreilly1.js create mode 100644 tests/javascript/prototype.js create mode 100644 tests/javascript/small.js create mode 100644 tests/jptest 天使のたまご水に棲む/test.cpp create mode 100644 tests/mxml/main.mxml create mode 100644 tests/ocaml/fifteen.ml create mode 100644 tests/ocaml/heap.ml create mode 100644 tests/ocaml/kb.ml create mode 100644 tests/ocaml/xmlParser.mli create mode 100644 tests/php/Crawler.php create mode 100644 tests/php/Link.php create mode 100644 tests/php/gda-clean.php create mode 100755 tests/python/bcontroller.py create mode 100644 tests/python/functions.py create mode 100755 tests/python/mlstring.py create mode 100644 tests/python/models.py create mode 100644 tests/python/multiline.py create mode 100644 tests/python/tabindent.py create mode 100644 tests/python/test.py create mode 100644 tests/python/test2.py create mode 100755 tests/python/vis.py create mode 100644 tests/ruby/cache_key.rb create mode 100644 tests/ruby/singleton.rb create mode 100644 tests/ruby/testcase.rb create mode 100644 tests/sql/backup.sql create mode 100644 tests/sql/ex.sql create mode 100644 tests/tex/nonascii.c create mode 100644 tests/tex/nonascii.tex create mode 100644 tests/tex/outputtest.txt create mode 100644 tests/tex/test.txt create mode 100644 tests/vala/closures.vala create mode 100644 tests/vala/generics.vala create mode 100644 tests/vala/gnomine.vala create mode 100644 tests/vala/hashset.vala create mode 100644 tests/vala/namespaces.vala create mode 100644 tests/vala/structs.vala create mode 100644 tests/vala/valacodewriter.vala create mode 100644 tests/vala/valaparser.vala create mode 100644 tests/vera/arb.if.vri create mode 100644 tests/vera/class_a.vr create mode 100644 tests/vera/class_extension.vr create mode 100644 tests/vera/copy_object.vr create mode 100644 tests/vera/enum_t.vr create mode 100644 tests/vera/properties.vr create mode 100644 tests/vera/this_ex.vr create mode 100644 tests/vera/virtual_port.vr create mode 100644 tests/vhdl/gcd2.vhd create mode 100644 tests/vhdl/test_multpipe.vhdl create mode 100644 tests/vhdl/test_parity.vhdl create mode 100644 tests/vhdl/testchip_core.vhdl diff --git a/.gitattributes b/.gitattributes index 3b1db11..37ce5f1 100644 --- a/.gitattributes +++ b/.gitattributes @@ -2,3 +2,4 @@ .gitattributes export-ignore README export-ignore .info export-ignore +tests/** export-ignore diff --git a/tests/actionscript/RayTracer.as b/tests/actionscript/RayTracer.as new file mode 100644 index 0000000..e13635a --- /dev/null +++ b/tests/actionscript/RayTracer.as @@ -0,0 +1,1113 @@ + //print("sanity check"); + var size; + //Scene scene; + var scene; + /** + * Lights for the rendering scene + * Light lights[]; + */ + var lights; + + /** + * Objects (spheres) for the rendering scene + * Primitive prim[]; + */ + var prim; + + /** + * The view for the rendering scene + * View view; + */ + var view; + /** + * Temporary ray + * Ray tRay = new Ray(); + */ + //print("point 1"); + var tRay = new Ray(); + //print("point2"); + /** + * Alpha channel + */ + var alpha = 255 << 24; + + /** + * Null vector (for speedup, instead of new Vec(0,0,0) + */ + var voidVec = new Vec(0.0,0.0,0.0); + + /** + * Temporary vect + */ + var L = new Vec(0.0,0.0,0.0); + + /** + * Current intersection instance (only one is needed!) + */ + var inter = new Isect(); + + /** + * Height of the Image to be rendered + */ + var height; + + /** + * Width of the Image to be rendered + */ + var width; + + var datasizes = new Array(4); + datasizes[0] = 50; + datasizes[1] = 150; + datasizes[2] = 500; + datasizes[3] = 7; + + var checksum = 0; + var lastValue = 0; + var size; + + var numobjects; + + var start = new Date(); + JGFrun(3); + var elapsed = new Date() - start; + + function JGFsetsize(sizel) { + size = sizel; + } + + function JGFinitialise() { + //print("Entering JGFinitialise"); + // set image size + width = height = datasizes[size]; + + // create the objects to be rendered + scene = createScene(); + + // get lights, objects etc. from scene. + setScene(scene); + + numobjects = scene.getObjects(); + + } + + function JGFapplication() { + // Set interval to be rendered to the whole picture + // (overkill, but will be useful to retain this for parallel versions) + var interval = new Interval(0, width, height, 0, height, 1); + + // Do the business! + render(interval); + + } + + function JGFvalidate() { + //print("entering JFGvalidate"); + var refval = new Array(4); + refval[0] = 2676692; + refval[1] = 29827635; + refval[2] = 29827635; + refval[3] = 5158; + var dev = checksum - refval[size]; + if (dev != 0) { + print("Validation failed"); + print("Pixel checksum = " + checksum); + print("Reference value = " + refval[size]); + }else + { + print("Validation successfull " + checksum); + } + } + + function JGFtidyup() { + scene = null; + lights = null; + prim = null; + tRay = null; + inter = null; + } + + function JGFrun(size) { + //print("entering JFGrun"); + JGFsetsize(size); + JGFinitialise(); + JGFapplication(); + JGFvalidate(); + //JGFtidyup(); + } + + /****************Start Class RayTracer*************/ + /** + * Create and initialize the scene for the rendering picture. + * + * @return The scene just created + */ + + function createScene() { + //print("entering createScene"); + var x = 0; + var y = 0; + + var scene = new Scene(); + + /* create spheres */ + + var p; + var nx = 4; + var ny = 4; + var nz = 4; + for (var i = 0; i < nx; i++) { + for (var j = 0; j < ny; j++) { + for (var k = 0; k < nz; k++) { + var xx = 20.0 / (nx - 1) * i - 10.0; + var yy = 20.0 / (ny - 1) * j - 10.0; + var zz = 20.0 / (nz - 1) * k - 10.0; + p = new Sphere(new Vec(xx, yy, zz), 3); + + // p.setColor(i/(double) (nx-1), j/(double)(ny-1), + // k/(double) (nz-1)); + p.setColor(0, 0, (i + j) / (nx + ny - 2)); + p.surf.shine = 15.0; + p.surf.ks = 1.5 - 1.0; + p.surf.kt = 1.5 - 1.0; + scene.addObject(p); + } + } + } + + /* Creates five lights for the scene */ + scene.addLight(new Light(100, 100, -50, 1.0)); + scene.addLight(new Light(-100, 100, -50, 1.0)); + scene.addLight(new Light(100, -100, -50, 1.0)); + scene.addLight(new Light(-100, -100, -50, 1.0)); + scene.addLight(new Light(200, 200, 0, 1.0)); + + /* Creates a View (viewing point) for the rendering scene */ + var v = new View(new Vec(x, 20, -30), new Vec(x, y, 0), new Vec(0, 1,0), 1.0, 35.0 * 3.14159265 / 180.0, 1.0); + /* + * v.from = new Vec(x, y, -30); v.at = new Vec(x, y, -15); v.up = new + * Vec(0, 1, 0); v.angle = 35.0 * 3.14159265 / 180.0; v.aspect = 1.0; + * v.dist = 1.0; + * + */ + scene.setView(v); + + return scene; + } + + function setScene(scene) { + //print("entering setScene"); + // Get the objects count + var nLights = scene.getLights(); + var nObjects = scene.getObjects(); + + lights = new Array(nLights); + prim = new Array(nObjects); + + // Get the lights + for (var l = 0; l < nLights; l++) { + lights[l] = scene.getLight(l); + } + + // Get the primitives + for (var o = 0; o < nObjects; o++) { + prim[o] = scene.getObject(o); + } + + // Set the view + view = scene.getView(); + } + + function render(interval) { + //print("entering render"); + // Screen variables + var row = new Array(interval.width * (interval.yto - interval.yfrom)); + var pixCounter = 0; // iterator + + // Rendering variables + var x, y, red, green, blue; + var xlen, ylen; + var viewVec; + + viewVec = Vec.sub(view.at, view.from); + + viewVec.normalize(); + + var tmpVec = new Vec(0.0,0.0,0.0); + tmpVec.setVec(viewVec); + tmpVec.scale(Vec.dot(view.up, viewVec)); + + var upVec = Vec.sub(view.up, tmpVec); + upVec.normalize(); + + var leftVec = Vec.cross(view.up, viewVec); + leftVec.normalize(); + + var frustrumwidth = view.dist * Math.tan(view.angle); + + upVec.scale(-frustrumwidth); + leftVec.scale(view.aspect * frustrumwidth); + + var r = new Ray(); + r.setRay(view.from, voidVec); + var col = new Vec(0.0,0.0,0.0); + + // Header for .ppm file + // System.out.println("P3"); + // System.out.println(width + " " + height); + // System.out.println("255"); + + // All loops are reversed for 'speedup' (cf. thinking in java p331) + + // For each line + for (y = interval.yfrom; y < interval.yto; y++) { + //print("outer loop in render :"+y); + ylen = (2.0 * y) / interval.width - 1.0; + // System.out.println("Doing line " + y); + // For each pixel of the line + for (x = 0; x < interval.width; x++) { + //print("innter loop in render: "+x); + xlen = (2.0 * x) / interval.width - 1.0; + r.D = Vec.comb(xlen, leftVec, ylen, upVec); + r.D.add(viewVec); + r.D.normalize(); + //print("executing trace"); + col = trace2(0, 1.0, r); + if(col == undefined) + { + print("col is set: "+col); + print("r is: "+r); + } + // computes the color of the ray + red = (col.x * 255.0); + if (red > 255) + red = 255; + green = (col.y * 255.0); + //print("green is set"); + if (green > 255) + green = 255; + blue = (col.z * 255.0); + //print("blue is set"); + if (blue > 255) + blue = 255; + //print("adding checksum"); + red = Math.floor(red); + green = Math.floor(green); + blue = Math.floor(blue); + checksum += red; + checksum += green; + checksum += blue; + + // RGB values for .ppm file + // System.out.println(red + " " + green + " " + blue); + // Sets the pixels + row[pixCounter++] = alpha | (red << 16) | (green << 8) | (blue); + } // end for (x) + } // end for (y) + + } + + function intersect( r, maxt) { + var tp; + var i, nhits; + //print("entering intersect"); + nhits = 0; + inter.t = 1e9; + for (i = 0; i < prim.length; i++) { + // uses global temporary Prim (tp) as temp.object for speedup + tp = prim[i].intersect(r); + if (tp != null && tp.t < inter.t) { + inter.t = tp.t; + inter.prim = tp.prim; + inter.surf = tp.surf; + inter.enter = tp.enter; + nhits++; + } + } + return nhits > 0 ? true : false; + } + + /** + * Checks if there is a shadow + * + * @param r + * The ray + * @return Returns 1 if there is a shadow, 0 if there isn't + */ + function Shadow( r, tmax) { + if (intersect(r, tmax)) + return 0; + return 1; + } + + /** + * Return the Vector's reflection direction + * + * @return The specular direction + */ + function SpecularDirection( I, N) { + var r; + r = Vec.comb(1.0 / Math.abs(Vec.dot(I, N)), I, 2.0, N); + r.normalize(); + return r; + } + + /** + * Return the Vector's transmission direction + */ + function TransDir( m1, m2, I, N) { + var n1, n2, eta, c1, cs2; + var r; + n1 = m1 == null ? 1.0 : m1.ior; + n2 = m2 == null ? 1.0 : m2.ior; + eta = n1 / n2; + c1 = -Vec.dot(I, N); + cs2 = 1.0 - eta * eta * (1.0 - c1 * c1); + if (cs2 < 0.0) + return null; + r = Vec.comb(eta, I, eta * c1 - Math.sqrt(cs2), N); + r.normalize(); + return r; + } + + /** + * Returns the shaded color + * + * @return The color in Vec form (rgb) + */ + function shade( level, weight, P, N, I, hit) { + var n1, n2, eta, c1, cs2; + var r; + var tcol; + var R; + var t, diff, spec; + var surf; + var col; + var l; + + col = new Vec(0.0,0.0,0.0); + surf = hit.surf; + R = new Vec(0.0,0.0,0.0); + if (surf.shine > 1e-6) { + R = SpecularDirection(I, N); + } + + // Computes the effectof each light + for (l = 0; l < lights.length; l++) { + L.sub2(lights[l].pos, P); + if (Vec.dot(N, L) >= 0.0) { + t = L.normalize(); + + tRay.P = P; + tRay.D = L; + + // Checks if there is a shadow + if (Shadow(tRay, t) > 0) { + diff = Vec.dot(N, L) * surf.kd * lights[l].brightness; + + col.adds_two(diff, surf.color); + if (surf.shine > 1e-6) { + spec = Vec.dot(R, L); + if (spec > 1e-6) { + spec = Math.pow(spec, surf.shine); + col.x += spec; + col.y += spec; + col.z += spec; + } + } + } + } // if + } // for + + tRay.P = P; + if (surf.ks * weight > 1e-3) { + tRay.D = SpecularDirection(I, N); + tcol = trace2(level + 1, surf.ks * weight, tRay); + col.adds_two(surf.ks, tcol); + } + if (surf.kt * weight > 1e-3) { + if (hit.enter > 0) + tRay.D = TransDir(null, surf, I, N); + else + tRay.D = TransDir(surf, null, I, N); + tcol = trace2(level + 1, surf.kt * weight, tRay); + col.adds_two(surf.kt, tcol); + } + + // garbaging... + tcol = null; + surf = null; + + return col; + } + + /** + * Launches a ray + */ + function trace2( level, weight, r) { + //print("entering trace"); + var P, N; + var hit; + //print("checking recursion in trace"); + // Checks the recursion level + if (level > 6) { + return new Vec(0.0,0.0,0.0); + } + + hit = intersect(r, 1e6); + //print("hit is: "+hit); + if (hit) { + P = r.point(inter.t); + N = inter.prim.normal(P); + if (Vec.dot(r.D, N) >= 0.0) { + N.negate(); + } + return shade(level, weight, P, N, r.D, inter); + } + // no intersection --> col = 0,0,0 + return voidVec; + } + /****************End Class RayTracer***************/ + +class Interval { + /* + * public int number; public int width; public int height; public int yfrom; + * public int yto; public int total; + */ + var number; + + var width; + + var height; + + var yfrom; + + var yto; + + var total; + + function Interval( number, width, height, yfrom, yto, total) { + this.number = number; + this.width = width; + this.height = height; + this.yfrom = yfrom; + this.yto = yto; + this.total = total; + } +} +class Isect { + //public double t; + var t; + //public int enter; + var enter; + //public Primitive prim; + var prim; + //public Surface surf; + var surf; +} + +class Light { + //public Vec pos; + var pos; + //public double brightness; + var brightness; + function Light( x, y, z, brightnessl) { + this.pos = new Vec(x, y, z); + this.brightness = brightnessl; + } +} + +class Primitive { + var surf = new Surface(); + + function setColor( r, g, b) { + surf.color = new Vec(r, g, b); + } + //abstract + function normal(pnt){} + //abstract + function intersect(ry){} + + //abstract, override + function toString(){} + //abstract + function getCenter(){} + //abstract + + function setCenter(c){} +} + +class Ray { + var P, D; + + function setRay(pnt, dir) { + //print("set ray start"); + P = new Vec(pnt.x, pnt.y, pnt.z); + D = new Vec(dir.x, dir.y, dir.z); + //print("set ray after init"); + D.normalize(); + //print("set ray after normalize"); + } + function Ray() + { + //print("start Ray()"); + P = new Vec(0.0,0.0,0.0); + D = new Vec(0.0,0.0,0.0); + //print("end Ray()"); + } + + function point(t) { + return new Vec(P.x + D.x * t, P.y + D.y * t, P.z + D.z * t); + } + + //@Override + function toString() { + return "{" + P.toString() + " -> " + D.toString() + "}"; + } +} +class Scene { + var lights; + + var objects; + + var view; + + function Scene() { + this.lights = new Array(); + this.objects = new Array(); + } + + function addLight(l) { + this.lights.push(l); + } + + function addObject(object) { + this.objects.push(object); + } + + function setView(view) { + this.view = view; + } + + function getView() { + return this.view; + } + + function getLight(number) { + return this.lights[number]; + } + + function getObject( number) { + return objects[number]; + } + + function getLights() { + //print("start getLights"); + return this.lights.length; + } + + function getObjects() { + return this.objects.length; + } + + function setObject(object, pos) { + this.objects[pos] = object; + } +} +class Sphere extends Primitive { + var c; + + var r, r2; + + var v, b; // temporary vecs used to minimize the memory load + + function Sphere( center, radius) { + c = center; + r = radius; + r2 = r * r; + v = new Vec(0.0,0.0,0.0); + b = new Vec(0.0,0.0,0.0); + } + + //@Override + override function intersect(ry) { + var b, disc, t; + var ip; + v.sub2(c, ry.P); + b = Vec.dot(v, ry.D); + disc = b * b - Vec.dot(v, v) + r2; + if (disc < 0.0) { + return null; + } + disc = Math.sqrt(disc); + t = (b - disc < 1e-6) ? b + disc : b - disc; + if (t < 1e-6) { + return null; + } + ip = new Isect(); + ip.t = t; + ip.enter = Vec.dot(v, v) > r2 + 1e-6 ? 1 : 0; + ip.prim = this; + ip.surf = surf; + return ip; + } + + //@Override + override function normal(p) { + var r; + r = Vec.sub(p, c); + r.normalize(); + return r; + } + + //@Override + override function toString() { + return "Sphere {" + c.toString() + "," + r + "}"; + } + + //@Override + override function getCenter() { + return c; + } + + //@Override + override function setCenter(c) { + this.c = c; + } +} + +class Surface { + var color; + + var kd; + + var ks; + + var shine; + + var kt; + + var ior; + + function Surface() { + color = new Vec(1, 0, 0); + kd = 1.0; + ks = 0.0; + shine = 0.0; + kt = 0.0; + ior = 1.0; + } + + //@Override + function toString() { + return "Surface { color=" + color + "}"; + } +} +/** + * This class reflects the 3d vectors used in 3d computations + */ +class Vec { + + /** + * The x coordinate + */ + var x; + + /** + * The y coordinate + */ + var y; + + /** + * The z coordinate + */ + var z; + + x = 0.0; + y = 0.0; + z = 0.0; + + /** + * Constructor + * + * @param a + * the x coordinate + * @param b + * the y coordinate + * @param c + * the z coordinate + */ + function Vec( a, b, c) { + x = a; + y = b; + z = c; + } + /** + * Copy constructor + */ + function setVec(a) { + x = a.x; + y = a.y; + z = a.z; + } + /** + * Add a vector to the current vector + * + * @param: a The vector to be added + */ + function add( a) { + x += a.x; + y += a.y; + z += a.z; + } + + /** + * adds: Returns a new vector such as new = sA + B + */ + function adds( s, a, b) { + return new Vec(s * a.x + b.x, s * a.y + b.y, s * a.z + b.z); + } + + /** + * Adds vector such as: this+=sB + * + * @param: s The multiplier + * @param: b The vector to be added + */ + function adds_two( s, b) { + x += s * b.x; + y += s * b.y; + z += s * b.z; + } + + /** + * Substracs two vectors + */ + static function sub( a, b) { + return new Vec(a.x - b.x, a.y - b.y, a.z - b.z); + } + + /** + * Substracts two vects and places the results in the current vector Used + * for speedup with local variables -there were too much Vec to be gc'ed + * Consumes about 10 units, whether sub consumes nearly 999 units!! cf + * thinking in java p. 831,832 + */ + function sub2( a, b) { + this.x = a.x - b.x; + this.y = a.y - b.y; + this.z = a.z - b.z; + } + + static function mult( a, b) { + return new Vec(a.x * b.x, a.y * b.y, a.z * b.z); + } + + static function cross( a, b) { + return new Vec(a.y * b.z - a.z * b.y, a.z * b.x - a.x * b.z, a.x * b.y + - a.y * b.x); + } + + static function dot( a, b) { + return a.x * b.x + a.y * b.y + a.z * b.z; + } + + static function comb( a, A, b, B) { + return new Vec(a * A.x + b * B.x, a * A.y + b * B.y, a * A.z + b * B.z); + } + + function comb2( a, A, b, B) { + x = a * A.x + b * B.x; + y = a * A.y + b * B.y; + z = a * A.z + b * B.z; + } + + function scale( t) { + x *= t; + y *= t; + z *= t; + } + + function negate() { + x = -x; + y = -y; + z = -z; + } + + function normalize() { + var len; + len = Math.sqrt(x * x + y * y + z * z); + if (len > 0.0) { + x /= len; + y /= len; + z /= len; + } + return len; + } + + //@Override + function toString() { + return "<" + x + "," + y + "," + z + ">"; + } +} + +class View { + /* + * public Vec from; public Vec at; public Vec up; public double dist; public + * double angle; public double aspect; + */ + var from; + + var at; + + var up; + + var dist; + + var angle; + + var aspect; + + function View( froml, atl, upl, distl, anglel, aspectl) { + this.from = froml; + this.at = atl; + this.up = upl; + this.dist = distl; + this.angle = anglel; + this.aspect = aspectl; + } +} +/*****************Start Vector class*****************/ +//http://sourceforge.net/projects/jsvector/ +// Vector Constructor -- constructs the object +// Vector Constructor -- constructs the object + +// Vector is now a builtin class - using that instead +/* +function Vector(inc) { + if (inc == 0) { + inc = 100; + } + + // Properties + this.data = new Array(inc); + this.increment = inc; + this.size = 0; + + // Methods + this.getCapacity = getCapacity; + this.getSize = getSize; + this.isEmpty = isEmpty; + this.getLastElement = getLastElement; + this.getFirstElement = getFirstElement; + this.getElementAt = getElementAt; + this.addElement = addElement; + this.insertElementAt = insertElementAt; + this.removeElementAt = removeElementAt; + this.removeAllElements = removeAllElements; + this.indexOf = indexOf; + this.contains = contains + this.resize = resize; + this.toString = toString; + this.sort = sort; + this.trimToSize = trimToSize; + this.clone = clone; + this.overwriteElementAt; +} + +// getCapacity() -- returns the number of elements the vector can hold +function getCapacity() { + return this.data.length; +} + +// getSize() -- returns the current size of the vector +function getSize() { + return this.size; +} + +// isEmpty() -- checks to see if the Vector has any elements +function isEmpty() { + return this.getSize() == 0; +} + +// getLastElement() -- returns the last element +function getLastElement() { + if (this.data[this.getSize() - 1] != null) { + return this.data[this.getSize() - 1]; + } +} + +// getFirstElement() -- returns the first element +function getFirstElement() { + if (this.data[0] != null) { + return this.data[0]; + } +} + +// getElementAt() -- returns an element at a specified index +function getElementAt(i) { + try { + return this.data[i]; + } + catch (e) { + return "Exception " + e + " occured when accessing " + i; + } +} + +// addElement() -- adds a element at the end of the Vector +function addElement(obj) { + if(this.getSize() == this.data.length) { + this.resize(); + } + this.data[this.size++] = obj; +} + +// insertElementAt() -- inserts an element at a given position +function insertElementAt(obj, index) { + try { + if (this.size == this.capacity) { + this.resize(); + } + + for (var i=this.getSize(); i > index; i--) { + this.data[i] = this.data[i-1]; + } + this.data[index] = obj; + this.size++; + } + catch (e) { + return "Invalid index " + i; + } +} + +// removeElementAt() -- removes an element at a specific index +function removeElementAt(index) { + try { + var element = this.data[index]; + + for(var i=index; i<(this.getSize()-1); i++) { + this.data[i] = this.data[i+1]; + } + + this.data[getSize()-1] = null; + this.size--; + return element; + } + catch(e) { + return "Invalid index " + index; + } +} + +// removeAllElements() -- removes all elements in the Vector +function removeAllElements() { + this.size = 0; + + for (var i=0; i=0 && compareValue > currentValue) { + this.data[j+1] = this.data[j]; + j--; + if (j >=0) { + compareObj = this.data[j]; + compareValue = compareObj[f]; + } + } + this.data[j+1] = currentObj; + } +} + +// clone() -- copies the contents of a Vector to another Vector returning the new Vector. +function clone() { + var newVector = new Vector(this.size); + + for (var i=0; i + + simple example build file + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/asm/gameport.asm b/tests/asm/gameport.asm new file mode 100644 index 0000000..fb920d9 --- /dev/null +++ b/tests/asm/gameport.asm @@ -0,0 +1,39 @@ +Game Port + +; GAMEPORT.ASM +; + + .MODEL TINY + + .DATA + + yes DB 13,10,"Game port is installed.",13,10,"$" + no DB 13,10,"Game port is not installed.",13,10,"$" + + .CODE + ORG 100h + +start: mov al, 1 ;value to write to port + mov dx, 201h ;port number + out dx, al ;write to port + mov cx, 0F00h ;# of loops + +port_loop: + in al, dx ;read from port + and al, 0Fh ;if jstick present, then AL should be + cmp al, 0Fh ; 0Fh after ANDing with 0Fh. + je jstick_exists + loop port_loop + mov dx, OFFSET no ;gameport not installed + jmp SHORT done + +jstick_exists: + mov dx, OFFSET yes ;gameport installed + +done: mov ah, 9h + int 21h + + mov ax, 4c00h + int 21h + +END start diff --git a/tests/asm/queueservers.asm b/tests/asm/queueservers.asm new file mode 100644 index 0000000..8d34da8 --- /dev/null +++ b/tests/asm/queueservers.asm @@ -0,0 +1,164 @@ +Gets a list of Queue servers under Novell Netware 3.11 + +%PAGESIZE 55,200 +%SUBTTL "Get List of Queue Servers under Netware 3.11" +; Net_Q.Asm +; + + .MODEL SMALL + + + .STACK 100h + +DOSint macro function + mov ah,function + int 21h +ENDM + + .DATA + STDOUT = 1 ; the stdout device handle + + DOS_WRITE_TO_HANDLE = 040h ; Write to File Handle + DOS_TERMINATE_EXE = 04Ch ; Terminate Program + + NOVELL_FUNCTION = 0E3h +; +; Object Types +; note that they're all big endian +; + OT_USER = 0100h + OT_USER_GROUP = 0200h + OT_PRINT_QUEUE = 0300h ; Print Queue object type + OT_FILE_SERVER = 0400h + + +BragMsg DB 0dh,0ah,"NET_Q.EXE",9,"WWW" + DB 9,"Version 1.00",0dh,0ah + DB 9,9,"released to the public domain by the author",0dh,0ah,0dh,0ah +BragLen = $ - BragMsg + +Crlf DB 0dh,0ah,0 + + SCAN_REQ STRUC ; bindery ScanObject request packet structure + MyLength DW 55 ; the length of this buffer + Function DB 37h ; scan object subfunction number + ObjectID DD -1 ; all ones for initial object search + ObjectType DW -1 ; wild card -- looks for all objects + ObjNameLen DB 1 ; at least one character + ObjName DB 47 DUP ('*') ; fill with wildcards to start + SCAN_REQ ENDS + + SCAN_REP STRUC ; bindery ScanObject request packet structure + MyLength DW 57 + RObjectID DD 0 ; all ones for initial object search + RObjectType DW 0 ; wild card -- looks for all objects + RObjName DB 48 DUP (0) ; fill with wildcards to start + ObjFlag DB 0 + ObjSecurty DB 0 + ObjHasProp DB 0 + ENDS + + ScanObjReq SCAN_REQ <> + ScanObjRep SCAN_REP <> + + .CODE + +; +; This is the main part of the code +; +; Test code gets and prints the name of all print queues from the +; logged server -- NO ERROR CHECKING IS DONE, so be careful! +; + +Start: + mov ax,@data + mov ds,ax ; set up the data segment + mov dx,OFFSET BragMsg ; prepare to print out brag line(s) + mov cx,BragLen + mov bx,STDOUT ; print to STDOUT + DOSint DOS_WRITE_TO_HANDLE + jc Exit ; if carry is set, there was an error + + mov [ScanObjReq.ObjectType],OT_PRINT_QUEUE + ; + ; in this case the name is already set up, (a wildcard) but if a + ; specific name were desired, it would be moved to + ; ScanObjReq.ObjName, with the appropriate length (not including + ; optional terminating NULL char set up in ScanObjReq.ObjNameLen. + ; +@@MoreQueues: + call BindScan + jc Exit + + lea dx,[ScanObjRep.ObjName] + call Puts + lea dx,[Crlf] + call Puts + jmp @@MoreQueues + +Exit: + DOSint DOS_TERMINATE_EXE ; return with error code preset in AL + +; +; BindScan +; +; scans the bindery for the object name set in the request buffer +; +BindScan proc + push ds si di es dx ax + + lea si,[ScanObjReq] ; point DS:DI to request buffer + mov dx,ds + mov es,dx + lea di,[ScanObjRep] ; point ES:SI to reply buffer + DOSint NOVELL_FUNCTION + jb @@Exit + + cld ; make sure to count up + mov si,OFFSET ScanObjRep.ObjectID + mov di,OFFSET ScanObjReq.ObjectID + movsw + movsw + + clc + +@@Exit: + pop ax dx es di si ds + ret + +BindScan endp + +; Puts +; +; prints a NUL terminated string to stdout +; +; INPUTS: ds:dx points to ASCIIZ string +; +; OUTPUTS: prints string to stdout +; +; RETURNS: ax = number of bytes actually printed +; carry set on error +; +; DESTROYED: ax +; +Puts proc + push bx cx di es + + push ds + pop es + mov cx,0ffffh ; maximum length of string + mov di,dx + cld + mov al,0 ; we're looking for NUL + repne scasb + dec di + mov cx,di + sub cx,dx + mov bx,STDOUT ; write to this device + DOSint DOS_WRITE_TO_HANDLE + + pop es di cx bx + ret +Puts endp + + END Start diff --git a/tests/asp/serialnumber.asp b/tests/asp/serialnumber.asp new file mode 100644 index 0000000..edaef7c --- /dev/null +++ b/tests/asp/serialnumber.asp @@ -0,0 +1,14 @@ + + + +<% +dim fs,d +set fs=Server.CreateObject("Scripting.FileSystemObject") +set d=fs.GetDrive("c:") +Response.Write("The serialnumber is " & d.SerialNumber) +set d=nothing +set fs=nothing +%> + + + diff --git a/tests/awk/ctime.awk b/tests/awk/ctime.awk new file mode 100644 index 0000000..0a50d26 --- /dev/null +++ b/tests/awk/ctime.awk @@ -0,0 +1,11 @@ +# ctime.awk +# +# awk version of C ctime(3) function + +function ctime(ts, format) +{ + format = "%a %b %d %H:%M:%S %Z %Y" + if (ts == 0) + ts = systime() # use current time as default + return strftime(format, ts) +} diff --git a/tests/basic/threads.bas b/tests/basic/threads.bas new file mode 100644 index 0000000..92b68f4 --- /dev/null +++ b/tests/basic/threads.bas @@ -0,0 +1,30 @@ +SuperStrict + +' Threading tutorial 1: +' A basic loading thread + + +' a threadable function +' threadable functions must return an Object and take 1 object as input, they don't need to be used +Function loadResources:Object(in:Object) + Print "Starting a child thread..." + For Local counter:Int = 0 Until 20 ' just a loop to make stuff happen + Print "Pretending to load resource " + counter + Delay(300) ' just to make this take some time like loading a real resource would + Next + Print "Child thread complete." +End Function + + + +'####### Main code starts here + +' Create a thread with loadResources() and Null as it's input object value +Local loadingThread:TThread = CreateThread(loadResources, Null) + +Print "Starting the main loop..." +While(ThreadRunning(loadingThread)) ' as long as that child thread is still running... + Print "Waiting on our resources..." + Delay(100) ' we could do whatever we want here... +Wend +Print "Main loop complete." diff --git a/tests/cobol/Acme99.cbl b/tests/cobol/Acme99.cbl new file mode 100644 index 0000000..3dca945 --- /dev/null +++ b/tests/cobol/Acme99.cbl @@ -0,0 +1,200 @@ + $ SET SOURCEFORMAT"FREE" +IDENTIFICATION DIVISION. +PROGRAM-ID. ACME99. +AUTHOR. Michael Coughlan. +*CS431399R-EXAM. + +ENVIRONMENT DIVISION. +INPUT-OUTPUT SECTION. +FILE-CONTROL. + SELECT ORDER-FILE ASSIGN TO "ORDERS.DAT" + ORGANIZATION IS LINE SEQUENTIAL. + + SELECT STOCK-FILE ASSIGN TO "STOCK.DAT" + ORGANIZATION IS RELATIVE + ACCESS MODE IS DYNAMIC + RELATIVE KEY IS STOCK-REC-POINTER-WB + FILE STATUS IS STOCK-STATUS-WB. + + SELECT MANF-FILE ASSIGN TO "MANF.DAT" + ORGANIZATION IS INDEXED + ACCESS MODE IS RANDOM + RECORD KEY IS MANF-CODE-FC + ALTERNATE RECORD KEY IS MANF-NAME-FC + WITH DUPLICATES + FILE STATUS IS MANF-STATUS-WB. + + + +DATA DIVISION. +FILE SECTION. +FD ORDER-FILE. +01 ORDER-REC-FA. + 02 ITEM-DESC-FA PIC X(30). + 02 MANF-NAME-FA PIC X(30). + 02 QTY-REQUIRED-FA PIC 9(6). + 02 COST-OF-ITEMS-FA PIC 9(5)V99. + 02 POSTAGE-FA PIC 99V99. + +FD STOCK-FILE. +01 STOCK-REC-FB. + 02 STOCK-NUM-FB PIC 9(5). + 02 MANF-CODE-FB PIC X(4). + 02 ITEM-DESC-FB PIC X(30). + 02 QTY-IN-STOCK-FB PIC 9(6). + 02 REORDER-LEVEL-FB PIC 999. + 02 REORDER-QTY-FB PIC 9(6). + 02 ITEM-COST-FB PIC 9(5). + 02 ITEM-WEIGHT-FB PIC 9(5). + 02 ON-ORDER-FB PIC X. + 88 NOT-ON-ORDER VALUE "N". + 88 ON-ORDER VALUE "Y". + +FD MANF-FILE. +01 MANF-REC-FC. + 02 MANF-CODE-FC PIC X(4). + 02 MANF-NAME-FC PIC X(30). + 02 MANF-ADDRESS-FC PIC X(70). + + + + +WORKING-STORAGE SECTION. +01 CALL-ITEMS-WA. + 02 POST-CHARGE-WA PIC 99V99. + 02 POST-NUM-WA PIC 99. + +01 FILE-DATA-WB. + 02 STOCK-REC-POINTER-WB PIC 9(5). + 02 STOCK-STATUS-WB PIC XX. + 02 MANF-STATUS-WB PIC XX. + 02 FILLER PIC 9 VALUE 0. + 88 END-OF-FILE VALUE 1. + +01 UNSTRING-DATA-WC. + 02 UNSTRING-POINTER-WC PIC 99. + 88 END-OF-ADDRESS VALUE 71. + 02 HOLD-STRING-WC PIC X(10). + 02 COUNTY-WC PIC X(9). + 88 NORTHERN-COUNTY + VALUE "ANTRIM", "ARMAGH", "DERRY", "DOWN", + "FERMANAGH", "TYRONE". + 02 COUNTRY-WC PIC X(10). + 88 EEC-COUNTRY + VALUE "AUSTRIA", "BELGIUM", "DENMARK", "ENGLAND", "FINLAND", + "FRANCE", "GERMANY", "GREECE", "IRELAND", "ITALY", + "LUXEMBOURG", "PORTUGAL", "SCOTLAND", "SPAIN", + "SWEDEN", "WALES". + 88 IRELAND VALUE "IRELAND". + + 02 COUNTRY-FLAGS-WC PIC 9. + 88 OTHER-EEC VALUE 1. + 88 REPUBLIC VALUE 0. + +01 POSTAGE-DATA-WD. + 02 TOTAL-WEIGHT-WD PIC 9(5). + 88 OVER-WEIGHT VALUE 50001 THRU 99999. + + + +PROCEDURE DIVISION. +CREATE-REORDER-FILE. + OPEN I-O STOCK-FILE. + OPEN INPUT MANF-FILE. + OPEN OUTPUT ORDER-FILE. + READ STOCK-FILE NEXT RECORD + AT END SET END-OF-FILE TO TRUE + END-READ. + PERFORM UNTIL END-OF-FILE + IF (QTY-IN-STOCK-FB NOT GREATER THAN REORDER-LEVEL-FB) + AND (NOT-ON-ORDER) + PERFORM CREATE-REORDER-RECORD + PERFORM UPDATE-STOCK-RECORD + END-IF + READ STOCK-FILE NEXT RECORD + AT END SET END-OF-FILE TO TRUE + END-READ + END-PERFORM. + CLOSE STOCK-FILE, MANF-FILE, ORDER-FILE. + STOP RUN. + +CREATE-REORDER-RECORD. + MOVE MANF-CODE-FB TO MANF-CODE-FC. + READ MANF-FILE + KEY IS MANF-CODE-FC + INVALID KEY DISPLAY "CRR MANF STATUS = " + MANF-STATUS-WB "CODE = " MANF-CODE-FC + END-READ. + PERFORM EXTRACT-ADDRESS-ITEMS. + + MOVE ZEROS TO POSTAGE-FA, COST-OF-ITEMS-FA. + IF EEC-COUNTRY + PERFORM GET-POSTAGE + MULTIPLY ITEM-COST-FB BY REORDER-QTY-FB + GIVING COST-OF-ITEMS-FA + MOVE POST-CHARGE-WA TO POSTAGE-FA + END-IF. + + MOVE ITEM-DESC-FB TO ITEM-DESC-FA. + MOVE MANF-NAME-FC TO MANF-NAME-FA. + MOVE REORDER-QTY-FB TO QTY-REQUIRED-FA. + WRITE ORDER-REC-FA. + +GET-POSTAGE. + IF IRELAND AND NOT NORTHERN-COUNTY + SET REPUBLIC TO TRUE + ELSE + SET OTHER-EEC TO TRUE + END-IF. + MULTIPLY ITEM-WEIGHT-FB BY REORDER-QTY-FB + GIVING TOTAL-WEIGHT-WD + ON SIZE ERROR MOVE 99999 TO TOTAL-WEIGHT-WD. + + EVALUATE TOTAL-WEIGHT-WD ALSO REPUBLIC ALSO OTHER-EEC + WHEN 1 THRU 500 ALSO TRUE ALSO FALSE MOVE 1 TO POST-NUM-WA + WHEN 1 THRU 500 ALSO FALSE ALSO TRUE MOVE 2 TO POST-NUM-WA + WHEN 501 THRU 1000 ALSO TRUE ALSO FALSE MOVE 3 TO POST-NUM-WA + WHEN 501 THRU 1000 ALSO FALSE ALSO TRUE MOVE 4 TO POST-NUM-WA + WHEN 1001 THRU 3000 ALSO TRUE ALSO FALSE MOVE 5 TO POST-NUM-WA + WHEN 1001 THRU 3000 ALSO FALSE ALSO TRUE MOVE 6 TO POST-NUM-WA + WHEN 3001 THRU 5000 ALSO TRUE ALSO FALSE MOVE 7 TO POST-NUM-WA + WHEN 3001 THRU 5000 ALSO FALSE ALSO TRUE MOVE 8 TO POST-NUM-WA + WHEN 5001 THRU 10000 ALSO TRUE ALSO FALSE MOVE 9 TO POST-NUM-WA + WHEN 5001 THRU 10000 ALSO FALSE ALSO TRUE MOVE 10 TO POST-NUM-WA + WHEN 10001 THRU 50000 ALSO TRUE ALSO FALSE MOVE 11 TO POST-NUM-WA + WHEN 10001 THRU 50000 ALSO FALSE ALSO TRUE MOVE 12 TO POST-NUM-WA + WHEN 50001 THRU 99999 ALSO ANY ALSO ANY MOVE ZEROS + TO POST-CHARGE-WA + WHEN OTHER DISPLAY "EVALUATE WRONG:- WEIGHT = " TOTAL-WEIGHT-WD + " COUNTRY FLAG = " COUNTRY-FLAGS-WC + END-EVALUATE. + IF NOT OVER-WEIGHT + CALL "POSTAGE-RATE" + USING BY CONTENT POST-NUM-WA + BY REFERENCE POST-CHARGE-WA + END-IF. + + + +UPDATE-STOCK-RECORD. + MOVE "Y" TO ON-ORDER-FB. + REWRITE STOCK-REC-FB + INVALID KEY DISPLAY "STOCK REWRITE STATUS = " STOCK-STATUS-WB + END-REWRITE. + + + +EXTRACT-ADDRESS-ITEMS. + MOVE 1 TO UNSTRING-POINTER-WC. + PERFORM UNTIL END-OF-ADDRESS + MOVE HOLD-STRING-WC TO COUNTY-WC + UNSTRING MANF-ADDRESS-FC DELIMITED BY "," + INTO HOLD-STRING-WC + WITH POINTER UNSTRING-POINTER-WC + END-PERFORM. + MOVE HOLD-STRING-WC TO COUNTRY-WC. + +*debugging displays + DISPLAY "COUNTY = " COUNTY-WC. + DISPLAY "COUNTRY = " COUNTRY-WC. + diff --git a/tests/cobol/DriverProg.cbl b/tests/cobol/DriverProg.cbl new file mode 100644 index 0000000..7af7f1d --- /dev/null +++ b/tests/cobol/DriverProg.cbl @@ -0,0 +1,132 @@ + $ SET SOURCEFORMAT"FREE" +IDENTIFICATION DIVISION. +PROGRAM-ID. DriverProg. +AUTHOR. Michael Coughlan. +* This program demonstrates the use of the CALL verb +* it calls three external sub-programs that help to demonstrate +* some of the features of the CALL. +* The "MultiplyNums" sub-program takes five parameters. The first two +* are the numbers to be multiplied, the second two are strings to +* demonstrate that strings can be passed as parameters and the +* last is the returned result of multiplying the two numbers. +* The "Fickle" sub-program demonstrates a program that exhibits +* State Memory. +* The "Steadfast" sub-program demonstrates how a sub-program that +* uses the IS INITIAL phrase can avoid State Memory. + +ENVIRONMENT DIVISION. +DATA DIVISION. + +WORKING-STORAGE SECTION. +01 UserNumber PIC 99. + +01 PrnResult PIC 9(6). +* field declared as COMP cannot be DISPLAYed +* it is necessary to move it to a DISPLAY field. +* DISPLAY is the default value for a field and +* need not be declared. + + +* Parameters must be either 01-level's or elementry +* data-items. +01 Parameters. + 02 Number1 PIC 9(3). + 02 Number2 PIC 9(3). + 02 FirstString PIC X(19) VALUE "First parameter = ". + 02 SecondString PIC X(19) VALUE "Second parameter = ". + 02 Result PIC 9(6) COMP. +* I've made this a COMP field to demonstrate that COMP +* items can be passed as parameters but a COMP field cannot +* be DISPLAYed and so is moved to a DISPLAY field before DISPLAYing it. + + + +PROCEDURE DIVISION. +Begin. + PERFORM CallMultiplyNums. + PERFORM CallFickle + PERFORM CallSteadfast + + PERFORM MakeFickleSteadfast. + + STOP RUN. + + +CallMultiplyNums. + DISPLAY "Input 2 numbers (3 digits each) to be multiplied" + DISPLAY "First number - " WITH NO ADVANCING + ACCEPT Number1 + DISPLAY "Second number - " WITH NO ADVANCING + ACCEPT Number2. + DISPLAY "The first string is " FirstString. + DISPLAY "The second string is " SecondString. + DISPLAY ">>>>>>>>> Calling the sub-program now". + + CALL "MultiplyNums" + USING BY CONTENT Number1, Number2, FirstString, + BY REFERENCE SecondString, Result. + +* The USING phrase specifies the parameters to be passed to the +* sub-program. The order of the parameters is important as the +* sub-program recognizes them by relative location not by name +* +* Parameters should be passed BY CONTENT when you are not expecting +* them to get a value from the called program. We have not passed +* SecondString by content and you can see that its value is +* overwritten by the called program. + + DISPLAY "Back in the main program now <<<<<<<<<<<". + MOVE Result to PrnResult. + DISPLAY Number1 " multiplied by " Number2 " is = " PrnResult. + + DISPLAY "The first string is " FirstString. + DISPLAY "The second string is " SecondString. + + +CallFickle. + DISPLAY SPACE + DISPLAY "------------------- Calling Fickle ---------" + MOVE 10 TO UserNumber + CALL "Fickle" USING BY CONTENT UserNumber + MOVE 10 TO UserNumber + CALL "Fickle" USING BY CONTENT UserNumber + MOVE 10 TO UserNumber + CALL "Fickle" USING BY CONTENT UserNumber. +* Every time I call Fickle with the same value +* produces a different result. This is because +* it remembers its state from one call to the next. +* It has "State Memory". + + +CallSteadFast. + DISPLAY SPACE + DISPLAY "------------------- Calling Steadfast ---------" + MOVE 10 TO UserNumber + CALL "Steadfast" USING BY CONTENT UserNumber + MOVE 10 TO UserNumber + CALL "Steadfast" USING BY CONTENT UserNumber + MOVE 10 TO UserNumber + CALL "Steadfast" USING BY CONTENT UserNumber. +* Every time I call Steadfast with the same value +* it produces the same result. We have eliminated +* State Memory by using the IS INITIAL phrase in +* Steadfast + + +MakeFickleSteadfast. + DISPLAY SPACE + DISPLAY "----- Making fickle act like Steadfast -------" + CANCEL "Fickle" + MOVE 10 TO UserNumber + CALL "Fickle" USING BY CONTENT UserNumber + + CANCEL "Fickle" + MOVE 10 TO UserNumber + CALL "Fickle" USING BY CONTENT UserNumber + + CANCEL "Fickle" + MOVE 10 TO UserNumber + CALL "Fickle" USING BY CONTENT UserNumber. +* We can make Fickle act like Steadfast by using +* the CANCEL verb to set it into its initial state +* each time we call it diff --git a/tests/coffee/campfire.coffee b/tests/coffee/campfire.coffee new file mode 100644 index 0000000..6fb5586 --- /dev/null +++ b/tests/coffee/campfire.coffee @@ -0,0 +1,251 @@ +Robot = require '../robot' +Adapter = require '../adapter' + +HTTPS = require 'https' +EventEmitter = require('events').EventEmitter + +class Campfire extends Adapter + + send: (user, strings...) -> + if strings.length > 0 + @bot.Room(user.room).speak strings.shift(), (err, data) => + @robot.logger.error "Campfire error: #{err}" if err? + @send user, strings... + + reply: (user, strings...) -> + @send user, strings.map((str) -> "#{user.name}: #{str}")... + + topic: (user, strings...) -> + @bot.Room(user.room).topic strings.join(" / "), (err, data) => + @robot.logger.error "Campfire error: #{err}" if err? + + run: -> + self = @ + + options = + token: process.env.HUBOT_CAMPFIRE_TOKEN + rooms: process.env.HUBOT_CAMPFIRE_ROOMS + account: process.env.HUBOT_CAMPFIRE_ACCOUNT + + bot = new CampfireStreaming(options, @robot) + + withAuthor = (callback) -> (id, created, room, user, body) -> + bot.User user, (err, userData) -> + if userData.user + author = self.userForId(userData.user.id, userData.user) + self.robot.brain.data.users[userData.user.id].name = userData.user.name + self.robot.brain.data.users[userData.user.id].email_address = userData.user.email_address + author.room = room + callback id, created, room, user, body, author + + bot.on "TextMessage", withAuthor (id, created, room, user, body, author) -> + unless bot.info.id == author.id + self.receive new Robot.TextMessage(author, body) + + bot.on "EnterMessage", withAuthor (id, created, room, user, body, author) -> + unless bot.info.id == author.id + self.receive new Robot.EnterMessage(author) + + bot.on "LeaveMessage", withAuthor (id, created, room, user, body, author) -> + unless bot.info.id == author.id + self.receive new Robot.LeaveMessage(author) + + bot.Me (err, data) -> + bot.info = data.user + bot.name = bot.info.name + + for roomId in bot.rooms + do (roomId) -> + bot.Room(roomId).join (err, callback) -> + bot.Room(roomId).listen() + + bot.on "reconnect", (roomId) -> + bot.Room(roomId).join (err, callback) -> + bot.Room(roomId).listen() + + @bot = bot + + self.emit "connected" + +exports.use = (robot) -> + new Campfire robot + +class CampfireStreaming extends EventEmitter + constructor: (options, @robot) -> + unless options.token? and options.rooms? and options.account? + @robot.logger.error "Not enough parameters provided. I Need a token, rooms and account" + process.exit(1) + + @token = options.token + @rooms = options.rooms.split(",") + @account = options.account + @domain = @account + ".campfirenow.com" + @authorization = "Basic " + new Buffer("#{@token}:x").toString("base64") + + Rooms: (callback) -> + @get "/rooms", callback + + User: (id, callback) -> + @get "/users/#{id}", callback + + Me: (callback) -> + @get "/users/me", callback + + Room: (id) -> + self = @ + logger = @robot.logger + + show: (callback) -> + self.post "/room/#{id}", "", callback + + join: (callback) -> + self.post "/room/#{id}/join", "", callback + + leave: (callback) -> + self.post "/room/#{id}/leave", "", callback + + lock: (callback) -> + self.post "/room/#{id}/lock", "", callback + + unlock: (callback) -> + self.post "/room/#{id}/unlock", "", callback + + # say things to this channel on behalf of the token user + paste: (text, callback) -> + @message text, "PasteMessage", callback + + topic: (text, callback) -> + body = {room: {topic: text}} + self.put "/room/#{id}", body, callback + + sound: (text, callback) -> + @message text, "SoundMessage", callback + + speak: (text, callback) -> + body = { message: { "body":text } } + self.post "/room/#{id}/speak", body, callback + + message: (text, type, callback) -> + body = { message: { "body":text, "type":type } } + self.post "/room/#{id}/speak", body, callback + + # listen for activity in channels + listen: -> + headers = + "Host" : "streaming.campfirenow.com" + "Authorization" : self.authorization + + options = + "agent" : false + "host" : "streaming.campfirenow.com" + "port" : 443 + "path" : "/room/#{id}/live.json" + "method" : "GET" + "headers": headers + + request = HTTPS.request options, (response) -> + response.setEncoding("utf8") + + buf = '' + + response.on "data", (chunk) -> + if chunk is ' ' + # campfire api sends a ' ' heartbeat every 3s + + else if chunk.match(/^\s*Access Denied/) + # errors are not json formatted + logger.error "Campfire error on room #{id}: #{chunk}" + + else + # api uses newline terminated json payloads + # buffer across tcp packets and parse out lines + buf += chunk + + while (offset = buf.indexOf("\r")) > -1 + part = buf.substr(0, offset) + buf = buf.substr(offset + 1) + + if part + try + data = JSON.parse part + self.emit data.type, data.id, data.created_at, data.room_id, data.user_id, data.body + catch err + logger.error "Campfire error: #{err}" + + response.on "end", -> + logger.error "Streaming connection closed for room #{id}. :(" + setTimeout (-> + self.emit "reconnect", id + ), 5000 + + response.on "error", (err) -> + logger.error "Campfire response error: #{err}" + + request.on "error", (err) -> + logger.error "Campfire request error: #{err}" + + request.end() + + # Convenience HTTP Methods for posting on behalf of the token"d user + get: (path, callback) -> + @request "GET", path, null, callback + + post: (path, body, callback) -> + @request "POST", path, body, callback + + put: (path, body, callback) -> + @request "PUT", path, body, callback + + request: (method, path, body, callback) -> + logger = @robot.logger + + headers = + "Authorization" : @authorization + "Host" : @domain + "Content-Type" : "application/json" + + options = + "agent" : false + "host" : @domain + "port" : 443 + "path" : path + "method" : method + "headers": headers + + if method is "POST" || method is "PUT" + if typeof(body) isnt "string" + body = JSON.stringify body + + body = new Buffer(body) + options.headers["Content-Length"] = body.length + + request = HTTPS.request options, (response) -> + data = "" + + response.on "data", (chunk) -> + data += chunk + + response.on "end", -> + if response.statusCode >= 400 + switch response.statusCode + when 401 + throw new Error "Invalid access token provided, campfire refused the authentication" + else + logger.error "Campfire error: #{response.statusCode}" + + try + callback null, JSON.parse(data) + catch err + callback null, data or { } + + response.on "error", (err) -> + logger.error "Campfire response error: #{err}" + callback err, { } + + if method is "POST" || method is "PUT" + request.end(body, 'binary') + else + request.end() + + request.on "error", (err) -> + logger.error "Campfire request error: #{err}" diff --git a/tests/cpp/DeadlockDetector.h b/tests/cpp/DeadlockDetector.h new file mode 100644 index 0000000..0f9e3ab --- /dev/null +++ b/tests/cpp/DeadlockDetector.h @@ -0,0 +1,588 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: sw=4 ts=4 et : + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is mozilla.org code. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Chris Jones + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ +#ifndef mozilla_DeadlockDetector_h +#define mozilla_DeadlockDetector_h + +#include + +#include "plhash.h" +#include "prlock.h" + +#include "nsTArray.h" + +#ifdef NS_TRACE_MALLOC +# include "nsTraceMalloc.h" +#endif // ifdef NS_TRACE_MALLOC + +namespace mozilla { + + +// FIXME bug 456272: split this off into a convenience API on top of +// nsStackWalk? +class NS_COM_GLUE CallStack +{ +private: +#ifdef NS_TRACE_MALLOC + typedef nsTMStackTraceID callstack_id; + // needs to be a macro to avoid disturbing the backtrace +# define NS_GET_BACKTRACE() NS_TraceMallocGetStackTrace() +#else + typedef void* callstack_id; +# define NS_GET_BACKTRACE() 0 +#endif // ifdef NS_TRACE_MALLOC + + callstack_id mCallStack; + +public: + /** + * CallStack + * *ALWAYS* *ALWAYS* *ALWAYS* call this with no arguments. This + * constructor takes an argument *ONLY* so that |GET_BACKTRACE()| + * can be evaluated in the stack frame of the caller, rather than + * that of the constructor. + * + * *BEWARE*: this means that calling this constructor with no + * arguments is not the same as a "default, do-nothing" + * constructor: it *will* construct a backtrace. This can cause + * unexpected performance issues. + */ + CallStack(const callstack_id aCallStack = NS_GET_BACKTRACE()) : + mCallStack(aCallStack) + { + } + CallStack(const CallStack& aFrom) : + mCallStack(aFrom.mCallStack) + { + } + CallStack& operator=(const CallStack& aFrom) + { + mCallStack = aFrom.mCallStack; + return *this; + } + bool operator==(const CallStack& aOther) const + { + return mCallStack == aOther.mCallStack; + } + bool operator!=(const CallStack& aOther) const + { + return mCallStack != aOther.mCallStack; + } + + // FIXME bug 456272: if this is split off, + // NS_TraceMallocPrintStackTrace should be modified to print into + // an nsACString + void Print(FILE* f) const + { +#ifdef NS_TRACE_MALLOC + if (this != &kNone && mCallStack) { + NS_TraceMallocPrintStackTrace(f, mCallStack); + return; + } +#endif + fputs(" [stack trace unavailable]\n", f); + } + + /** The "null" callstack. */ + static const CallStack kNone; +}; + + +/** + * DeadlockDetector + * + * The following is an approximate description of how the deadlock detector + * works. + * + * The deadlock detector ensures that all blocking resources are + * acquired according to a partial order P. One type of blocking + * resource is a lock. If a lock l1 is acquired (locked) before l2, + * then we say that |l1 <_P l2|. The detector flags an error if two + * locks l1 and l2 have an inconsistent ordering in P; that is, if + * both |l1 <_P l2| and |l2 <_P l1|. This is a potential error + * because a thread acquiring l1,l2 according to the first order might + * race with a thread acquiring them according to the second order. + * If this happens under the right conditions, then the acquisitions + * will deadlock. + * + * This deadlock detector doesn't know at compile-time what P is. So, + * it tries to discover the order at run time. More precisely, it + * finds some order P, then tries to find chains of resource + * acquisitions that violate P. An example acquisition sequence, and + * the orders they impose, is + * l1.lock() // current chain: [ l1 ] + * // order: { } + * + * l2.lock() // current chain: [ l1, l2 ] + * // order: { l1 <_P l2 } + * + * l3.lock() // current chain: [ l1, l2, l3 ] + * // order: { l1 <_P l2, l2 <_P l3, l1 <_P l3 } + * // (note: <_P is transitive, so also |l1 <_P l3|) + * + * l2.unlock() // current chain: [ l1, l3 ] + * // order: { l1 <_P l2, l2 <_P l3, l1 <_P l3 } + * // (note: it's OK, but weird, that l2 was unlocked out + * // of order. we still have l1 <_P l3). + * + * l2.lock() // current chain: [ l1, l3, l2 ] + * // order: { l1 <_P l2, l2 <_P l3, l1 <_P l3, + * l3 <_P l2 (!!!) } + * BEEP BEEP! Here the detector will flag a potential error, since + * l2 and l3 were used inconsistently (and potentially in ways that + * would deadlock). + */ +template +class DeadlockDetector +{ +public: + /** + * ResourceAcquisition + * Consists simply of a resource and the calling context from + * which it was acquired. We pack this information together so + * that it can be returned back to the caller when a potential + * deadlock has been found. + */ + struct ResourceAcquisition + { + const T* mResource; + CallStack mCallContext; + + ResourceAcquisition( + const T* aResource, + const CallStack aCallContext=CallStack::kNone) : + mResource(aResource), + mCallContext(aCallContext) + { + } + ResourceAcquisition(const ResourceAcquisition& aFrom) : + mResource(aFrom.mResource), + mCallContext(aFrom.mCallContext) + { + } + ResourceAcquisition& operator=(const ResourceAcquisition& aFrom) + { + mResource = aFrom.mResource; + mCallContext = aFrom.mCallContext; + return *this; + } + }; + typedef nsTArray ResourceAcquisitionArray; + +private: + typedef nsTArray HashEntryArray; + typedef typename HashEntryArray::index_type index_type; + typedef typename HashEntryArray::size_type size_type; + enum { + NoIndex = HashEntryArray::NoIndex + }; + + /** + * Value type for the ordering table. Contains the other + * resources on which an ordering constraint |key < other| + * exists. The catch is that we also store the calling context at + * which the other resource was acquired; this improves the + * quality of error messages when potential deadlock is detected. + */ + struct OrderingEntry + { + OrderingEntry() : + mFirstSeen(CallStack::kNone), + mOrderedLT() // FIXME bug 456272: set to empirical + { // dep size? + } + ~OrderingEntry() + { + } + + CallStack mFirstSeen; // first site from which the resource appeared + HashEntryArray mOrderedLT; // this <_o Other + }; + + static void* TableAlloc(void* /*pool*/, PRSize size) + { + return operator new(size); + } + static void TableFree(void* /*pool*/, void* item) + { + operator delete(item); + } + static PLHashEntry* EntryAlloc(void* /*pool*/, const void* key) + { + return new PLHashEntry; + } + static void EntryFree(void* /*pool*/, PLHashEntry* entry, PRUintn flag) + { + delete static_cast(const_cast(entry->key)); + delete static_cast(entry->value); + entry->value = 0; + if (HT_FREE_ENTRY == flag) + delete entry; + } + static PLHashNumber HashKey(const void* aKey) + { + return NS_PTR_TO_INT32(aKey) >> 2; + } + static const PLHashAllocOps kAllocOps; + + // Hash table "interface" the rest of the code should use + + PLHashEntry** GetEntry(const T* aKey) + { + return PL_HashTableRawLookup(mOrdering, HashKey(aKey), aKey); + } + + void PutEntry(T* aKey) + { + PL_HashTableAdd(mOrdering, aKey, new OrderingEntry()); + } + + // XXX need these helper methods because OrderingEntry doesn't have + // XXX access to underlying PLHashEntry + + /** + * Add the order |aFirst <_o aSecond|. + * + * WARNING: this does not check whether it's sane to add this + * order. In the "best" bad case, when this order already exists, + * adding it anyway may unnecessarily result in O(n^2) space. In + * the "worst" bad case, adding it anyway will cause + * |InTransitiveClosure()| to diverge. + */ + void AddOrder(PLHashEntry* aLT, PLHashEntry* aGT) + { + static_cast(aLT->value)->mOrderedLT + .InsertElementSorted(aGT); + } + + /** + * Return true iff the order |aFirst < aSecond| has been + * *explicitly* added. + * + * Does not consider transitivity. + */ + bool IsOrdered(const PLHashEntry* aFirst, const PLHashEntry* aSecond) + const + { + return NoIndex != + static_cast(aFirst->value)->mOrderedLT + .BinaryIndexOf(aSecond); + } + + /** + * Return a pointer to the array of all elements "that" for + * which the order |this < that| has been explicitly added. + * + * NOTE: this does *not* consider transitive orderings. + */ + PLHashEntry* const* GetOrders(const PLHashEntry* aEntry) const + { + return static_cast(aEntry->value)->mOrderedLT + .Elements(); + } + + /** + * Return the number of elements "that" for which the order + * |this < that| has been explicitly added. + * + * NOTE: this does *not* consider transitive orderings. + */ + size_type NumOrders(const PLHashEntry* aEntry) const + { + return static_cast(aEntry->value)->mOrderedLT + .Length(); + } + + /** Make a ResourceAcquisition out of |aEntry|. */ + ResourceAcquisition MakeResourceAcquisition(const PLHashEntry* aEntry) + const + { + return ResourceAcquisition( + static_cast(aEntry->key), + static_cast(aEntry->value)->mFirstSeen); + } + + // Throwaway RAII lock to make the following code safer. + struct PRAutoLock + { + PRAutoLock(PRLock* aLock) : mLock(aLock) { PR_Lock(mLock); } + ~PRAutoLock() { PR_Unlock(mLock); } + PRLock* mLock; + }; + +public: + static const PRUint32 kDefaultNumBuckets; + + /** + * DeadlockDetector + * Create a new deadlock detector. + * + * @param aNumResourcesGuess Guess at approximate number of resources + * that will be checked. + */ + DeadlockDetector(PRUint32 aNumResourcesGuess = kDefaultNumBuckets) + { + mOrdering = PL_NewHashTable(aNumResourcesGuess, + HashKey, + PL_CompareValues, PL_CompareValues, + &kAllocOps, 0); + if (!mOrdering) + NS_RUNTIMEABORT("couldn't initialize resource ordering table"); + + mLock = PR_NewLock(); + if (!mLock) + NS_RUNTIMEABORT("couldn't allocate deadlock detector lock"); + } + + /** + * ~DeadlockDetector + * + * *NOT* thread safe. + */ + ~DeadlockDetector() + { + PL_HashTableDestroy(mOrdering); + PR_DestroyLock(mLock); + } + + /** + * Add + * Make the deadlock detector aware of |aResource|. + * + * WARNING: The deadlock detector owns |aResource|. + * + * Thread safe. + * + * @param aResource Resource to make deadlock detector aware of. + */ + void Add(T* aResource) + { + PRAutoLock _(mLock); + PutEntry(aResource); + } + + // Nb: implementing a Remove() method makes the detector "more + // unsound." By removing a resource from the orderings, deadlocks + // may be missed that would otherwise have been found. However, + // removing resources possibly reduces the # of false positives, + // and additionally saves space. So it's a trade off; we have + // chosen to err on the side of caution and not implement Remove(). + + /** + * CheckAcquisition This method is called after acquiring |aLast|, + * but before trying to acquire |aProposed| from |aCallContext|. + * It determines whether actually trying to acquire |aProposed| + * will create problems. It is OK if |aLast| is NULL; this is + * interpreted as |aProposed| being the thread's first acquisition + * of its current chain. + * + * Iff acquiring |aProposed| may lead to deadlock for some thread + * interleaving (including the current one!), the cyclical + * dependency from which this was deduced is returned. Otherwise, + * 0 is returned. + * + * If a potential deadlock is detected and a resource cycle is + * returned, it is the *caller's* responsibility to free it. + * + * Thread safe. + * + * @param aLast Last resource acquired by calling thread (or 0). + * @param aProposed Resource calling thread proposes to acquire. + * @param aCallContext Calling context whence acquisiton request came. + */ + ResourceAcquisitionArray* CheckAcquisition(const T* aLast, + const T* aProposed, + const CallStack& aCallContext) + { + NS_ASSERTION(aProposed, "null resource"); + PRAutoLock _(mLock); + + PLHashEntry* second = *GetEntry(aProposed); + OrderingEntry* e = static_cast(second->value); + if (CallStack::kNone == e->mFirstSeen) + e->mFirstSeen = aCallContext; + + if (!aLast) + // don't check if |0 < proposed|; just vamoose + return 0; + + PLHashEntry* first = *GetEntry(aLast); + + // this is the crux of the deadlock detector algorithm + + if (first == second) { + // reflexive deadlock. fastpath b/c InTransitiveClosure is + // not applicable here. + ResourceAcquisitionArray* cycle = new ResourceAcquisitionArray(); + if (!cycle) + NS_RUNTIMEABORT("can't allocate dep. cycle array"); + cycle->AppendElement(MakeResourceAcquisition(first)); + cycle->AppendElement(ResourceAcquisition(aProposed, + aCallContext)); + return cycle; + } + if (InTransitiveClosure(first, second)) { + // we've already established |last < proposed|. all is well. + return 0; + } + if (InTransitiveClosure(second, first)) { + // the order |proposed < last| has been deduced, perhaps + // transitively. we're attempting to violate that + // constraint by acquiring resources in the order + // |last < proposed|, and thus we may deadlock under the + // right conditions. + ResourceAcquisitionArray* cycle = GetDeductionChain(second, first); + // show how acquiring |proposed| would complete the cycle + cycle->AppendElement(ResourceAcquisition(aProposed, + aCallContext)); + return cycle; + } + // |last|, |proposed| are unordered according to our + // poset. this is fine, but we now need to add this + // ordering constraint. + AddOrder(first, second); + return 0; + } + + /** + * Return true iff |aTarget| is in the transitive closure of |aStart| + * over the ordering relation `<_this'. + * + * @precondition |aStart != aTarget| + */ + bool InTransitiveClosure(const PLHashEntry* aStart, + const PLHashEntry* aTarget) const + { + if (IsOrdered(aStart, aTarget)) + return true; + + index_type i = 0; + size_type len = NumOrders(aStart); + for (const PLHashEntry* const* it = GetOrders(aStart); + i < len; ++i, ++it) + if (InTransitiveClosure(*it, aTarget)) + return true; + return false; + } + + /** + * Return an array of all resource acquisitions + * aStart <_this r1 <_this r2 <_ ... <_ aTarget + * from which |aStart <_this aTarget| was deduced, including + * |aStart| and |aTarget|. + * + * Nb: there may be multiple deductions of |aStart <_this + * aTarget|. This function returns the first ordering found by + * depth-first search. + * + * Nb: |InTransitiveClosure| could be replaced by this function. + * However, this one is more expensive because we record the DFS + * search stack on the heap whereas the other doesn't. + * + * @precondition |aStart != aTarget| + */ + ResourceAcquisitionArray* GetDeductionChain( + const PLHashEntry* aStart, + const PLHashEntry* aTarget) + { + ResourceAcquisitionArray* chain = new ResourceAcquisitionArray(); + if (!chain) + NS_RUNTIMEABORT("can't allocate dep. cycle array"); + chain->AppendElement(MakeResourceAcquisition(aStart)); + + NS_ASSERTION(GetDeductionChain_Helper(aStart, aTarget, chain), + "GetDeductionChain called when there's no deadlock"); + return chain; + } + + // precondition: |aStart != aTarget| + // invariant: |aStart| is the last element in |aChain| + bool GetDeductionChain_Helper(const PLHashEntry* aStart, + const PLHashEntry* aTarget, + ResourceAcquisitionArray* aChain) + { + if (IsOrdered(aStart, aTarget)) { + aChain->AppendElement(MakeResourceAcquisition(aTarget)); + return true; + } + + index_type i = 0; + size_type len = NumOrders(aStart); + for (const PLHashEntry* const* it = GetOrders(aStart); + i < len; ++i, ++it) { + aChain->AppendElement(MakeResourceAcquisition(*it)); + if (GetDeductionChain_Helper(*it, aTarget, aChain)) + return true; + aChain->RemoveElementAt(aChain->Length() - 1); + } + return false; + } + + /** + * The partial order on resource acquisitions used by the deadlock + * detector. + */ + PLHashTable* mOrdering; // T* -> PLHashEntry + + /** + * Protects contentious methods. + * Nb: can't use mozilla::Mutex since we are used as its deadlock + * detector. + */ + PRLock* mLock; + + DeadlockDetector(const DeadlockDetector& aDD); + DeadlockDetector& operator=(const DeadlockDetector& aDD); +}; + + +template +const PLHashAllocOps DeadlockDetector::kAllocOps = { + DeadlockDetector::TableAlloc, DeadlockDetector::TableFree, + DeadlockDetector::EntryAlloc, DeadlockDetector::EntryFree +}; + + +template +// FIXME bug 456272: tune based on average workload +const PRUint32 DeadlockDetector::kDefaultNumBuckets = 64; + + +} // namespace mozilla + +#endif // ifndef mozilla_DeadlockDetector_h diff --git a/tests/cpp/ceded-test.cpp b/tests/cpp/ceded-test.cpp new file mode 100644 index 0000000..5a706e8 --- /dev/null +++ b/tests/cpp/ceded-test.cpp @@ -0,0 +1,597 @@ +/* Test file for C++ language. + * Attempt to include as many aspects of the C++ language as possible. + * Do not include things tested in test.c since that shares the + * same language. + * + * $Id: test.cpp,v 1.22 2008/05/17 20:12:55 zappo Exp $ + * + */ + +/* An include test */ +#include + +#include + +#include "c++-test.hh" + +#include + +double var1 = 1.2; + +int simple1(int a) { + +} + +struct foo1 { + int test; +}; + +struct foo2 : public foo1 { + const int foo21(int a, int b); + const int foo22(int a, int b) { return 1 } +}; + +/* Classes */ +class class1 { +private: + int var11; + struct foo1 var12; +public: + int p_var11; + struct foo p_var12; +}; + +class i_class1 : public class1 { +private: + int var11; + struct foo var12; +public: + int p_var11; + struct foo p_var12; +}; + +class class2 { +private: + int var21; + struct foo var22; +public: + int p_var21; + struct foo p_var22; +}; + +class i_class2 : public class1, public class2 { +private: + int var21; + struct foo var22; +protected: + int pt_var21; +public: + int p_var21; + struct foo p_var22; +}; + +class class3 { + /* A class with strange things in it */ +public: + class3(); /* A constructor */ + enum embedded_foo_enum { + a, b, c + } embed1; + struct embedded_bar_struct { + int a; + int b; + } embed2; + class embedded_baz_class { + embedded_baz_class(); + ~embedded_baz_class(); + } embed3; + ~class3(); /* destructor */ + + /* Methods */ + int method_for_class3(int a, char b); + + int inline_method(int c) { return c; } + + /* Operators */ + class3& operator^= (const class3& something); + + /* Funny declmods */ + const class3 * const method_const_ptr_ptr(const int * const argconst) const = 0; +}; + +class3::class3() +{ + /* Constructor outside the definition. */ +} + +int class3::method_for_class3(int a, char b) +{ +} + +int class3::method1_for_class3( int a, int &b) +{ + int cvariablename; + class3 fooy[]; + class3 moose = new class3; + + // Complktion testing line should find external members. + a = fooy[1].me ; + b = cv ; + + if (fooy.emb) { + simple1(c); + } + + cos(10); + abs(10); + + return 1; +} + +char class3::method2_for_class3( int a, int b) throw ( exception1 ) +{ + return 'a'; +} + +void *class3::method3_for_class3( int a, int b) throw ( exception1, exception2 ) +{ + int q = a; + return "Moose"; +} + +void *class3::method31_for_class3( int a, int b) throw ( ) +{ + int q = a; + return "Moose"; +} + +void *class3::method4_for_class3( int a, int b) reentrant +{ + class3 ct; + + ct.method5_for_class3(1,a); + + pritf(); +} + +/* + * A method on class3. + */ +void *class3::method5_for_class3( int a, int b) const +{ +} + +/* + * Namespace parsing tests + */ +namespace NS { + class class_in_namespace { + int equiv(const NS::class_in_namespace *) const; + }; +} + +int NS::class_in_namespace::equiv(const NS::class_in_namespace *cin) const +{ + return 0; +} + +// Stuff Klaus found. +// Inheritance w/out a specifying for public. +class class4 : class1 { + // Pure virtual methods. + void virtual print () const = 0; + +public: + // The whacky constructor type + class4() + try : class1(args) + { + // constructor body + } + catch () + { + + } + + +}; + +class class5 : public virtual class4 { + // Virtual inheritance +}; + +class class6 : class1 { + // Mutable + mutable int i; +}; + +/* Namespaces */ +namespace namespace1 { + void ns_method1() { } + + class n_class1 { + public: + void method11(int a) { } + }; + + /* This shouldn't parse due to missing semicolon. */ + class _n_class2 : public n_class1 { + void n_c2_method1(int a, int b) { } + }; + + // Macros in the namespace +#define NSMACRO 1 + + // Template in the namespace + template T nsti1(const Foo& foo); + template<> int nsti1(const Foo& foo); + +} + +namespace namespace2 { + + using namespace1::n_class1; + +} + +/* Initializers */ +void tinitializers1(): inita1(False), + inita2(False) +{ + inita1= 1; +} + +/* How about Extern C type things. */ +int funny_prototype(int ,int b,float c) +{ + +} + +extern "C" +int extern_c_1(int a, int b) +{ + + funny_prototype(1,2,3.4); + + printf("Moose", ); + + return 1; +} + +extern "C" { + + int extern_c_2(int a, int b) + { + return 1; + } + +} + +// Some operator stuff +class Action +{ + // Problems!! operator() and operator[] can not be parsed with semantic + // 1.4.2 but with latest c.by + virtual void operator()(int i, char *p ) = 0; + virtual String& operator[]() = 0; + virtual void operator!() = 0; + virtual void operator->() = 0; + virtual T& operator+=(); + virtual T& operator*(); + virtual T& operator*=(); +}; + +// class with namespace qualified parents +class Multiinherit : public virtual POA::Parent, + public virtual POA::Parent1, + Parent +{ +private: + int i; + +public: + Multiinherit(); + ~Multiinherit(); + + // method with a list of qualified exceptions + void* throwtest() + throw(Exception0, + Testnamespace::Exception1, + Testnamespace::Excpetion2, + Testnamespace::testnamespace1::Exception3); + +}; + +void* +Multiinherit::throwtest() + throw (Exception0, + Testnamespace::Exception1, + Testnamespace::Excpetion2, + Testnamespace::testnamespace1::Exception3) +{ + return; +} + +// Jens Rock : Nested classes or structs defined +// outside of the containing class/struct. +class container +{ + public: + struct contained; + container(); + ~container(); +}; + +struct container::contained +{ + public: + contained(); + ~contained(); +}; + +/* + * Ok, how about some template stuff. + */ +template > +const CT& max (const CT& a, const CT& b) +{ + return a < b ? b : a; +} + +// Arne Schmitz found this one +std::vector &a, &b, &c; + +class TemplateUsingClass +{ + typedef TestClassMap::iterator iterator; + typedef map TestClassMap; + + // typedefs with const and volatile + typedef const map const_TestClassMap; + typedef TestClassMap::iterator volatile volatile_iterator; + + map mapclassvarthingy; +}; + +template T ti1(const Foo& foo); +template<> int ti1(const Foo& foo); + + +// ----------------------------------- +// Now some namespace and related stuff +// ----------------------------------- + +using CORBA::LEX::get_token; +using Namespace1; + +using namespace POA::std; +using namespace Test; + + + +namespace Parser +{ + namespace + { + using Lexer::get_test; + string str = ""; + } + + namespace XXX + { + + class Foobar : public virtual POA::Parent, + public virtual POA::Parent1, + private POA::list, + private map + { + int i; + list >::const_iterator l; + public: + + Foobar(); + ~Foobar(); + }; + } + + + void test_function(int i); + +}; + +// unnamed namespaces - even nested +namespace +{ + namespace + { + using Lexer::get_test; + string str = ""; + class FooClass + { + FooClass(); + }; + } + + // some builtin types + long long ll = 0; + long double d = 0.0; + unsigned test; + unsigned long int **uli = 0; + signed si = 0; + signed short ss = 0; + short int i = 0; + long int li = 0; + + // expressions with namespace/class-qualifyiers + ORB_var cGlobalOrb = ORB::_nil(); + ORB_var1 cGlobalOrb1 = ORB::_test; + + class Testclass + { + #define TEST 0 + ini i; + + public: + + Testclass(); + ~Testclass(); + }; + + static void test_function(unsigned int i); + +}; + + +// outside method implementations which should be grouped to type Test +XXX& +Test::waiting() +{ + return; +} + +void +Test::print() +{ + return; +} + +// outside method implementations with namespaces which should be grouped to +// their complete (incl. namespace) types +void* +Parser::XXX::Foobar::wait(int i, const char const * const * p) +{ + return; +} + +void* +Namespace1::Test::wait1(int i) +{ + return; +} + +int +Namespace1::Test::waiting(int i) +{ + return; +} + +// a class with some outside implementations which should all be grouped to +// this class declaration +class ClassWithExternals +{ +private: + int i; + +public: + ClassWithExternals(); + ~ClassWithExternals(); + void non_nil(); +}; + + +// Foobar is not displayed; seems that semantic tries to add this to the class +// Foobar but can not find/display it, because contained in the namespace above. +void +Foobar::non_nil() +{ + return; +} + +// are correctly grouped to the ClassWithExternals class +void +ClassWithExternals::non_nil() +{ + String s = "lödfjg dlfgkdlfkgjdl"; + return; +} + +ClassWithExternals::ClassWithExternals() +{ + return; +} + +void +ClassWithExternals::~ClassWithExternals() +{ + return; +} + + +// ------------------------------- +// Now some macro and define stuff +// ------------------------------- + +#define TEST 0 +#define TEST1 "String" + +// The first backslash makes this macro unmatched syntax with semantic 1.4.2! +// With flexing \+newline as nothing all is working fine! +#define MZK_ENTER(METHOD) \ +{ \ + CzkMethodLog lMethodLog(METHOD,"Framework");\ +} + +#define ZK_ASSERTM(METHOD,ASSERTION,MESSAGE) \ + { if(!(ASSERTION))\ + {\ + std::ostringstream lMesgStream; \ + lMesgStream << "Assertion failed: " \ + << MESSAGE; \ + CzkLogManager::doLog(CzkLogManager::FATAL,"",METHOD, \ + "Assert",lMesgStream); \ + assert(ASSERTION);\ + }\ + } + +// Test if not newline-backslashes are handled correctly +string s = "My \"quoted\" string"; + +// parsed fine as macro +#define FOO (arg) method(arg, "foo"); + +// With semantic 1.4.2 this parsed as macro BAR *and* function method. +// With latest c.bnf at least one-liner macros can be parsed correctly. +#define BAR (arg) CzkMessageLog method(arg, "bar"); + +// some const and volatile stuff +char * p1 = "Hello"; // 1. variable Pointer, variable Data +const char * p2 = "Hello"; // 2. variable pointer, constant data +char * const p3 = "Hello"; // 3. constant pointer, variable data +const char * const p4 = "Hello"; // 4. constant pointer, constant data + +// Case 2 and 4 can exchange first "const" and "char" +char const * p21 = "Hello"; // variable pointer, constant data +char const * const p41 = "Hello"; // constant pointer, constant data + +char volatile a = 0; // a volatile char +void foo(bar const &arg); // a reference to a const bar +int foobar(bar const * const p); // a const pointer to a const bar +int foobar(bar const volatile * const p); // a const pointer to a const bar +int foobar3(char* p); // a const pointer to a const bar + +// Should not be parsed because this is invalid code +int const & const r3 = i; + +boolean i = 0; +boolean & r1 = i; +boolean const & r2 = i; + +// const * sequences can be very long in C++ ;-) +char const * const * const * const * ppp; + +// complex function declarationen with named pointer-arguments +const char** foobar1(volatile char const * const **p); +const char** foobar11(volatile Test::Namespace::Char const * const **p); + +// complex function declarationen with unnamed pointer-arguments +const char* foobar2(const char***); +const char* foobar21(const Test::Namespace::Char***); + +// string literal parsing even with wchar_t +char const *p = "string1"; +char const *q = "string1" "str\"ing2" "string3"; +wchar_t testc = L'a'; + +wchar_t const *wp = L"string with a \" in it"; +wchar_t const *wq = L"string \n\t\"test" L"string2"; +wchar_t const *wr = L"string L"; diff --git a/tests/cpp/issue82.cpp b/tests/cpp/issue82.cpp new file mode 100644 index 0000000..8267704 --- /dev/null +++ b/tests/cpp/issue82.cpp @@ -0,0 +1,8 @@ +#include + +namespace phantom { namespace io_stream { namespace proto_http { +namespace handler_bts { + +} // namespace handler_bts + +}}} // namespace phantom::io_stream::proto_http diff --git a/tests/cpp/jstracer.cpp b/tests/cpp/jstracer.cpp new file mode 100644 index 0000000..8ff4923 --- /dev/null +++ b/tests/cpp/jstracer.cpp @@ -0,0 +1,14392 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: set ts=4 sw=4 et tw=99: + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla SpiderMonkey JavaScript 1.9 code, released + * May 28, 2008. + * + * The Initial Developer of the Original Code is + * Brendan Eich + * + * Contributor(s): + * Andreas Gal + * Mike Shaver + * David Anderson + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#include "jsstdint.h" +#include "jsbit.h" // low-level (NSPR-based) headers next +#include "jsprf.h" +#include // standard headers next + +#if defined(_MSC_VER) || defined(__MINGW32__) +#include +#ifdef _MSC_VER +#define alloca _alloca +#endif +#endif +#ifdef SOLARIS +#include +#endif +#include + +#include "nanojit/nanojit.h" +#include "jsapi.h" // higher-level library and API headers +#include "jsarray.h" +#include "jsbool.h" +#include "jscntxt.h" +#include "jsdate.h" +#include "jsdbgapi.h" +#include "jsemit.h" +#include "jsfun.h" +#include "jsinterp.h" +#include "jsiter.h" +#include "jsmath.h" +#include "jsobj.h" +#include "jsopcode.h" +#include "jsregexp.h" +#include "jsscope.h" +#include "jsscript.h" +#include "jsstaticcheck.h" +#include "jstracer.h" +#include "jsxml.h" + +#include "jsatominlines.h" +#include "jsscriptinlines.h" + +#include "jsautooplen.h" // generated headers last +#include "imacros.c.out" + +using namespace nanojit; + +#if JS_HAS_XML_SUPPORT +#define ABORT_IF_XML(v) \ + JS_BEGIN_MACRO \ + if (!JSVAL_IS_PRIMITIVE(v) && OBJECT_IS_XML(BOGUS_CX, JSVAL_TO_OBJECT(v)))\ + ABORT_TRACE("xml detected"); \ + JS_END_MACRO +#else +#define ABORT_IF_XML(v) ((void) 0) +#endif + +/* + * Never use JSVAL_IS_BOOLEAN because it restricts the value (true, false) and + * the type. What you want to use is JSVAL_IS_SPECIAL(x) and then handle the + * undefined case properly (bug 457363). + */ +#undef JSVAL_IS_BOOLEAN +#define JSVAL_IS_BOOLEAN(x) JS_STATIC_ASSERT(0) + +JS_STATIC_ASSERT(sizeof(JSTraceType) == 1); + +/* Map to translate a type tag into a printable representation. */ +static const char typeChar[] = "OIDXSNBF"; +static const char tagChar[] = "OIDISIBI"; + +/* Blacklist parameters. */ + +/* + * Number of iterations of a loop where we start tracing. That is, we don't + * start tracing until the beginning of the HOTLOOP-th iteration. + */ +#define HOTLOOP 2 + +/* Attempt recording this many times before blacklisting permanently. */ +#define BL_ATTEMPTS 2 + +/* Skip this many hits before attempting recording again, after an aborted attempt. */ +#define BL_BACKOFF 32 + +/* Number of times we wait to exit on a side exit before we try to extend the tree. */ +#define HOTEXIT 1 + +/* Number of times we try to extend the tree along a side exit. */ +#define MAXEXIT 3 + +/* Maximum number of peer trees allowed. */ +#define MAXPEERS 9 + +/* Max call depths for inlining. */ +#define MAX_CALLDEPTH 10 + +/* Max native stack size. */ +#define MAX_NATIVE_STACK_SLOTS 1024 + +/* Max call stack size. */ +#define MAX_CALL_STACK_ENTRIES 64 + +/* Max global object size. */ +#define MAX_GLOBAL_SLOTS 4096 + +/* Max memory needed to rebuild the interpreter stack when falling off trace. */ +#define MAX_INTERP_STACK_BYTES \ + (MAX_NATIVE_STACK_SLOTS * sizeof(jsval) + \ + MAX_CALL_STACK_ENTRIES * sizeof(JSInlineFrame) + \ + sizeof(JSInlineFrame)) /* possibly slow native frame at top of stack */ + +/* Max number of branches per tree. */ +#define MAX_BRANCHES 32 + +#define CHECK_STATUS(expr) \ + JS_BEGIN_MACRO \ + JSRecordingStatus _status = (expr); \ + if (_status != JSRS_CONTINUE) \ + return _status; \ + JS_END_MACRO + +#ifdef JS_JIT_SPEW +#define ABORT_TRACE_RV(msg, value) \ + JS_BEGIN_MACRO \ + debug_only_printf(LC_TMAbort, "abort: %d: %s\n", __LINE__, (msg)); \ + return (value); \ + JS_END_MACRO +#else +#define ABORT_TRACE_RV(msg, value) return (value) +#endif + +#define ABORT_TRACE(msg) ABORT_TRACE_RV(msg, JSRS_STOP) +#define ABORT_TRACE_ERROR(msg) ABORT_TRACE_RV(msg, JSRS_ERROR) + +#ifdef JS_JIT_SPEW +struct __jitstats { +#define JITSTAT(x) uint64 x; +#include "jitstats.tbl" +#undef JITSTAT +} jitstats = { 0LL, }; + +JS_STATIC_ASSERT(sizeof(jitstats) % sizeof(uint64) == 0); + +enum jitstat_ids { +#define JITSTAT(x) STAT ## x ## ID, +#include "jitstats.tbl" +#undef JITSTAT + STAT_IDS_TOTAL +}; + +static JSPropertySpec jitstats_props[] = { +#define JITSTAT(x) { #x, STAT ## x ## ID, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT }, +#include "jitstats.tbl" +#undef JITSTAT + { 0 } +}; + +static JSBool +jitstats_getProperty(JSContext *cx, JSObject *obj, jsid id, jsval *vp) +{ + int index = -1; + + if (JSVAL_IS_STRING(id)) { + JSString* str = JSVAL_TO_STRING(id); + if (strcmp(JS_GetStringBytes(str), "HOTLOOP") == 0) { + *vp = INT_TO_JSVAL(HOTLOOP); + return JS_TRUE; + } + } + + if (JSVAL_IS_INT(id)) + index = JSVAL_TO_INT(id); + + uint64 result = 0; + switch (index) { +#define JITSTAT(x) case STAT ## x ## ID: result = jitstats.x; break; +#include "jitstats.tbl" +#undef JITSTAT + default: + *vp = JSVAL_VOID; + return JS_TRUE; + } + + if (result < JSVAL_INT_MAX) { + *vp = INT_TO_JSVAL(result); + return JS_TRUE; + } + char retstr[64]; + JS_snprintf(retstr, sizeof retstr, "%llu", result); + *vp = STRING_TO_JSVAL(JS_NewStringCopyZ(cx, retstr)); + return JS_TRUE; +} + +JSClass jitstats_class = { + "jitstats", + 0, + JS_PropertyStub, JS_PropertyStub, + jitstats_getProperty, JS_PropertyStub, + JS_EnumerateStub, JS_ResolveStub, + JS_ConvertStub, NULL, + JSCLASS_NO_OPTIONAL_MEMBERS +}; + +void +js_InitJITStatsClass(JSContext *cx, JSObject *glob) +{ + JS_InitClass(cx, glob, NULL, &jitstats_class, NULL, 0, jitstats_props, NULL, NULL, NULL); +} + +#define AUDIT(x) (jitstats.x++) +#else +#define AUDIT(x) ((void)0) +#endif /* JS_JIT_SPEW */ + +/* + * INS_CONSTPTR can be used to embed arbitrary pointers into the native code. It should not + * be used directly to embed GC thing pointers. Instead, use the INS_CONSTOBJ/FUN/STR/SPROP + * variants which ensure that the embedded pointer will be kept alive across GCs. + */ + +#define INS_CONST(c) addName(lir->insImm(c), #c) +#define INS_CONSTPTR(p) addName(lir->insImmPtr(p), #p) +#define INS_CONSTWORD(v) addName(lir->insImmPtr((void *) (v)), #v) +#define INS_CONSTVAL(v) addName(insImmVal(v), #v) +#define INS_CONSTOBJ(obj) addName(insImmObj(obj), #obj) +#define INS_CONSTFUN(fun) addName(insImmFun(fun), #fun) +#define INS_CONSTSTR(str) addName(insImmStr(str), #str) +#define INS_CONSTSPROP(sprop) addName(insImmSprop(sprop), #sprop) +#define INS_ATOM(atom) INS_CONSTSTR(ATOM_TO_STRING(atom)) +#define INS_NULL() INS_CONSTPTR(NULL) +#define INS_VOID() INS_CONST(JSVAL_TO_SPECIAL(JSVAL_VOID)) + +static avmplus::AvmCore s_core = avmplus::AvmCore(); +static avmplus::AvmCore* core = &s_core; + +/* Allocator SPI implementation. */ + +void* +nanojit::Allocator::allocChunk(size_t nbytes) +{ + VMAllocator *vma = (VMAllocator*)this; + JS_ASSERT(!vma->outOfMemory()); + void *p = malloc(nbytes); + if (!p) { + JS_ASSERT(nbytes < sizeof(vma->mReserve)); + vma->mOutOfMemory = true; + p = (void*) &vma->mReserve[0]; + } + vma->mSize += nbytes; + return p; +} + +void +nanojit::Allocator::freeChunk(void *p) { + VMAllocator *vma = (VMAllocator*)this; + if (p != &vma->mReserve[0]) + free(p); +} + +void +nanojit::Allocator::postReset() { + VMAllocator *vma = (VMAllocator*)this; + vma->mOutOfMemory = false; + vma->mSize = 0; +} + + +#ifdef JS_JIT_SPEW +static void +DumpPeerStability(JSTraceMonitor* tm, const void* ip, JSObject* globalObj, uint32 globalShape, uint32 argc); +#endif + +/* + * We really need a better way to configure the JIT. Shaver, where is + * my fancy JIT object? + * + * NB: this is raced on, if jstracer.cpp should ever be running MT. + * I think it's harmless tho. + */ +static bool did_we_check_processor_features = false; + +/* ------ Debug logging control ------ */ + +/* + * All the logging control stuff lives in here. It is shared between + * all threads, but I think that's OK. + */ +LogControl js_LogController; + +#ifdef JS_JIT_SPEW + +/* + * NB: this is raced on too, if jstracer.cpp should ever be running MT. + * Also harmless. + */ +static bool did_we_set_up_debug_logging = false; + +static void +InitJITLogController() +{ + char *tm, *tmf; + uint32_t bits; + + js_LogController.lcbits = 0; + + tm = getenv("TRACEMONKEY"); + if (tm) { + fflush(NULL); + printf( + "The environment variable $TRACEMONKEY has been replaced by $TMFLAGS.\n" + "Try 'TMFLAGS=help js -j' for a list of options.\n" + ); + exit(0); + } + + tmf = getenv("TMFLAGS"); + if (!tmf) return; + + /* Using strstr() is really a cheap hack as far as flag decoding goes. */ + if (strstr(tmf, "help")) { + fflush(NULL); + printf( + "usage: TMFLAGS=option,option,option,... where options can be:\n" + "\n" + " help show this message\n" + " ------ options for jstracer & jsregexp ------\n" + " minimal ultra-minimalist output; try this first\n" + " full everything except 'treevis' and 'nocodeaddrs'\n" + " tracer tracer lifetime (FIXME:better description)\n" + " recorder trace recording stuff (FIXME:better description)\n" + " abort show trace recording aborts\n" + " stats show trace recording stats\n" + " regexp show compilation & entry for regexps\n" + " treevis spew that tracevis/tree.py can parse\n" + " ------ options for Nanojit ------\n" + " fragprofile count entries and exits for each fragment\n" + " activation show activation info\n" + " liveness show LIR liveness at start of rdr pipeline\n" + " readlir show LIR as it enters the reader pipeline\n" + " aftersf show LIR after StackFilter\n" + " regalloc show regalloc details\n" + " assembly show final aggregated assembly code\n" + " nocodeaddrs don't show code addresses in assembly listings\n" + "\n" + ); + exit(0); + /*NOTREACHED*/ + } + + bits = 0; + + /* flags for jstracer.cpp */ + if (strstr(tmf, "minimal") || strstr(tmf, "full")) bits |= LC_TMMinimal; + if (strstr(tmf, "tracer") || strstr(tmf, "full")) bits |= LC_TMTracer; + if (strstr(tmf, "recorder") || strstr(tmf, "full")) bits |= LC_TMRecorder; + if (strstr(tmf, "abort") || strstr(tmf, "full")) bits |= LC_TMAbort; + if (strstr(tmf, "stats") || strstr(tmf, "full")) bits |= LC_TMStats; + if (strstr(tmf, "regexp") || strstr(tmf, "full")) bits |= LC_TMRegexp; + if (strstr(tmf, "treevis")) bits |= LC_TMTreeVis; + + /* flags for nanojit */ + if (strstr(tmf, "fragprofile")) bits |= LC_FragProfile; + if (strstr(tmf, "liveness") || strstr(tmf, "full")) bits |= LC_Liveness; + if (strstr(tmf, "activation") || strstr(tmf, "full")) bits |= LC_Activation; + if (strstr(tmf, "readlir") || strstr(tmf, "full")) bits |= LC_ReadLIR; + if (strstr(tmf, "aftersf") || strstr(tmf, "full")) bits |= LC_AfterSF; + if (strstr(tmf, "regalloc") || strstr(tmf, "full")) bits |= LC_RegAlloc; + if (strstr(tmf, "assembly") || strstr(tmf, "full")) bits |= LC_Assembly; + if (strstr(tmf, "nocodeaddrs")) bits |= LC_NoCodeAddrs; + + js_LogController.lcbits = bits; + return; + +} +#endif + +/* ------------------ Frag-level profiling support ------------------ */ + +#ifdef JS_JIT_SPEW + +/* + * All the allocations done by this profile data-collection and + * display machinery, are done in JSTraceMonitor::profAlloc. That is + * emptied out at the end of js_FinishJIT. It has a lifetime from + * js_InitJIT to js_FinishJIT, which exactly matches the span + * js_FragProfiling_init to js_FragProfiling_showResults. + */ +template +static +Seq* reverseInPlace(Seq* seq) +{ + Seq* prev = NULL; + Seq* curr = seq; + while (curr) { + Seq* next = curr->tail; + curr->tail = prev; + prev = curr; + curr = next; + } + return prev; +} + +// The number of top blocks to show in the profile +#define N_TOP_BLOCKS 50 + +// Contains profile info for a single guard +struct GuardPI { + uint32_t guardID; // identifying number + uint32_t count; // count. +}; + +struct FragPI { + uint32_t count; // entry count for this Fragment + uint32_t nStaticExits; // statically: the number of exits + size_t nCodeBytes; // statically: the number of insn bytes in the main fragment + size_t nExitBytes; // statically: the number of insn bytes in the exit paths + Seq* guards; // guards, each with its own count + uint32_t largestGuardID; // that exists in .guards +}; + +/* A mapping of Fragment.profFragID to FragPI */ +typedef HashMap FragStatsMap; + +void +js_FragProfiling_FragFinalizer(Fragment* f, JSTraceMonitor* tm) +{ + // Recover profiling data from 'f', which is logically at the end + // of its useful lifetime. + if (!(js_LogController.lcbits & LC_FragProfile)) + return; + + NanoAssert(f); + // Valid profFragIDs start at 1 + NanoAssert(f->profFragID >= 1); + // Should be called exactly once per Fragment. This will assert if + // you issue the same FragID to more than one Fragment. + NanoAssert(!tm->profTab->containsKey(f->profFragID)); + + FragPI pi = { f->profCount, + f->nStaticExits, + f->nCodeBytes, + f->nExitBytes, + NULL, 0 }; + + // Begin sanity check on the guards + SeqBuilder guardsBuilder(*tm->profAlloc); + GuardRecord* gr; + uint32_t nGs = 0; + uint32_t sumOfDynExits = 0; + for (gr = f->guardsForFrag; gr; gr = gr->nextInFrag) { + nGs++; + // Also copy the data into our auxiliary structure. + // f->guardsForFrag is in reverse order, and so this + // copy preserves that ordering (->add adds at end). + // Valid profGuardIDs start at 1. + NanoAssert(gr->profGuardID > 0); + sumOfDynExits += gr->profCount; + GuardPI gpi = { gr->profGuardID, gr->profCount }; + guardsBuilder.add(gpi); + if (gr->profGuardID > pi.largestGuardID) + pi.largestGuardID = gr->profGuardID; + } + pi.guards = guardsBuilder.get(); + // And put the guard list in forwards order + pi.guards = reverseInPlace(pi.guards); + + // Why is this so? Because nGs is the number of guards + // at the time the LIR was generated, whereas f->nStaticExits + // is the number of them observed by the time it makes it + // through to the assembler. It can be the case that LIR + // optimisation removes redundant guards; hence we expect + // nGs to always be the same or higher. + NanoAssert(nGs >= f->nStaticExits); + + // Also we can assert that the sum of the exit counts + // can't exceed the entry count. It'd be nice to assert that + // they are exactly equal, but we can't because we don't know + // how many times we got to the end of the trace. + NanoAssert(f->profCount >= sumOfDynExits); + + // End sanity check on guards + + tm->profTab->put(f->profFragID, pi); +} + +static void +js_FragProfiling_showResults(JSTraceMonitor* tm) +{ + uint32_t topFragID[N_TOP_BLOCKS]; + FragPI topPI[N_TOP_BLOCKS]; + uint64_t totCount = 0, cumulCount; + uint32_t totSE = 0; + size_t totCodeB = 0, totExitB = 0; + memset(topFragID, 0, sizeof(topFragID)); + memset(topPI, 0, sizeof(topPI)); + FragStatsMap::Iter iter(*tm->profTab); + while (iter.next()) { + uint32_t fragID = iter.key(); + FragPI pi = iter.value(); + uint32_t count = pi.count; + totCount += (uint64_t)count; + /* Find the rank for this entry, in tops */ + int r = N_TOP_BLOCKS-1; + while (true) { + if (r == -1) + break; + if (topFragID[r] == 0) { + r--; + continue; + } + if (count > topPI[r].count) { + r--; + continue; + } + break; + } + r++; + AvmAssert(r >= 0 && r <= N_TOP_BLOCKS); + /* This entry should be placed at topPI[r], and entries + at higher numbered slots moved up one. */ + if (r < N_TOP_BLOCKS) { + for (int s = N_TOP_BLOCKS-1; s > r; s--) { + topFragID[s] = topFragID[s-1]; + topPI[s] = topPI[s-1]; + } + topFragID[r] = fragID; + topPI[r] = pi; + } + } + + js_LogController.printf( + "\n----------------- Per-fragment execution counts ------------------\n"); + js_LogController.printf( + "\nTotal count = %llu\n\n", (unsigned long long int)totCount); + + js_LogController.printf( + " Entry counts Entry counts ----- Static -----\n"); + js_LogController.printf( + " ------Self------ ----Cumulative--- Exits Cbytes Xbytes FragID\n"); + js_LogController.printf("\n"); + + if (totCount == 0) + totCount = 1; /* avoid division by zero */ + cumulCount = 0; + int r; + for (r = 0; r < N_TOP_BLOCKS; r++) { + if (topFragID[r] == 0) + break; + cumulCount += (uint64_t)topPI[r].count; + js_LogController.printf("%3d: %5.2f%% %9u %6.2f%% %9llu" + " %3d %5u %5u %06u\n", + r, + (double)topPI[r].count * 100.0 / (double)totCount, + topPI[r].count, + (double)cumulCount * 100.0 / (double)totCount, + (unsigned long long int)cumulCount, + topPI[r].nStaticExits, + (unsigned int)topPI[r].nCodeBytes, + (unsigned int)topPI[r].nExitBytes, + topFragID[r]); + totSE += (uint32_t)topPI[r].nStaticExits; + totCodeB += topPI[r].nCodeBytes; + totExitB += topPI[r].nExitBytes; + } + js_LogController.printf("\nTotal displayed code bytes = %u, " + "exit bytes = %u\n" + "Total displayed static exits = %d\n\n", + (unsigned int)totCodeB, (unsigned int)totExitB, totSE); + + js_LogController.printf("Analysis by exit counts\n\n"); + + for (r = 0; r < N_TOP_BLOCKS; r++) { + if (topFragID[r] == 0) + break; + js_LogController.printf("FragID=%06u, total count %u:\n", topFragID[r], + topPI[r].count); + uint32_t madeItToEnd = topPI[r].count; + uint32_t totThisFrag = topPI[r].count; + if (totThisFrag == 0) + totThisFrag = 1; + GuardPI gpi; + // visit the guards, in forward order + for (Seq* guards = topPI[r].guards; guards; guards = guards->tail) { + gpi = (*guards).head; + if (gpi.count == 0) + continue; + madeItToEnd -= gpi.count; + js_LogController.printf(" GuardID=%03u %7u (%5.2f%%)\n", + gpi.guardID, gpi.count, + 100.0 * (double)gpi.count / (double)totThisFrag); + } + js_LogController.printf(" Looped (%03u) %7u (%5.2f%%)\n", + topPI[r].largestGuardID+1, + madeItToEnd, + 100.0 * (double)madeItToEnd / (double)totThisFrag); + NanoAssert(madeItToEnd <= topPI[r].count); // else unsigned underflow + js_LogController.printf("\n"); + } + + tm->profTab = NULL; +} + +#endif + +/* ----------------------------------------------------------------- */ + +#ifdef DEBUG +static const char* +getExitName(ExitType type) +{ + static const char* exitNames[] = + { + #define MAKE_EXIT_STRING(x) #x, + JS_TM_EXITCODES(MAKE_EXIT_STRING) + #undef MAKE_EXIT_STRING + NULL + }; + + JS_ASSERT(type < TOTAL_EXIT_TYPES); + + return exitNames[type]; +} + +static JSBool FASTCALL +PrintOnTrace(char* format, uint32 argc, double *argv) +{ + union { + struct { + uint32 lo; + uint32 hi; + } i; + double d; + char *cstr; + JSObject *o; + JSString *s; + } u; + +#define GET_ARG() JS_BEGIN_MACRO \ + if (argi >= argc) { \ + fprintf(out, "[too few args for format]"); \ + break; \ +} \ + u.d = argv[argi++]; \ + JS_END_MACRO + + FILE *out = stderr; + + uint32 argi = 0; + for (char *p = format; *p; ++p) { + if (*p != '%') { + putc(*p, out); + continue; + } + char ch = *++p; + if (!ch) { + fprintf(out, "[trailing %%]"); + continue; + } + + switch (ch) { + case 'a': + GET_ARG(); + fprintf(out, "[%u:%u 0x%x:0x%x %f]", u.i.lo, u.i.hi, u.i.lo, u.i.hi, u.d); + break; + case 'd': + GET_ARG(); + fprintf(out, "%d", u.i.lo); + break; + case 'u': + GET_ARG(); + fprintf(out, "%u", u.i.lo); + break; + case 'x': + GET_ARG(); + fprintf(out, "%x", u.i.lo); + break; + case 'f': + GET_ARG(); + fprintf(out, "%f", u.d); + break; + case 'o': + GET_ARG(); + js_DumpObject(u.o); + break; + case 's': + GET_ARG(); + { + size_t length = u.s->length(); + // protect against massive spew if u.s is a bad pointer. + if (length > 1 << 16) + length = 1 << 16; + jschar *chars = u.s->chars(); + for (unsigned i = 0; i < length; ++i) { + jschar co = chars[i]; + if (co < 128) + putc(co, out); + else if (co < 256) + fprintf(out, "\\u%02x", co); + else + fprintf(out, "\\u%04x", co); + } + } + break; + case 'S': + GET_ARG(); + fprintf(out, "%s", u.cstr); + break; + default: + fprintf(out, "[invalid %%%c]", *p); + } + } + +#undef GET_ARG + + return JS_TRUE; +} + +JS_DEFINE_CALLINFO_3(extern, BOOL, PrintOnTrace, CHARPTR, UINT32, DOUBLEPTR, 0, 0) + +// This version is not intended to be called directly: usually it is easier to +// use one of the other overloads. +void +TraceRecorder::tprint(const char *format, int count, nanojit::LIns *insa[]) +{ + size_t size = strlen(format) + 1; + char *data = (char*) lir->insSkip(size)->payload(); + memcpy(data, format, size); + + double *args = (double*) lir->insSkip(count * sizeof(double))->payload(); + for (int i = 0; i < count; ++i) { + JS_ASSERT(insa[i]); + lir->insStorei(insa[i], INS_CONSTPTR(args), sizeof(double) * i); + } + + LIns* args_ins[] = { INS_CONSTPTR(args), INS_CONST(count), INS_CONSTPTR(data) }; + LIns* call_ins = lir->insCall(&PrintOnTrace_ci, args_ins); + guard(false, lir->ins_eq0(call_ins), MISMATCH_EXIT); +} + +// Generate a 'printf'-type call from trace for debugging. +void +TraceRecorder::tprint(const char *format) +{ + LIns* insa[] = { NULL }; + tprint(format, 0, insa); +} + +void +TraceRecorder::tprint(const char *format, LIns *ins) +{ + LIns* insa[] = { ins }; + tprint(format, 1, insa); +} + +void +TraceRecorder::tprint(const char *format, LIns *ins1, LIns *ins2) +{ + LIns* insa[] = { ins1, ins2 }; + tprint(format, 2, insa); +} + +void +TraceRecorder::tprint(const char *format, LIns *ins1, LIns *ins2, LIns *ins3) +{ + LIns* insa[] = { ins1, ins2, ins3 }; + tprint(format, 3, insa); +} + +void +TraceRecorder::tprint(const char *format, LIns *ins1, LIns *ins2, LIns *ins3, LIns *ins4) +{ + LIns* insa[] = { ins1, ins2, ins3, ins4 }; + tprint(format, 4, insa); +} + +void +TraceRecorder::tprint(const char *format, LIns *ins1, LIns *ins2, LIns *ins3, LIns *ins4, + LIns *ins5) +{ + LIns* insa[] = { ins1, ins2, ins3, ins4, ins5 }; + tprint(format, 5, insa); +} + +void +TraceRecorder::tprint(const char *format, LIns *ins1, LIns *ins2, LIns *ins3, LIns *ins4, + LIns *ins5, LIns *ins6) +{ + LIns* insa[] = { ins1, ins2, ins3, ins4, ins5, ins6 }; + tprint(format, 6, insa); +} +#endif + +/* + * The entire VM shares one oracle. Collisions and concurrent updates are + * tolerated and worst case cause performance regressions. + */ +static Oracle oracle; + +/* + * This confusing and mysterious expression is used for the Tracker. The + * tracker's responsibility is to map opaque, 4-byte aligned addresses to LIns + * pointers. To do this efficiently, we observe that the addresses of jsvals + * living in the interpreter tend to be aggregated close to each other - + * usually on the same page (where a tracker page doesn't have to be the same + * size as the OS page size, but it's typically similar). + * + * For every address, we split it into two values: upper bits which represent + * the "base", and lower bits which represent an offset against the base. We + * create a list of: + * struct TrackerPage { + * void* base; + * LIns* map; + * }; + * The mapping then becomes: + * page = page such that Base(address) == page->base, + * page->map[Index(address)] + * + * The size of the map is allocated as N * sizeof(LIns*), where N is + * (TRACKER_PAGE_SIZE >> 2). Since the lower two bits are 0, they are always + * discounted. + * + * TRACKER_PAGE_MASK is the "reverse" expression, with a |- 1| to get a mask + * which separates an address into the Base and Index bits. It is necessary to + * do all this work rather than use TRACKER_PAGE_SIZE - 1, because on 64-bit + * platforms the pointer width is twice as large, and only half as many + * indexes can fit into TrackerPage::map. So the "Base" grows by one bit, and + * the "Index" shrinks by one bit. + */ +#define TRACKER_PAGE_MASK (((TRACKER_PAGE_SIZE / sizeof(void*)) << 2) - 1) + +#define TRACKER_PAGE_SIZE 4096 + +Tracker::Tracker() +{ + pagelist = 0; +} + +Tracker::~Tracker() +{ + clear(); +} + +jsuword +Tracker::getTrackerPageBase(const void* v) const +{ + return jsuword(v) & ~jsuword(TRACKER_PAGE_MASK); +} + +struct Tracker::TrackerPage* +Tracker::findTrackerPage(const void* v) const +{ + jsuword base = getTrackerPageBase(v); + struct Tracker::TrackerPage* p = pagelist; + while (p) { + if (p->base == base) { + return p; + } + p = p->next; + } + return 0; +} + +struct Tracker::TrackerPage* +Tracker::addTrackerPage(const void* v) { + jsuword base = getTrackerPageBase(v); + struct Tracker::TrackerPage* p = (struct Tracker::TrackerPage*) + calloc(1, sizeof(*p) - sizeof(p->map) + (TRACKER_PAGE_SIZE >> 2) * sizeof(LIns*)); + p->base = base; + p->next = pagelist; + pagelist = p; + return p; +} + +void +Tracker::clear() +{ + while (pagelist) { + TrackerPage* p = pagelist; + pagelist = pagelist->next; + free(p); + } +} + +bool +Tracker::has(const void *v) const +{ + return get(v) != NULL; +} + +LIns* +Tracker::get(const void* v) const +{ + struct Tracker::TrackerPage* p = findTrackerPage(v); + if (!p) + return NULL; + return p->map[(jsuword(v) & TRACKER_PAGE_MASK) >> 2]; +} + +void +Tracker::set(const void* v, LIns* i) +{ + struct Tracker::TrackerPage* p = findTrackerPage(v); + if (!p) + p = addTrackerPage(v); + p->map[(jsuword(v) & TRACKER_PAGE_MASK) >> 2] = i; +} + +static inline jsuint +argSlots(JSStackFrame* fp) +{ + return JS_MAX(fp->argc, fp->fun->nargs); +} + +static inline bool +isNumber(jsval v) +{ + return JSVAL_IS_INT(v) || JSVAL_IS_DOUBLE(v); +} + +static inline jsdouble +asNumber(jsval v) +{ + JS_ASSERT(isNumber(v)); + if (JSVAL_IS_DOUBLE(v)) + return *JSVAL_TO_DOUBLE(v); + return (jsdouble)JSVAL_TO_INT(v); +} + +static inline bool +isInt32(jsval v) +{ + if (!isNumber(v)) + return false; + jsdouble d = asNumber(v); + jsint i; + return JSDOUBLE_IS_INT(d, i); +} + +static inline jsint +asInt32(jsval v) +{ + JS_ASSERT(isNumber(v)); + if (JSVAL_IS_INT(v)) + return JSVAL_TO_INT(v); +#ifdef DEBUG + jsint i; + JS_ASSERT(JSDOUBLE_IS_INT(*JSVAL_TO_DOUBLE(v), i)); +#endif + return jsint(*JSVAL_TO_DOUBLE(v)); +} + +/* Return TT_DOUBLE for all numbers (int and double) and the tag otherwise. */ +static inline JSTraceType +GetPromotedType(jsval v) +{ + if (JSVAL_IS_INT(v)) + return TT_DOUBLE; + if (JSVAL_IS_OBJECT(v)) { + if (JSVAL_IS_NULL(v)) + return TT_NULL; + if (HAS_FUNCTION_CLASS(JSVAL_TO_OBJECT(v))) + return TT_FUNCTION; + return TT_OBJECT; + } + uint8_t tag = JSVAL_TAG(v); + JS_ASSERT(tag == JSVAL_DOUBLE || tag == JSVAL_STRING || tag == JSVAL_SPECIAL); + JS_STATIC_ASSERT(static_cast(TT_DOUBLE) == JSVAL_DOUBLE); + JS_STATIC_ASSERT(static_cast(TT_STRING) == JSVAL_STRING); + JS_STATIC_ASSERT(static_cast(TT_PSEUDOBOOLEAN) == JSVAL_SPECIAL); + return JSTraceType(tag); +} + +/* Return TT_INT32 for all whole numbers that fit into signed 32-bit and the tag otherwise. */ +static inline JSTraceType +getCoercedType(jsval v) +{ + if (isInt32(v)) + return TT_INT32; + if (JSVAL_IS_OBJECT(v)) { + if (JSVAL_IS_NULL(v)) + return TT_NULL; + if (HAS_FUNCTION_CLASS(JSVAL_TO_OBJECT(v))) + return TT_FUNCTION; + return TT_OBJECT; + } + uint8_t tag = JSVAL_TAG(v); + JS_ASSERT(tag == JSVAL_DOUBLE || tag == JSVAL_STRING || tag == JSVAL_SPECIAL); + JS_STATIC_ASSERT(static_cast(TT_DOUBLE) == JSVAL_DOUBLE); + JS_STATIC_ASSERT(static_cast(TT_STRING) == JSVAL_STRING); + JS_STATIC_ASSERT(static_cast(TT_PSEUDOBOOLEAN) == JSVAL_SPECIAL); + return JSTraceType(tag); +} + +/* Constant seed and accumulate step borrowed from the DJB hash. */ + +const uintptr_t ORACLE_MASK = ORACLE_SIZE - 1; +JS_STATIC_ASSERT((ORACLE_MASK & ORACLE_SIZE) == 0); + +const uintptr_t FRAGMENT_TABLE_MASK = FRAGMENT_TABLE_SIZE - 1; +JS_STATIC_ASSERT((FRAGMENT_TABLE_MASK & FRAGMENT_TABLE_SIZE) == 0); + +const uintptr_t HASH_SEED = 5381; + +static inline void +HashAccum(uintptr_t& h, uintptr_t i, uintptr_t mask) +{ + h = ((h << 5) + h + (mask & i)) & mask; +} + +static JS_REQUIRES_STACK inline int +StackSlotHash(JSContext* cx, unsigned slot) +{ + uintptr_t h = HASH_SEED; + HashAccum(h, uintptr_t(cx->fp->script), ORACLE_MASK); + HashAccum(h, uintptr_t(cx->fp->regs->pc), ORACLE_MASK); + HashAccum(h, uintptr_t(slot), ORACLE_MASK); + return int(h); +} + +static JS_REQUIRES_STACK inline int +GlobalSlotHash(JSContext* cx, unsigned slot) +{ + uintptr_t h = HASH_SEED; + JSStackFrame* fp = cx->fp; + + while (fp->down) + fp = fp->down; + + HashAccum(h, uintptr_t(fp->script), ORACLE_MASK); + HashAccum(h, uintptr_t(OBJ_SHAPE(JS_GetGlobalForObject(cx, fp->scopeChain))), ORACLE_MASK); + HashAccum(h, uintptr_t(slot), ORACLE_MASK); + return int(h); +} + +static inline int +PCHash(jsbytecode* pc) +{ + return int(uintptr_t(pc) & ORACLE_MASK); +} + +Oracle::Oracle() +{ + /* Grow the oracle bitsets to their (fixed) size here, once. */ + _stackDontDemote.set(ORACLE_SIZE-1); + _globalDontDemote.set(ORACLE_SIZE-1); + clear(); +} + +/* Tell the oracle that a certain global variable should not be demoted. */ +JS_REQUIRES_STACK void +Oracle::markGlobalSlotUndemotable(JSContext* cx, unsigned slot) +{ + _globalDontDemote.set(GlobalSlotHash(cx, slot)); +} + +/* Consult with the oracle whether we shouldn't demote a certain global variable. */ +JS_REQUIRES_STACK bool +Oracle::isGlobalSlotUndemotable(JSContext* cx, unsigned slot) const +{ + return _globalDontDemote.get(GlobalSlotHash(cx, slot)); +} + +/* Tell the oracle that a certain slot at a certain stack slot should not be demoted. */ +JS_REQUIRES_STACK void +Oracle::markStackSlotUndemotable(JSContext* cx, unsigned slot) +{ + _stackDontDemote.set(StackSlotHash(cx, slot)); +} + +/* Consult with the oracle whether we shouldn't demote a certain slot. */ +JS_REQUIRES_STACK bool +Oracle::isStackSlotUndemotable(JSContext* cx, unsigned slot) const +{ + return _stackDontDemote.get(StackSlotHash(cx, slot)); +} + +/* Tell the oracle that a certain slot at a certain bytecode location should not be demoted. */ +void +Oracle::markInstructionUndemotable(jsbytecode* pc) +{ + _pcDontDemote.set(PCHash(pc)); +} + +/* Consult with the oracle whether we shouldn't demote a certain bytecode location. */ +bool +Oracle::isInstructionUndemotable(jsbytecode* pc) const +{ + return _pcDontDemote.get(PCHash(pc)); +} + +void +Oracle::clearDemotability() +{ + _stackDontDemote.reset(); + _globalDontDemote.reset(); + _pcDontDemote.reset(); +} + +JS_REQUIRES_STACK static JS_INLINE void +MarkSlotUndemotable(JSContext* cx, TreeInfo* ti, unsigned slot) +{ + if (slot < ti->nStackTypes) { + oracle.markStackSlotUndemotable(cx, slot); + return; + } + + uint16* gslots = ti->globalSlots->data(); + oracle.markGlobalSlotUndemotable(cx, gslots[slot - ti->nStackTypes]); +} + +static JS_REQUIRES_STACK inline bool +IsSlotUndemotable(JSContext* cx, TreeInfo* ti, unsigned slot) +{ + if (slot < ti->nStackTypes) + return oracle.isStackSlotUndemotable(cx, slot); + + uint16* gslots = ti->globalSlots->data(); + return oracle.isGlobalSlotUndemotable(cx, gslots[slot - ti->nStackTypes]); +} + +struct PCHashEntry : public JSDHashEntryStub { + size_t count; +}; + +#define PC_HASH_COUNT 1024 + +static void +Blacklist(jsbytecode* pc) +{ + AUDIT(blacklisted); + JS_ASSERT(*pc == JSOP_TRACE || *pc == JSOP_NOP); + *pc = JSOP_NOP; +} + +static void +Backoff(JSContext *cx, jsbytecode* pc, Fragment* tree = NULL) +{ + JSDHashTable *table = &JS_TRACE_MONITOR(cx).recordAttempts; + + if (table->ops) { + PCHashEntry *entry = (PCHashEntry *) + JS_DHashTableOperate(table, pc, JS_DHASH_ADD); + + if (entry) { + if (!entry->key) { + entry->key = pc; + JS_ASSERT(entry->count == 0); + } + JS_ASSERT(JS_DHASH_ENTRY_IS_LIVE(&(entry->hdr))); + if (entry->count++ > (BL_ATTEMPTS * MAXPEERS)) { + entry->count = 0; + Blacklist(pc); + return; + } + } + } + + if (tree) { + tree->hits() -= BL_BACKOFF; + + /* + * In case there is no entry or no table (due to OOM) or some + * serious imbalance in the recording-attempt distribution on a + * multitree, give each tree another chance to blacklist here as + * well. + */ + if (++tree->recordAttempts > BL_ATTEMPTS) + Blacklist(pc); + } +} + +static void +ResetRecordingAttempts(JSContext *cx, jsbytecode* pc) +{ + JSDHashTable *table = &JS_TRACE_MONITOR(cx).recordAttempts; + if (table->ops) { + PCHashEntry *entry = (PCHashEntry *) + JS_DHashTableOperate(table, pc, JS_DHASH_LOOKUP); + + if (JS_DHASH_ENTRY_IS_FREE(&(entry->hdr))) + return; + JS_ASSERT(JS_DHASH_ENTRY_IS_LIVE(&(entry->hdr))); + entry->count = 0; + } +} + +static inline size_t +FragmentHash(const void *ip, JSObject* globalObj, uint32 globalShape, uint32 argc) +{ + uintptr_t h = HASH_SEED; + HashAccum(h, uintptr_t(ip), FRAGMENT_TABLE_MASK); + HashAccum(h, uintptr_t(globalObj), FRAGMENT_TABLE_MASK); + HashAccum(h, uintptr_t(globalShape), FRAGMENT_TABLE_MASK); + HashAccum(h, uintptr_t(argc), FRAGMENT_TABLE_MASK); + return size_t(h); +} + +/* + * argc is cx->fp->argc at the trace loop header, i.e., the number of arguments + * pushed for the innermost JS frame. This is required as part of the fragment + * key because the fragment will write those arguments back to the interpreter + * stack when it exits, using its typemap, which implicitly incorporates a + * given value of argc. Without this feature, a fragment could be called as an + * inner tree with two different values of argc, and entry type checking or + * exit frame synthesis could crash. + */ +struct VMFragment : public Fragment +{ + VMFragment(const void* _ip, JSObject* _globalObj, uint32 _globalShape, uint32 _argc + verbose_only(, uint32_t profFragID)) : + Fragment(_ip verbose_only(, profFragID)), + first(NULL), + next(NULL), + peer(NULL), + globalObj(_globalObj), + globalShape(_globalShape), + argc(_argc) + { } + + inline TreeInfo* getTreeInfo() { + return (TreeInfo*)vmprivate; + } + + VMFragment* first; + VMFragment* next; + VMFragment* peer; + JSObject* globalObj; + uint32 globalShape; + uint32 argc; +}; + +static VMFragment* +getVMFragment(JSTraceMonitor* tm, const void *ip, JSObject* globalObj, uint32 globalShape, + uint32 argc) +{ + size_t h = FragmentHash(ip, globalObj, globalShape, argc); + VMFragment* vf = tm->vmfragments[h]; + while (vf && + ! (vf->globalObj == globalObj && + vf->globalShape == globalShape && + vf->ip == ip && + vf->argc == argc)) { + vf = vf->next; + } + return vf; +} + +static VMFragment* +getLoop(JSTraceMonitor* tm, const void *ip, JSObject* globalObj, uint32 globalShape, uint32 argc) +{ + return getVMFragment(tm, ip, globalObj, globalShape, argc); +} + +static VMFragment* +getAnchor(JSTraceMonitor* tm, const void *ip, JSObject* globalObj, uint32 globalShape, uint32 argc) +{ + verbose_only( + uint32_t profFragID = (js_LogController.lcbits & LC_FragProfile) + ? (++(tm->lastFragID)) : 0; + ) + VMFragment *f = new (*tm->dataAlloc) VMFragment(ip, globalObj, globalShape, argc + verbose_only(, profFragID)); + JS_ASSERT(f); + + VMFragment *p = getVMFragment(tm, ip, globalObj, globalShape, argc); + + if (p) { + f->first = p; + /* append at the end of the peer list */ + VMFragment* next; + while ((next = p->peer) != NULL) + p = next; + p->peer = f; + } else { + /* this is the first fragment */ + f->first = f; + size_t h = FragmentHash(ip, globalObj, globalShape, argc); + f->next = tm->vmfragments[h]; + tm->vmfragments[h] = f; + } + f->root = f; + return f; +} + +#ifdef DEBUG +static void +AssertTreeIsUnique(JSTraceMonitor* tm, VMFragment* f, TreeInfo* ti) +{ + JS_ASSERT(f->root == f); + + /* + * Check for duplicate entry type maps. This is always wrong and hints at + * trace explosion since we are trying to stabilize something without + * properly connecting peer edges. + */ + TreeInfo* ti_other; + for (VMFragment* peer = getLoop(tm, f->ip, f->globalObj, f->globalShape, f->argc); + peer != NULL; + peer = peer->peer) { + if (!peer->code() || peer == f) + continue; + ti_other = (TreeInfo*)peer->vmprivate; + JS_ASSERT(ti_other); + JS_ASSERT(!ti->typeMap.matches(ti_other->typeMap)); + } +} +#endif + +static void +AttemptCompilation(JSContext *cx, JSTraceMonitor* tm, JSObject* globalObj, jsbytecode* pc, + uint32 argc) +{ + /* If we already permanently blacklisted the location, undo that. */ + JS_ASSERT(*pc == JSOP_NOP || *pc == JSOP_TRACE); + *pc = JSOP_TRACE; + ResetRecordingAttempts(cx, pc); + + /* Breathe new life into all peer fragments at the designated loop header. */ + VMFragment* f = (VMFragment*)getLoop(tm, pc, globalObj, OBJ_SHAPE(globalObj), argc); + if (!f) { + /* + * If the global object's shape changed, we can't easily find the + * corresponding loop header via a hash table lookup. In this + * we simply bail here and hope that the fragment has another + * outstanding compilation attempt. This case is extremely rare. + */ + return; + } + JS_ASSERT(f->root == f); + f = f->first; + while (f) { + JS_ASSERT(f->root == f); + --f->recordAttempts; + f->hits() = HOTLOOP; + f = f->peer; + } +} + +// Forward declarations. +JS_DEFINE_CALLINFO_1(static, DOUBLE, i2f, INT32, 1, 1) +JS_DEFINE_CALLINFO_1(static, DOUBLE, u2f, UINT32, 1, 1) + +static bool +isi2f(LIns* i) +{ + if (i->isop(LIR_i2f)) + return true; + + if (nanojit::AvmCore::config.soft_float && + i->isop(LIR_qjoin) && + i->oprnd1()->isop(LIR_pcall) && + i->oprnd2()->isop(LIR_callh)) { + if (i->oprnd1()->callInfo() == &i2f_ci) + return true; + } + + return false; +} + +static bool +isu2f(LIns* i) +{ + if (i->isop(LIR_u2f)) + return true; + + if (nanojit::AvmCore::config.soft_float && + i->isop(LIR_qjoin) && + i->oprnd1()->isop(LIR_pcall) && + i->oprnd2()->isop(LIR_callh)) { + if (i->oprnd1()->callInfo() == &u2f_ci) + return true; + } + + return false; +} + +static LIns* +iu2fArg(LIns* i) +{ + if (nanojit::AvmCore::config.soft_float && + i->isop(LIR_qjoin)) { + return i->oprnd1()->arg(0); + } + + return i->oprnd1(); +} + +static LIns* +demote(LirWriter *out, LIns* i) +{ + if (i->isCall()) + return i->callArgN(0); + if (isi2f(i) || isu2f(i)) + return iu2fArg(i); + if (i->isconst()) + return i; + JS_ASSERT(i->isconstf()); + double cf = i->imm64f(); + int32_t ci = cf > 0x7fffffff ? uint32_t(cf) : int32_t(cf); + return out->insImm(ci); +} + +static bool +isPromoteInt(LIns* i) +{ + if (isi2f(i) || i->isconst()) + return true; + if (!i->isconstf()) + return false; + jsdouble d = i->imm64f(); + return d == jsdouble(jsint(d)) && !JSDOUBLE_IS_NEGZERO(d); +} + +static bool +isPromoteUint(LIns* i) +{ + if (isu2f(i) || i->isconst()) + return true; + if (!i->isconstf()) + return false; + jsdouble d = i->imm64f(); + return d == jsdouble(jsuint(d)) && !JSDOUBLE_IS_NEGZERO(d); +} + +static bool +isPromote(LIns* i) +{ + return isPromoteInt(i) || isPromoteUint(i); +} + +static bool +IsConst(LIns* i, int32_t c) +{ + return i->isconst() && i->imm32() == c; +} + +/* + * Determine whether this operand is guaranteed to not overflow the specified + * integer operation. + */ +static bool +IsOverflowSafe(LOpcode op, LIns* i) +{ + LIns* c; + switch (op) { + case LIR_add: + case LIR_sub: + return (i->isop(LIR_and) && ((c = i->oprnd2())->isconst()) && + ((c->imm32() & 0xc0000000) == 0)) || + (i->isop(LIR_rsh) && ((c = i->oprnd2())->isconst()) && + ((c->imm32() > 0))); + default: + JS_ASSERT(op == LIR_mul); + } + return (i->isop(LIR_and) && ((c = i->oprnd2())->isconst()) && + ((c->imm32() & 0xffff0000) == 0)) || + (i->isop(LIR_ush) && ((c = i->oprnd2())->isconst()) && + ((c->imm32() >= 16))); +} + +/* soft float support */ + +static jsdouble FASTCALL +fneg(jsdouble x) +{ + return -x; +} +JS_DEFINE_CALLINFO_1(static, DOUBLE, fneg, DOUBLE, 1, 1) + +static jsdouble FASTCALL +i2f(int32 i) +{ + return i; +} + +static jsdouble FASTCALL +u2f(jsuint u) +{ + return u; +} + +static int32 FASTCALL +fcmpeq(jsdouble x, jsdouble y) +{ + return x==y; +} +JS_DEFINE_CALLINFO_2(static, INT32, fcmpeq, DOUBLE, DOUBLE, 1, 1) + +static int32 FASTCALL +fcmplt(jsdouble x, jsdouble y) +{ + return x < y; +} +JS_DEFINE_CALLINFO_2(static, INT32, fcmplt, DOUBLE, DOUBLE, 1, 1) + +static int32 FASTCALL +fcmple(jsdouble x, jsdouble y) +{ + return x <= y; +} +JS_DEFINE_CALLINFO_2(static, INT32, fcmple, DOUBLE, DOUBLE, 1, 1) + +static int32 FASTCALL +fcmpgt(jsdouble x, jsdouble y) +{ + return x > y; +} +JS_DEFINE_CALLINFO_2(static, INT32, fcmpgt, DOUBLE, DOUBLE, 1, 1) + +static int32 FASTCALL +fcmpge(jsdouble x, jsdouble y) +{ + return x >= y; +} +JS_DEFINE_CALLINFO_2(static, INT32, fcmpge, DOUBLE, DOUBLE, 1, 1) + +static jsdouble FASTCALL +fmul(jsdouble x, jsdouble y) +{ + return x * y; +} +JS_DEFINE_CALLINFO_2(static, DOUBLE, fmul, DOUBLE, DOUBLE, 1, 1) + +static jsdouble FASTCALL +fadd(jsdouble x, jsdouble y) +{ + return x + y; +} +JS_DEFINE_CALLINFO_2(static, DOUBLE, fadd, DOUBLE, DOUBLE, 1, 1) + +static jsdouble FASTCALL +fdiv(jsdouble x, jsdouble y) +{ + return x / y; +} +JS_DEFINE_CALLINFO_2(static, DOUBLE, fdiv, DOUBLE, DOUBLE, 1, 1) + +static jsdouble FASTCALL +fsub(jsdouble x, jsdouble y) +{ + return x - y; +} +JS_DEFINE_CALLINFO_2(static, DOUBLE, fsub, DOUBLE, DOUBLE, 1, 1) + +// replace fpu ops with function calls +class SoftFloatFilter: public LirWriter +{ +public: + SoftFloatFilter(LirWriter *out) : LirWriter(out) + {} + + LIns *hi(LIns *q) { + return ins1(LIR_qhi, q); + } + LIns *lo(LIns *q) { + return ins1(LIR_qlo, q); + } + + LIns *split(LIns *a) { + if (a->isQuad() && !a->isop(LIR_qjoin)) { + // all quad-sized args must be qjoin's for soft-float + a = ins2(LIR_qjoin, lo(a), hi(a)); + } + return a; + } + + LIns *split(const CallInfo *call, LInsp args[]) { + LIns *lo = out->insCall(call, args); + LIns *hi = out->ins1(LIR_callh, lo); + return out->ins2(LIR_qjoin, lo, hi); + } + + LIns *fcall1(const CallInfo *call, LIns *a) { + LIns *args[] = { split(a) }; + return split(call, args); + } + + LIns *fcall2(const CallInfo *call, LIns *a, LIns *b) { + LIns *args[] = { split(b), split(a) }; + return split(call, args); + } + + LIns *fcmp(const CallInfo *call, LIns *a, LIns *b) { + LIns *args[] = { split(b), split(a) }; + return out->ins2(LIR_eq, out->insCall(call, args), out->insImm(1)); + } + + LIns *ins1(LOpcode op, LIns *a) { + switch (op) { + case LIR_i2f: + return fcall1(&i2f_ci, a); + case LIR_u2f: + return fcall1(&u2f_ci, a); + case LIR_fneg: + return fcall1(&fneg_ci, a); + case LIR_fret: + return out->ins1(op, split(a)); + default: + return out->ins1(op, a); + } + } + + LIns *ins2(LOpcode op, LIns *a, LIns *b) { + switch (op) { + case LIR_fadd: + return fcall2(&fadd_ci, a, b); + case LIR_fsub: + return fcall2(&fsub_ci, a, b); + case LIR_fmul: + return fcall2(&fmul_ci, a, b); + case LIR_fdiv: + return fcall2(&fdiv_ci, a, b); + case LIR_feq: + return fcmp(&fcmpeq_ci, a, b); + case LIR_flt: + return fcmp(&fcmplt_ci, a, b); + case LIR_fgt: + return fcmp(&fcmpgt_ci, a, b); + case LIR_fle: + return fcmp(&fcmple_ci, a, b); + case LIR_fge: + return fcmp(&fcmpge_ci, a, b); + default: + ; + } + return out->ins2(op, a, b); + } + + LIns *insCall(const CallInfo *ci, LInsp args[]) { + uint32_t argt = ci->_argtypes; + + for (uint32_t i = 0, argsizes = argt >> ARGSIZE_SHIFT; argsizes != 0; i++, argsizes >>= ARGSIZE_SHIFT) + args[i] = split(args[i]); + + if ((argt & ARGSIZE_MASK_ANY) == ARGSIZE_F) { + // this function returns a double as two 32bit values, so replace + // call with qjoin(qhi(call), call) + return split(ci, args); + } else { + return out->insCall(ci, args); + } + } +}; + +class FuncFilter: public LirWriter +{ +public: + FuncFilter(LirWriter* out): + LirWriter(out) + { + } + + LIns* ins2(LOpcode v, LIns* s0, LIns* s1) + { + if (s0 == s1 && v == LIR_feq) { + if (isPromote(s0)) { + // double(int) and double(uint) cannot be nan + return insImm(1); + } + if (s0->isop(LIR_fmul) || s0->isop(LIR_fsub) || s0->isop(LIR_fadd)) { + LIns* lhs = s0->oprnd1(); + LIns* rhs = s0->oprnd2(); + if (isPromote(lhs) && isPromote(rhs)) { + // add/sub/mul promoted ints can't be nan + return insImm(1); + } + } + } else if (LIR_feq <= v && v <= LIR_fge) { + if (isPromoteInt(s0) && isPromoteInt(s1)) { + // demote fcmp to cmp + v = LOpcode(v + (LIR_eq - LIR_feq)); + return out->ins2(v, demote(out, s0), demote(out, s1)); + } else if (isPromoteUint(s0) && isPromoteUint(s1)) { + // uint compare + v = LOpcode(v + (LIR_eq - LIR_feq)); + if (v != LIR_eq) + v = LOpcode(v + (LIR_ult - LIR_lt)); // cmp -> ucmp + return out->ins2(v, demote(out, s0), demote(out, s1)); + } + } else if (v == LIR_or && + s0->isop(LIR_lsh) && IsConst(s0->oprnd2(), 16) && + s1->isop(LIR_and) && IsConst(s1->oprnd2(), 0xffff)) { + LIns* msw = s0->oprnd1(); + LIns* lsw = s1->oprnd1(); + LIns* x; + LIns* y; + if (lsw->isop(LIR_add) && + lsw->oprnd1()->isop(LIR_and) && + lsw->oprnd2()->isop(LIR_and) && + IsConst(lsw->oprnd1()->oprnd2(), 0xffff) && + IsConst(lsw->oprnd2()->oprnd2(), 0xffff) && + msw->isop(LIR_add) && + msw->oprnd1()->isop(LIR_add) && + msw->oprnd2()->isop(LIR_rsh) && + msw->oprnd1()->oprnd1()->isop(LIR_rsh) && + msw->oprnd1()->oprnd2()->isop(LIR_rsh) && + IsConst(msw->oprnd2()->oprnd2(), 16) && + IsConst(msw->oprnd1()->oprnd1()->oprnd2(), 16) && + IsConst(msw->oprnd1()->oprnd2()->oprnd2(), 16) && + (x = lsw->oprnd1()->oprnd1()) == msw->oprnd1()->oprnd1()->oprnd1() && + (y = lsw->oprnd2()->oprnd1()) == msw->oprnd1()->oprnd2()->oprnd1() && + lsw == msw->oprnd2()->oprnd1()) { + return out->ins2(LIR_add, x, y); + } + } + + return out->ins2(v, s0, s1); + } + + LIns* insCall(const CallInfo *ci, LIns* args[]) + { + if (ci == &js_DoubleToUint32_ci) { + LIns* s0 = args[0]; + if (s0->isconstf()) + return out->insImm(js_DoubleToECMAUint32(s0->imm64f())); + if (isi2f(s0) || isu2f(s0)) + return iu2fArg(s0); + } else if (ci == &js_DoubleToInt32_ci) { + LIns* s0 = args[0]; + if (s0->isconstf()) + return out->insImm(js_DoubleToECMAInt32(s0->imm64f())); + if (s0->isop(LIR_fadd) || s0->isop(LIR_fsub)) { + LIns* lhs = s0->oprnd1(); + LIns* rhs = s0->oprnd2(); + if (isPromote(lhs) && isPromote(rhs)) { + LOpcode op = LOpcode(s0->opcode() & ~LIR64); + return out->ins2(op, demote(out, lhs), demote(out, rhs)); + } + } + if (isi2f(s0) || isu2f(s0)) + return iu2fArg(s0); + + // XXX ARM -- check for qjoin(call(UnboxDouble),call(UnboxDouble)) + if (s0->isCall()) { + const CallInfo* ci2 = s0->callInfo(); + if (ci2 == &js_UnboxDouble_ci) { + LIns* args2[] = { s0->callArgN(0) }; + return out->insCall(&js_UnboxInt32_ci, args2); + } else if (ci2 == &js_StringToNumber_ci) { + // callArgN's ordering is that as seen by the builtin, not as stored in + // args here. True story! + LIns* args2[] = { s0->callArgN(1), s0->callArgN(0) }; + return out->insCall(&js_StringToInt32_ci, args2); + } else if (ci2 == &js_String_p_charCodeAt0_ci) { + // Use a fast path builtin for a charCodeAt that converts to an int right away. + LIns* args2[] = { s0->callArgN(0) }; + return out->insCall(&js_String_p_charCodeAt0_int_ci, args2); + } else if (ci2 == &js_String_p_charCodeAt_ci) { + LIns* idx = s0->callArgN(1); + // If the index is not already an integer, force it to be an integer. + idx = isPromote(idx) + ? demote(out, idx) + : out->insCall(&js_DoubleToInt32_ci, &idx); + LIns* args2[] = { idx, s0->callArgN(0) }; + return out->insCall(&js_String_p_charCodeAt_int_ci, args2); + } + } + } else if (ci == &js_BoxDouble_ci) { + LIns* s0 = args[0]; + JS_ASSERT(s0->isQuad()); + if (isPromoteInt(s0)) { + LIns* args2[] = { demote(out, s0), args[1] }; + return out->insCall(&js_BoxInt32_ci, args2); + } + if (s0->isCall() && s0->callInfo() == &js_UnboxDouble_ci) + return s0->callArgN(0); + } + return out->insCall(ci, args); + } +}; + +/* + * Visit the values in the given JSStackFrame that the tracer cares about. This + * visitor function is (implicitly) the primary definition of the native stack + * area layout. There are a few other independent pieces of code that must be + * maintained to assume the same layout. They are marked like this: + * + * Duplicate native stack layout computation: see VisitFrameSlots header comment. + */ +template +static JS_REQUIRES_STACK bool +VisitFrameSlots(Visitor &visitor, unsigned depth, JSStackFrame *fp, + JSStackFrame *up) +{ + if (depth > 0 && !VisitFrameSlots(visitor, depth-1, fp->down, fp)) + return false; + + if (fp->argv) { + if (depth == 0) { + visitor.setStackSlotKind("args"); + if (!visitor.visitStackSlots(&fp->argv[-2], argSlots(fp) + 2, fp)) + return false; + } + visitor.setStackSlotKind("arguments"); + if (!visitor.visitStackSlots(&fp->argsobj, 1, fp)) + return false; + visitor.setStackSlotKind("var"); + if (!visitor.visitStackSlots(fp->slots, fp->script->nfixed, fp)) + return false; + } + visitor.setStackSlotKind("stack"); + JS_ASSERT(fp->regs->sp >= StackBase(fp)); + if (!visitor.visitStackSlots(StackBase(fp), + size_t(fp->regs->sp - StackBase(fp)), + fp)) { + return false; + } + if (up) { + int missing = up->fun->nargs - up->argc; + if (missing > 0) { + visitor.setStackSlotKind("missing"); + if (!visitor.visitStackSlots(fp->regs->sp, size_t(missing), fp)) + return false; + } + } + return true; +} + +template +static JS_REQUIRES_STACK JS_ALWAYS_INLINE bool +VisitStackSlots(Visitor &visitor, JSContext *cx, unsigned callDepth) +{ + return VisitFrameSlots(visitor, callDepth, cx->fp, NULL); +} + +template +static JS_REQUIRES_STACK JS_ALWAYS_INLINE void +VisitGlobalSlots(Visitor &visitor, JSContext *cx, JSObject *globalObj, + unsigned ngslots, uint16 *gslots) +{ + for (unsigned n = 0; n < ngslots; ++n) { + unsigned slot = gslots[n]; + visitor.visitGlobalSlot(&STOBJ_GET_SLOT(globalObj, slot), n, slot); + } +} + +class AdjustCallerTypeVisitor; + +template +static JS_REQUIRES_STACK JS_ALWAYS_INLINE void +VisitGlobalSlots(Visitor &visitor, JSContext *cx, SlotList &gslots) +{ + VisitGlobalSlots(visitor, cx, JS_GetGlobalForObject(cx, cx->fp->scopeChain), + gslots.length(), gslots.data()); +} + + +template +static JS_REQUIRES_STACK JS_ALWAYS_INLINE void +VisitSlots(Visitor& visitor, JSContext* cx, JSObject* globalObj, + unsigned callDepth, unsigned ngslots, uint16* gslots) +{ + if (VisitStackSlots(visitor, cx, callDepth)) + VisitGlobalSlots(visitor, cx, globalObj, ngslots, gslots); +} + +template +static JS_REQUIRES_STACK JS_ALWAYS_INLINE void +VisitSlots(Visitor& visitor, JSContext* cx, unsigned callDepth, + unsigned ngslots, uint16* gslots) +{ + VisitSlots(visitor, cx, JS_GetGlobalForObject(cx, cx->fp->scopeChain), + callDepth, ngslots, gslots); +} + +template +static JS_REQUIRES_STACK JS_ALWAYS_INLINE void +VisitSlots(Visitor &visitor, JSContext *cx, JSObject *globalObj, + unsigned callDepth, const SlotList& slots) +{ + VisitSlots(visitor, cx, globalObj, callDepth, slots.length(), + slots.data()); +} + +template +static JS_REQUIRES_STACK JS_ALWAYS_INLINE void +VisitSlots(Visitor &visitor, JSContext *cx, unsigned callDepth, + const SlotList& slots) +{ + VisitSlots(visitor, cx, JS_GetGlobalForObject(cx, cx->fp->scopeChain), + callDepth, slots.length(), slots.data()); +} + + +class SlotVisitorBase { +#ifdef JS_JIT_SPEW +protected: + char const *mStackSlotKind; +public: + SlotVisitorBase() : mStackSlotKind(NULL) {} + JS_ALWAYS_INLINE const char *stackSlotKind() { return mStackSlotKind; } + JS_ALWAYS_INLINE void setStackSlotKind(char const *k) { + mStackSlotKind = k; + } +#else +public: + JS_ALWAYS_INLINE const char *stackSlotKind() { return NULL; } + JS_ALWAYS_INLINE void setStackSlotKind(char const *k) {} +#endif +}; + +struct CountSlotsVisitor : public SlotVisitorBase +{ + unsigned mCount; + bool mDone; + jsval* mStop; +public: + JS_ALWAYS_INLINE CountSlotsVisitor(jsval* stop = NULL) : + mCount(0), + mDone(false), + mStop(stop) + {} + + JS_REQUIRES_STACK JS_ALWAYS_INLINE bool + visitStackSlots(jsval *vp, size_t count, JSStackFrame* fp) { + if (mDone) + return false; + if (mStop && size_t(mStop - vp) < count) { + mCount += size_t(mStop - vp); + mDone = true; + return false; + } + mCount += count; + return true; + } + + JS_ALWAYS_INLINE unsigned count() { + return mCount; + } + + JS_ALWAYS_INLINE bool stopped() { + return mDone; + } +}; + +/* + * Calculate the total number of native frame slots we need from this frame all + * the way back to the entry frame, including the current stack usage. + */ +JS_REQUIRES_STACK unsigned +NativeStackSlots(JSContext *cx, unsigned callDepth) +{ + JSStackFrame* fp = cx->fp; + unsigned slots = 0; + unsigned depth = callDepth; + for (;;) { + /* + * Duplicate native stack layout computation: see VisitFrameSlots + * header comment. + */ + unsigned operands = fp->regs->sp - StackBase(fp); + slots += operands; + if (fp->argv) + slots += fp->script->nfixed + 1 /*argsobj*/; + if (depth-- == 0) { + if (fp->argv) + slots += 2/*callee,this*/ + argSlots(fp); +#ifdef DEBUG + CountSlotsVisitor visitor; + VisitStackSlots(visitor, cx, callDepth); + JS_ASSERT(visitor.count() == slots && !visitor.stopped()); +#endif + return slots; + } + JSStackFrame* fp2 = fp; + fp = fp->down; + int missing = fp2->fun->nargs - fp2->argc; + if (missing > 0) + slots += missing; + } + JS_NOT_REACHED("NativeStackSlots"); +} + +class CaptureTypesVisitor : public SlotVisitorBase +{ + JSContext* mCx; + JSTraceType* mTypeMap; + JSTraceType* mPtr; + +public: + JS_ALWAYS_INLINE CaptureTypesVisitor(JSContext* cx, JSTraceType* typeMap) : + mCx(cx), + mTypeMap(typeMap), + mPtr(typeMap) + {} + + JS_REQUIRES_STACK JS_ALWAYS_INLINE void + visitGlobalSlot(jsval *vp, unsigned n, unsigned slot) { + JSTraceType type = getCoercedType(*vp); + if (type == TT_INT32 && + oracle.isGlobalSlotUndemotable(mCx, slot)) + type = TT_DOUBLE; + JS_ASSERT(type != TT_JSVAL); + debug_only_printf(LC_TMTracer, + "capture type global%d: %d=%c\n", + n, type, typeChar[type]); + *mPtr++ = type; + } + + JS_REQUIRES_STACK JS_ALWAYS_INLINE bool + visitStackSlots(jsval *vp, int count, JSStackFrame* fp) { + for (int i = 0; i < count; ++i) { + JSTraceType type = getCoercedType(vp[i]); + if (type == TT_INT32 && + oracle.isStackSlotUndemotable(mCx, length())) + type = TT_DOUBLE; + JS_ASSERT(type != TT_JSVAL); + debug_only_printf(LC_TMTracer, + "capture type %s%d: %d=%c\n", + stackSlotKind(), i, type, typeChar[type]); + *mPtr++ = type; + } + return true; + } + + JS_ALWAYS_INLINE uintptr_t length() { + return mPtr - mTypeMap; + } +}; + +/* + * Capture the type map for the selected slots of the global object and currently pending + * stack frames. + */ +JS_REQUIRES_STACK void +TypeMap::captureTypes(JSContext* cx, JSObject* globalObj, SlotList& slots, unsigned callDepth) +{ + setLength(NativeStackSlots(cx, callDepth) + slots.length()); + CaptureTypesVisitor visitor(cx, data()); + VisitSlots(visitor, cx, globalObj, callDepth, slots); + JS_ASSERT(visitor.length() == length()); +} + +JS_REQUIRES_STACK void +TypeMap::captureMissingGlobalTypes(JSContext* cx, JSObject* globalObj, SlotList& slots, unsigned stackSlots) +{ + unsigned oldSlots = length() - stackSlots; + int diff = slots.length() - oldSlots; + JS_ASSERT(diff >= 0); + setLength(length() + diff); + CaptureTypesVisitor visitor(cx, data() + stackSlots + oldSlots); + VisitGlobalSlots(visitor, cx, globalObj, diff, slots.data() + oldSlots); +} + +/* Compare this type map to another one and see whether they match. */ +bool +TypeMap::matches(TypeMap& other) const +{ + if (length() != other.length()) + return false; + return !memcmp(data(), other.data(), length()); +} + +void +TypeMap::fromRaw(JSTraceType* other, unsigned numSlots) +{ + unsigned oldLength = length(); + setLength(length() + numSlots); + for (unsigned i = 0; i < numSlots; i++) + get(oldLength + i) = other[i]; +} + +/* + * Use the provided storage area to create a new type map that contains the + * partial type map with the rest of it filled up from the complete type + * map. + */ +static void +MergeTypeMaps(JSTraceType** partial, unsigned* plength, JSTraceType* complete, unsigned clength, JSTraceType* mem) +{ + unsigned l = *plength; + JS_ASSERT(l < clength); + memcpy(mem, *partial, l * sizeof(JSTraceType)); + memcpy(mem + l, complete + l, (clength - l) * sizeof(JSTraceType)); + *partial = mem; + *plength = clength; +} + +/* + * Specializes a tree to any specifically missing globals, including any + * dependent trees. + */ +static JS_REQUIRES_STACK void +SpecializeTreesToLateGlobals(JSContext* cx, TreeInfo* root, JSTraceType* globalTypeMap, + unsigned numGlobalSlots) +{ + TreeInfo* ti = root; + + for (unsigned i = ti->nGlobalTypes(); i < numGlobalSlots; i++) + ti->typeMap.add(globalTypeMap[i]); + + JS_ASSERT(ti->nGlobalTypes() == numGlobalSlots); + + for (unsigned i = 0; i < root->dependentTrees.length(); i++) { + ti = (TreeInfo*)root->dependentTrees[i]->vmprivate; + + /* ti can be NULL if we hit the recording tree in emitTreeCall; this is harmless. */ + if (ti && ti->nGlobalTypes() < numGlobalSlots) + SpecializeTreesToLateGlobals(cx, ti, globalTypeMap, numGlobalSlots); + } + for (unsigned i = 0; i < root->linkedTrees.length(); i++) { + ti = (TreeInfo*)root->linkedTrees[i]->vmprivate; + if (ti && ti->nGlobalTypes() < numGlobalSlots) + SpecializeTreesToLateGlobals(cx, ti, globalTypeMap, numGlobalSlots); + } +} + +/* Specializes a tree to any missing globals, including any dependent trees. */ +static JS_REQUIRES_STACK void +SpecializeTreesToMissingGlobals(JSContext* cx, JSObject* globalObj, TreeInfo* root) +{ + TreeInfo* ti = root; + + ti->typeMap.captureMissingGlobalTypes(cx, globalObj, *ti->globalSlots, ti->nStackTypes); + JS_ASSERT(ti->globalSlots->length() == ti->typeMap.length() - ti->nStackTypes); + + SpecializeTreesToLateGlobals(cx, ti, ti->globalTypeMap(), ti->nGlobalTypes()); +} + +static void +TrashTree(JSContext* cx, Fragment* f); + +JS_REQUIRES_STACK +TraceRecorder::TraceRecorder(JSContext* cx, VMSideExit* _anchor, Fragment* _fragment, + TreeInfo* ti, unsigned stackSlots, unsigned ngslots, JSTraceType* typeMap, + VMSideExit* innermostNestedGuard, jsbytecode* outer, uint32 outerArgc) + : tempAlloc(*JS_TRACE_MONITOR(cx).tempAlloc), + whichTreesToTrash(&tempAlloc), + cfgMerges(&tempAlloc), + tempTypeMap(cx) +{ + JS_ASSERT(!_fragment->vmprivate && ti && cx->fp->regs->pc == (jsbytecode*)_fragment->ip); + /* Reset the fragment state we care about in case we got a recycled fragment. + This includes resetting any profiling data we might have accumulated. */ + _fragment->lastIns = NULL; + verbose_only( _fragment->profCount = 0; ) + verbose_only( _fragment->nStaticExits = 0; ) + verbose_only( _fragment->nCodeBytes = 0; ) + verbose_only( _fragment->nExitBytes = 0; ) + verbose_only( _fragment->guardNumberer = 1; ) + verbose_only( _fragment->guardsForFrag = NULL; ) + verbose_only( _fragment->loopLabel = NULL; ) + // don't change _fragment->profFragID, though. Once the identity of + // the Fragment is set up (for profiling purposes), we can't change it. + this->cx = cx; + this->traceMonitor = &JS_TRACE_MONITOR(cx); + this->globalObj = JS_GetGlobalForObject(cx, cx->fp->scopeChain); + this->lexicalBlock = cx->fp->blockChain; + this->anchor = _anchor; + this->fragment = _fragment; + this->lirbuf = _fragment->lirbuf; + this->treeInfo = ti; + this->callDepth = _anchor ? _anchor->calldepth : 0; + this->atoms = FrameAtomBase(cx, cx->fp); + this->trashSelf = false; + this->global_dslots = this->globalObj->dslots; + this->loop = true; /* default assumption is we are compiling a loop */ + this->outer = outer; + this->outerArgc = outerArgc; + this->pendingSpecializedNative = NULL; + this->newobj_ins = NULL; + this->loopLabel = NULL; + +#ifdef JS_JIT_SPEW + debug_only_print0(LC_TMMinimal, "\n"); + debug_only_printf(LC_TMMinimal, "Recording starting from %s:%u@%u (FragID=%06u)\n", + ti->treeFileName, ti->treeLineNumber, ti->treePCOffset, + _fragment->profFragID); + + debug_only_printf(LC_TMTracer, "globalObj=%p, shape=%d\n", + (void*)this->globalObj, OBJ_SHAPE(this->globalObj)); + debug_only_printf(LC_TMTreeVis, "TREEVIS RECORD FRAG=%p ANCHOR=%p\n", (void*)fragment, + (void*)anchor); +#endif + + lir = lir_buf_writer = new LirBufWriter(lirbuf); +#ifdef DEBUG + lir = sanity_filter_1 = new SanityFilter(lir); +#endif + debug_only_stmt( + if (js_LogController.lcbits & LC_TMRecorder) { + lir = verbose_filter + = new VerboseWriter (tempAlloc, lir, lirbuf->names, + &js_LogController); + } + ) + // CseFilter must be downstream of SoftFloatFilter (see bug 527754 for why). + lir = cse_filter = new CseFilter(lir, tempAlloc); + if (nanojit::AvmCore::config.soft_float) + lir = float_filter = new SoftFloatFilter(lir); + lir = expr_filter = new ExprFilter(lir); + lir = func_filter = new FuncFilter(lir); +#ifdef DEBUG + lir = sanity_filter_2 = new SanityFilter(lir); +#endif + lir->ins0(LIR_start); + + for (int i = 0; i < NumSavedRegs; ++i) + lir->insParam(i, 1); +#ifdef DEBUG + for (int i = 0; i < NumSavedRegs; ++i) + addName(lirbuf->savedRegs[i], regNames[Assembler::savedRegs[i]]); +#endif + + lirbuf->state = addName(lir->insParam(0, 0), "state"); + + if (fragment == fragment->root) + loopLabel = lir->ins0(LIR_label); + + // if profiling, drop a label, so the assembler knows to put a + // frag-entry-counter increment at this point. If there's a + // loopLabel, use that; else we'll have to make a dummy label + // especially for this purpose. + verbose_only( if (js_LogController.lcbits & LC_FragProfile) { + LIns* entryLabel = NULL; + if (fragment == fragment->root) { + entryLabel = loopLabel; + } else { + entryLabel = lir->ins0(LIR_label); + } + NanoAssert(entryLabel); + NanoAssert(!fragment->loopLabel); + fragment->loopLabel = entryLabel; + }) + + lirbuf->sp = addName(lir->insLoad(LIR_ldp, lirbuf->state, (int)offsetof(InterpState, sp)), "sp"); + lirbuf->rp = addName(lir->insLoad(LIR_ldp, lirbuf->state, offsetof(InterpState, rp)), "rp"); + cx_ins = addName(lir->insLoad(LIR_ldp, lirbuf->state, offsetof(InterpState, cx)), "cx"); + eos_ins = addName(lir->insLoad(LIR_ldp, lirbuf->state, offsetof(InterpState, eos)), "eos"); + eor_ins = addName(lir->insLoad(LIR_ldp, lirbuf->state, offsetof(InterpState, eor)), "eor"); + + /* If we came from exit, we might not have enough global types. */ + if (ti->globalSlots->length() > ti->nGlobalTypes()) + SpecializeTreesToMissingGlobals(cx, globalObj, ti); + + /* read into registers all values on the stack and all globals we know so far */ + import(treeInfo, lirbuf->sp, stackSlots, ngslots, callDepth, typeMap); + + if (fragment == fragment->root) { + /* + * We poll the operation callback request flag. It is updated asynchronously whenever + * the callback is to be invoked. + */ + LIns* x = lir->insLoad(LIR_ld, cx_ins, offsetof(JSContext, operationCallbackFlag)); + guard(true, lir->ins_eq0(x), snapshot(TIMEOUT_EXIT)); + } + + /* + * If we are attached to a tree call guard, make sure the guard the inner + * tree exited from is what we expect it to be. + */ + if (_anchor && _anchor->exitType == NESTED_EXIT) { + LIns* nested_ins = addName(lir->insLoad(LIR_ldp, lirbuf->state, + offsetof(InterpState, lastTreeExitGuard)), + "lastTreeExitGuard"); + guard(true, lir->ins2(LIR_peq, nested_ins, INS_CONSTPTR(innermostNestedGuard)), NESTED_EXIT); + } +} + +TraceRecorder::~TraceRecorder() +{ + JS_ASSERT(treeInfo && fragment); + + if (trashSelf) + TrashTree(cx, fragment->root); + + for (unsigned int i = 0; i < whichTreesToTrash.length(); i++) + TrashTree(cx, whichTreesToTrash[i]); + + /* Purge the tempAlloc used during recording. */ + tempAlloc.reset(); + traceMonitor->lirbuf->clear(); + +#ifdef DEBUG + debug_only_stmt( delete verbose_filter; ) + delete sanity_filter_1; + delete sanity_filter_2; +#endif + delete cse_filter; + delete expr_filter; + delete func_filter; + delete float_filter; + delete lir_buf_writer; +} + +bool +TraceRecorder::outOfMemory() +{ + return traceMonitor->dataAlloc->outOfMemory() || tempAlloc.outOfMemory(); +} + +/* Add debug information to a LIR instruction as we emit it. */ +inline LIns* +TraceRecorder::addName(LIns* ins, const char* name) +{ +#ifdef JS_JIT_SPEW + /* + * We'll only ask for verbose Nanojit when .lcbits > 0, so there's no point + * in adding names otherwise. + */ + if (js_LogController.lcbits > 0) + lirbuf->names->addName(ins, name); +#endif + return ins; +} + +inline LIns* +TraceRecorder::insImmVal(jsval val) +{ + if (JSVAL_IS_TRACEABLE(val)) + treeInfo->gcthings.addUnique(val); + return lir->insImmWord(val); +} + +inline LIns* +TraceRecorder::insImmObj(JSObject* obj) +{ + treeInfo->gcthings.addUnique(OBJECT_TO_JSVAL(obj)); + return lir->insImmPtr((void*)obj); +} + +inline LIns* +TraceRecorder::insImmFun(JSFunction* fun) +{ + treeInfo->gcthings.addUnique(OBJECT_TO_JSVAL(FUN_OBJECT(fun))); + return lir->insImmPtr((void*)fun); +} + +inline LIns* +TraceRecorder::insImmStr(JSString* str) +{ + treeInfo->gcthings.addUnique(STRING_TO_JSVAL(str)); + return lir->insImmPtr((void*)str); +} + +inline LIns* +TraceRecorder::insImmSprop(JSScopeProperty* sprop) +{ + treeInfo->sprops.addUnique(sprop); + return lir->insImmPtr((void*)sprop); +} + +inline LIns* +TraceRecorder::p2i(nanojit::LIns* ins) +{ +#ifdef NANOJIT_64BIT + return lir->ins1(LIR_qlo, ins); +#else + return ins; +#endif +} + +/* Determine the current call depth (starting with the entry frame.) */ +unsigned +TraceRecorder::getCallDepth() const +{ + return callDepth; +} + +/* Determine the offset in the native global frame for a jsval we track. */ +ptrdiff_t +TraceRecorder::nativeGlobalOffset(jsval* p) const +{ + JS_ASSERT(isGlobal(p)); + if (size_t(p - globalObj->fslots) < JS_INITIAL_NSLOTS) + return sizeof(InterpState) + size_t(p - globalObj->fslots) * sizeof(double); + return sizeof(InterpState) + ((p - globalObj->dslots) + JS_INITIAL_NSLOTS) * sizeof(double); +} + +/* Determine whether a value is a global stack slot. */ +bool +TraceRecorder::isGlobal(jsval* p) const +{ + return ((size_t(p - globalObj->fslots) < JS_INITIAL_NSLOTS) || + (size_t(p - globalObj->dslots) < (STOBJ_NSLOTS(globalObj) - JS_INITIAL_NSLOTS))); +} + +/* + * Return the offset in the native stack for the given jsval. More formally, + * |p| must be the address of a jsval that is represented in the native stack + * area. The return value is the offset, from InterpState::stackBase, in bytes, + * where the native representation of |*p| is stored. To get the offset + * relative to InterpState::sp, subtract TreeInfo::nativeStackBase. + */ +JS_REQUIRES_STACK ptrdiff_t +TraceRecorder::nativeStackOffset(jsval* p) const +{ + CountSlotsVisitor visitor(p); + VisitStackSlots(visitor, cx, callDepth); + size_t offset = visitor.count() * sizeof(double); + + /* + * If it's not in a pending frame, it must be on the stack of the current + * frame above sp but below fp->slots + script->nslots. + */ + if (!visitor.stopped()) { + JS_ASSERT(size_t(p - cx->fp->slots) < cx->fp->script->nslots); + offset += size_t(p - cx->fp->regs->sp) * sizeof(double); + } + return offset; +} + +/* Track the maximum number of native frame slots we need during execution. */ +void +TraceRecorder::trackNativeStackUse(unsigned slots) +{ + if (slots > treeInfo->maxNativeStackSlots) + treeInfo->maxNativeStackSlots = slots; +} + +/* + * Unbox a jsval into a slot. Slots are wide enough to hold double values + * directly (instead of storing a pointer to them). We assert instead of + * type checking. The caller must ensure the types are compatible. + */ +static void +ValueToNative(JSContext* cx, jsval v, JSTraceType type, double* slot) +{ + uint8_t tag = JSVAL_TAG(v); + switch (type) { + case TT_OBJECT: + JS_ASSERT(tag == JSVAL_OBJECT); + JS_ASSERT(!JSVAL_IS_NULL(v) && !HAS_FUNCTION_CLASS(JSVAL_TO_OBJECT(v))); + *(JSObject**)slot = JSVAL_TO_OBJECT(v); + debug_only_printf(LC_TMTracer, + "object<%p:%s> ", (void*)JSVAL_TO_OBJECT(v), + JSVAL_IS_NULL(v) + ? "null" + : STOBJ_GET_CLASS(JSVAL_TO_OBJECT(v))->name); + return; + + case TT_INT32: + jsint i; + if (JSVAL_IS_INT(v)) + *(jsint*)slot = JSVAL_TO_INT(v); + else if (tag == JSVAL_DOUBLE && JSDOUBLE_IS_INT(*JSVAL_TO_DOUBLE(v), i)) + *(jsint*)slot = i; + else + JS_ASSERT(JSVAL_IS_INT(v)); + debug_only_printf(LC_TMTracer, "int<%d> ", *(jsint*)slot); + return; + + case TT_DOUBLE: + jsdouble d; + if (JSVAL_IS_INT(v)) + d = JSVAL_TO_INT(v); + else + d = *JSVAL_TO_DOUBLE(v); + JS_ASSERT(JSVAL_IS_INT(v) || JSVAL_IS_DOUBLE(v)); + *(jsdouble*)slot = d; + debug_only_printf(LC_TMTracer, "double<%g> ", d); + return; + + case TT_JSVAL: + JS_NOT_REACHED("found jsval type in an entry type map"); + return; + + case TT_STRING: + JS_ASSERT(tag == JSVAL_STRING); + *(JSString**)slot = JSVAL_TO_STRING(v); + debug_only_printf(LC_TMTracer, "string<%p> ", (void*)(*(JSString**)slot)); + return; + + case TT_NULL: + JS_ASSERT(tag == JSVAL_OBJECT); + *(JSObject**)slot = NULL; + debug_only_print0(LC_TMTracer, "null "); + return; + + case TT_PSEUDOBOOLEAN: + /* Watch out for pseudo-booleans. */ + JS_ASSERT(tag == JSVAL_SPECIAL); + *(JSBool*)slot = JSVAL_TO_SPECIAL(v); + debug_only_printf(LC_TMTracer, "pseudoboolean<%d> ", *(JSBool*)slot); + return; + + case TT_FUNCTION: { + JS_ASSERT(tag == JSVAL_OBJECT); + JSObject* obj = JSVAL_TO_OBJECT(v); + *(JSObject**)slot = obj; +#ifdef DEBUG + JSFunction* fun = GET_FUNCTION_PRIVATE(cx, obj); + debug_only_printf(LC_TMTracer, + "function<%p:%s> ", (void*) obj, + fun->atom + ? JS_GetStringBytes(ATOM_TO_STRING(fun->atom)) + : "unnamed"); +#endif + return; + } + } + + JS_NOT_REACHED("unexpected type"); +} + +/* + * We maintain an emergency pool of doubles so we can recover safely if a trace + * runs out of memory (doubles or objects). + */ +static jsval +AllocateDoubleFromReservedPool(JSContext* cx) +{ + JSTraceMonitor* tm = &JS_TRACE_MONITOR(cx); + JS_ASSERT(tm->reservedDoublePoolPtr > tm->reservedDoublePool); + return *--tm->reservedDoublePoolPtr; +} + +static bool +ReplenishReservedPool(JSContext* cx, JSTraceMonitor* tm) +{ + /* We should not be called with a full pool. */ + JS_ASSERT((size_t) (tm->reservedDoublePoolPtr - tm->reservedDoublePool) < + MAX_NATIVE_STACK_SLOTS); + + /* + * When the GC runs in js_NewDoubleInRootedValue, it resets + * tm->reservedDoublePoolPtr back to tm->reservedDoublePool. + */ + JSRuntime* rt = cx->runtime; + uintN gcNumber = rt->gcNumber; + uintN lastgcNumber = gcNumber; + jsval* ptr = tm->reservedDoublePoolPtr; + while (ptr < tm->reservedDoublePool + MAX_NATIVE_STACK_SLOTS) { + if (!js_NewDoubleInRootedValue(cx, 0.0, ptr)) + goto oom; + + /* Check if the last call to js_NewDoubleInRootedValue GC'd. */ + if (rt->gcNumber != lastgcNumber) { + lastgcNumber = rt->gcNumber; + ptr = tm->reservedDoublePool; + + /* + * Have we GC'd more than once? We're probably running really + * low on memory, bail now. + */ + if (uintN(rt->gcNumber - gcNumber) > uintN(1)) + goto oom; + continue; + } + ++ptr; + } + tm->reservedDoublePoolPtr = ptr; + return true; + +oom: + /* + * Already massive GC pressure, no need to hold doubles back. + * We won't run any native code anyway. + */ + tm->reservedDoublePoolPtr = tm->reservedDoublePool; + return false; +} + +void +JSTraceMonitor::flush() +{ + AUDIT(cacheFlushed); + + // recover profiling data from expiring Fragments + verbose_only( + for (size_t i = 0; i < FRAGMENT_TABLE_SIZE; ++i) { + for (VMFragment *f = vmfragments[i]; f; f = f->next) { + JS_ASSERT(f->root == f); + for (VMFragment *p = f; p; p = p->peer) + js_FragProfiling_FragFinalizer(p, this); + } + } + ) + + verbose_only( + for (Seq* f = branches; f; f = f->tail) + js_FragProfiling_FragFinalizer(f->head, this); + ) + + dataAlloc->reset(); + codeAlloc->reset(); + tempAlloc->reset(); + reTempAlloc->reset(); + + Allocator& alloc = *dataAlloc; + + for (size_t i = 0; i < MONITOR_N_GLOBAL_STATES; ++i) { + globalStates[i].globalShape = -1; + globalStates[i].globalSlots = new (alloc) SlotList(&alloc); + } + + assembler = new (alloc) Assembler(*codeAlloc, alloc, core, &js_LogController); + lirbuf = new (alloc) LirBuffer(*tempAlloc); + reLirBuf = new (alloc) LirBuffer(*reTempAlloc); + verbose_only( branches = NULL; ) + +#ifdef DEBUG + labels = new (alloc) LabelMap(alloc, &js_LogController); + reLirBuf->names = + lirbuf->names = new (alloc) LirNameMap(alloc, labels); +#endif + + memset(&vmfragments[0], 0, FRAGMENT_TABLE_SIZE * sizeof(VMFragment*)); + reFragments = new (alloc) REHashMap(alloc); + + needFlush = JS_FALSE; +} + +static inline void +MarkTreeInfo(JSTracer* trc, TreeInfo *ti) +{ + jsval* vp = ti->gcthings.data(); + unsigned len = ti->gcthings.length(); + while (len--) { + jsval v = *vp++; + JS_SET_TRACING_NAME(trc, "jitgcthing"); + JS_CallTracer(trc, JSVAL_TO_TRACEABLE(v), JSVAL_TRACE_KIND(v)); + } + JSScopeProperty** spropp = ti->sprops.data(); + len = ti->sprops.length(); + while (len--) { + JSScopeProperty* sprop = *spropp++; + sprop->trace(trc); + } +} + +void +JSTraceMonitor::mark(JSTracer* trc) +{ + if (!trc->context->runtime->gcFlushCodeCaches) { + for (size_t i = 0; i < FRAGMENT_TABLE_SIZE; ++i) { + VMFragment* f = vmfragments[i]; + while (f) { + if (TreeInfo* ti = (TreeInfo*)f->vmprivate) + MarkTreeInfo(trc, ti); + VMFragment* peer = (VMFragment*)f->peer; + while (peer) { + if (TreeInfo* ti = (TreeInfo*)peer->vmprivate) + MarkTreeInfo(trc, ti); + peer = (VMFragment*)peer->peer; + } + f = f->next; + } + } + if (recorder) + MarkTreeInfo(trc, recorder->getTreeInfo()); + } +} + +/* + * Box a value from the native stack back into the jsval format. Integers that + * are too large to fit into a jsval are automatically boxed into + * heap-allocated doubles. + */ +template +static inline bool +NativeToValueBase(JSContext* cx, jsval& v, JSTraceType type, double* slot) +{ + jsint i; + jsdouble d; + switch (type) { + case TT_OBJECT: + v = OBJECT_TO_JSVAL(*(JSObject**)slot); + JS_ASSERT(v != JSVAL_ERROR_COOKIE); /* don't leak JSVAL_ERROR_COOKIE */ + debug_only_printf(LC_TMTracer, + "object<%p:%s> ", (void*)JSVAL_TO_OBJECT(v), + JSVAL_IS_NULL(v) + ? "null" + : STOBJ_GET_CLASS(JSVAL_TO_OBJECT(v))->name); + break; + + case TT_INT32: + i = *(jsint*)slot; + debug_only_printf(LC_TMTracer, "int<%d> ", i); + store_int: + if (INT_FITS_IN_JSVAL(i)) { + v = INT_TO_JSVAL(i); + break; + } + d = (jsdouble)i; + goto store_double; + case TT_DOUBLE: + d = *slot; + debug_only_printf(LC_TMTracer, "double<%g> ", d); + if (JSDOUBLE_IS_INT(d, i)) + goto store_int; + store_double: { + /* + * It's not safe to trigger the GC here, so use an emergency heap if we + * are out of double boxes. + */ + if (cx->doubleFreeList) { +#ifdef DEBUG + JSBool ok = +#endif + js_NewDoubleInRootedValue(cx, d, &v); + JS_ASSERT(ok); + return true; + } + return E::handleDoubleOOM(cx, d, v); + } + + case TT_JSVAL: + v = *(jsval*)slot; + JS_ASSERT(v != JSVAL_ERROR_COOKIE); /* don't leak JSVAL_ERROR_COOKIE */ + debug_only_printf(LC_TMTracer, "box<%p> ", (void*)v); + break; + + case TT_STRING: + v = STRING_TO_JSVAL(*(JSString**)slot); + debug_only_printf(LC_TMTracer, "string<%p> ", (void*)(*(JSString**)slot)); + break; + + case TT_NULL: + JS_ASSERT(*(JSObject**)slot == NULL); + v = JSVAL_NULL; + debug_only_printf(LC_TMTracer, "null<%p> ", (void*)(*(JSObject**)slot)); + break; + + case TT_PSEUDOBOOLEAN: + /* Watch out for pseudo-booleans. */ + v = SPECIAL_TO_JSVAL(*(JSBool*)slot); + debug_only_printf(LC_TMTracer, "boolean<%d> ", *(JSBool*)slot); + break; + + case TT_FUNCTION: { + JS_ASSERT(HAS_FUNCTION_CLASS(*(JSObject**)slot)); + v = OBJECT_TO_JSVAL(*(JSObject**)slot); +#ifdef DEBUG + JSFunction* fun = GET_FUNCTION_PRIVATE(cx, JSVAL_TO_OBJECT(v)); + debug_only_printf(LC_TMTracer, + "function<%p:%s> ", (void*)JSVAL_TO_OBJECT(v), + fun->atom + ? JS_GetStringBytes(ATOM_TO_STRING(fun->atom)) + : "unnamed"); +#endif + break; + } + } + return true; +} + +struct ReserveDoubleOOMHandler { + static bool handleDoubleOOM(JSContext *cx, double d, jsval& v) { + v = AllocateDoubleFromReservedPool(cx); + JS_ASSERT(JSVAL_IS_DOUBLE(v) && *JSVAL_TO_DOUBLE(v) == 0.0); + *JSVAL_TO_DOUBLE(v) = d; + return true; + } +}; + +static void +NativeToValue(JSContext* cx, jsval& v, JSTraceType type, double* slot) +{ +#ifdef DEBUG + bool ok = +#endif + NativeToValueBase(cx, v, type, slot); + JS_ASSERT(ok); +} + +struct FailDoubleOOMHandler { + static bool handleDoubleOOM(JSContext *cx, double d, jsval& v) { + return false; + } +}; + +bool +js_NativeToValue(JSContext* cx, jsval& v, JSTraceType type, double* slot) +{ + return NativeToValueBase(cx, v, type, slot); +} + +class BuildNativeFrameVisitor : public SlotVisitorBase +{ + JSContext *mCx; + JSTraceType *mTypeMap; + double *mGlobal; + double *mStack; +public: + BuildNativeFrameVisitor(JSContext *cx, + JSTraceType *typemap, + double *global, + double *stack) : + mCx(cx), + mTypeMap(typemap), + mGlobal(global), + mStack(stack) + {} + + JS_REQUIRES_STACK JS_ALWAYS_INLINE void + visitGlobalSlot(jsval *vp, unsigned n, unsigned slot) { + debug_only_printf(LC_TMTracer, "global%d: ", n); + ValueToNative(mCx, *vp, *mTypeMap++, &mGlobal[slot]); + } + + JS_REQUIRES_STACK JS_ALWAYS_INLINE bool + visitStackSlots(jsval *vp, int count, JSStackFrame* fp) { + for (int i = 0; i < count; ++i) { + debug_only_printf(LC_TMTracer, "%s%d: ", stackSlotKind(), i); + ValueToNative(mCx, *vp++, *mTypeMap++, mStack++); + } + return true; + } +}; + +static JS_REQUIRES_STACK void +BuildNativeFrame(JSContext *cx, JSObject *globalObj, unsigned callDepth, + unsigned ngslots, uint16 *gslots, + JSTraceType *typeMap, double *global, double *stack) +{ + BuildNativeFrameVisitor visitor(cx, typeMap, global, stack); + VisitSlots(visitor, cx, globalObj, callDepth, ngslots, gslots); + debug_only_print0(LC_TMTracer, "\n"); +} + +class FlushNativeGlobalFrameVisitor : public SlotVisitorBase +{ + JSContext *mCx; + JSTraceType *mTypeMap; + double *mGlobal; +public: + FlushNativeGlobalFrameVisitor(JSContext *cx, + JSTraceType *typeMap, + double *global) : + mCx(cx), + mTypeMap(typeMap), + mGlobal(global) + {} + + JS_REQUIRES_STACK JS_ALWAYS_INLINE void + visitGlobalSlot(jsval *vp, unsigned n, unsigned slot) { + debug_only_printf(LC_TMTracer, "global%d=", n); + NativeToValue(mCx, *vp, *mTypeMap++, &mGlobal[slot]); + } +}; + +class FlushNativeStackFrameVisitor : public SlotVisitorBase +{ + JSContext *mCx; + JSTraceType *mTypeMap; + double *mStack; + jsval *mStop; +public: + FlushNativeStackFrameVisitor(JSContext *cx, + JSTraceType *typeMap, + double *stack, + jsval *stop) : + mCx(cx), + mTypeMap(typeMap), + mStack(stack), + mStop(stop) + {} + + JSTraceType* getTypeMap() + { + return mTypeMap; + } + + JS_REQUIRES_STACK JS_ALWAYS_INLINE bool + visitStackSlots(jsval *vp, size_t count, JSStackFrame* fp) { + for (size_t i = 0; i < count; ++i) { + if (vp == mStop) + return false; + debug_only_printf(LC_TMTracer, "%s%u=", stackSlotKind(), unsigned(i)); + NativeToValue(mCx, *vp++, *mTypeMap++, mStack++); + } + return true; + } +}; + +/* Box the given native frame into a JS frame. This is infallible. */ +static JS_REQUIRES_STACK void +FlushNativeGlobalFrame(JSContext *cx, double *global, unsigned ngslots, + uint16 *gslots, JSTraceType *typemap) +{ + FlushNativeGlobalFrameVisitor visitor(cx, typemap, global); + JSObject *globalObj = JS_GetGlobalForObject(cx, cx->fp->scopeChain); + VisitGlobalSlots(visitor, cx, globalObj, ngslots, gslots); + debug_only_print0(LC_TMTracer, "\n"); +} + +/* + * Returns the number of values on the native stack, excluding the innermost + * frame. This walks all FrameInfos on the native frame stack and sums the + * slot usage of each frame. + */ +static int32 +StackDepthFromCallStack(InterpState* state, uint32 callDepth) +{ + int32 nativeStackFramePos = 0; + + // Duplicate native stack layout computation: see VisitFrameSlots header comment. + for (FrameInfo** fip = state->callstackBase; fip < state->rp + callDepth; fip++) + nativeStackFramePos += (*fip)->callerHeight; + return nativeStackFramePos; +} + +/* + * Generic function to read upvars on trace from slots of active frames. + * T Traits type parameter. Must provide static functions: + * interp_get(fp, slot) Read the value out of an interpreter frame. + * native_slot(argc, slot) Return the position of the desired value in the on-trace + * stack frame (with position 0 being callee). + * + * upvarLevel Static level of the function containing the upvar definition + * slot Identifies the value to get. The meaning is defined by the traits type. + * callDepth Call depth of current point relative to trace entry + */ +template +inline JSTraceType +GetUpvarOnTrace(JSContext* cx, uint32 upvarLevel, int32 slot, uint32 callDepth, double* result) +{ + InterpState* state = cx->interpState; + FrameInfo** fip = state->rp + callDepth; + + /* + * First search the FrameInfo call stack for an entry containing our + * upvar, namely one with level == upvarLevel. The first FrameInfo is a + * transition from the entry frame to some callee. However, it is not + * known (from looking at the FrameInfo) whether the entry frame had a + * callee. Rather than special-case this or insert more logic into the + * loop, instead just stop before that FrameInfo (i.e. |> base| instead of + * |>= base|), and let the code after the loop handle it. + */ + int32 stackOffset = StackDepthFromCallStack(state, callDepth); + while (--fip > state->callstackBase) { + FrameInfo* fi = *fip; + + /* + * The loop starts aligned to the top of the stack, so move down to the first meaningful + * callee. Then read the callee directly from the frame. + */ + stackOffset -= fi->callerHeight; + JSObject* callee = *(JSObject**)(&state->stackBase[stackOffset]); + JSFunction* fun = GET_FUNCTION_PRIVATE(cx, callee); + uintN calleeLevel = fun->u.i.script->staticLevel; + if (calleeLevel == upvarLevel) { + /* + * Now find the upvar's value in the native stack. stackOffset is + * the offset of the start of the activation record corresponding + * to *fip in the native stack. + */ + uint32 native_slot = T::native_slot(fi->callerArgc, slot); + *result = state->stackBase[stackOffset + native_slot]; + return fi->get_typemap()[native_slot]; + } + } + + // Next search the trace entry frame, which is not in the FrameInfo stack. + if (state->outermostTree->script->staticLevel == upvarLevel) { + uint32 argc = ((VMFragment*) state->outermostTree->fragment)->argc; + uint32 native_slot = T::native_slot(argc, slot); + *result = state->stackBase[native_slot]; + return state->callstackBase[0]->get_typemap()[native_slot]; + } + + /* + * If we did not find the upvar in the frames for the active traces, + * then we simply get the value from the interpreter state. + */ + JS_ASSERT(upvarLevel < JS_DISPLAY_SIZE); + JSStackFrame* fp = cx->display[upvarLevel]; + jsval v = T::interp_get(fp, slot); + JSTraceType type = getCoercedType(v); + ValueToNative(cx, v, type, result); + return type; +} + +// For this traits type, 'slot' is the argument index, which may be -2 for callee. +struct UpvarArgTraits { + static jsval interp_get(JSStackFrame* fp, int32 slot) { + return fp->argv[slot]; + } + + static uint32 native_slot(uint32 argc, int32 slot) { + return 2 /*callee,this*/ + slot; + } +}; + +uint32 JS_FASTCALL +GetUpvarArgOnTrace(JSContext* cx, uint32 upvarLevel, int32 slot, uint32 callDepth, double* result) +{ + return GetUpvarOnTrace(cx, upvarLevel, slot, callDepth, result); +} + +// For this traits type, 'slot' is an index into the local slots array. +struct UpvarVarTraits { + static jsval interp_get(JSStackFrame* fp, int32 slot) { + return fp->slots[slot]; + } + + static uint32 native_slot(uint32 argc, int32 slot) { + return 3 /*callee,this,arguments*/ + argc + slot; + } +}; + +uint32 JS_FASTCALL +GetUpvarVarOnTrace(JSContext* cx, uint32 upvarLevel, int32 slot, uint32 callDepth, double* result) +{ + return GetUpvarOnTrace(cx, upvarLevel, slot, callDepth, result); +} + +/* + * For this traits type, 'slot' is an index into the stack area (within slots, + * after nfixed) of a frame with no function. (On trace, the top-level frame is + * the only one that can have no function.) + */ +struct UpvarStackTraits { + static jsval interp_get(JSStackFrame* fp, int32 slot) { + return fp->slots[slot + fp->script->nfixed]; + } + + static uint32 native_slot(uint32 argc, int32 slot) { + /* + * Locals are not imported by the tracer when the frame has no + * function, so we do not add fp->script->nfixed. + */ + JS_ASSERT(argc == 0); + return slot; + } +}; + +uint32 JS_FASTCALL +GetUpvarStackOnTrace(JSContext* cx, uint32 upvarLevel, int32 slot, uint32 callDepth, + double* result) +{ + return GetUpvarOnTrace(cx, upvarLevel, slot, callDepth, result); +} + +// Parameters needed to access a value from a closure on trace. +struct ClosureVarInfo +{ + jsid id; + uint32 slot; + uint32 callDepth; + uint32 resolveFlags; +}; + +/* + * Generic function to read upvars from Call objects of active heavyweight functions. + * call Callee Function object in which the upvar is accessed. + */ +template +inline uint32 +GetFromClosure(JSContext* cx, JSObject* call, const ClosureVarInfo* cv, double* result) +{ + JS_ASSERT(OBJ_GET_CLASS(cx, call) == &js_CallClass); + + InterpState* state = cx->interpState; + +#ifdef DEBUG + int32 stackOffset = StackDepthFromCallStack(state, cv->callDepth); + FrameInfo** fip = state->rp + cv->callDepth; + while (--fip > state->callstackBase) { + FrameInfo* fi = *fip; + JSObject* callee = *(JSObject**)(&state->stackBase[stackOffset]); + if (callee == call) { + // This is not reachable as long as JSOP_LAMBDA is not traced: + // - The upvar is found at this point only if the upvar was defined on a frame that was + // entered on this trace. + // - The upvar definition must be (dynamically, and thus on trace) before the definition + // of the function that uses the upvar. + // - Therefore, if the upvar is found at this point, the function definition JSOP_LAMBDA + // is on the trace. + JS_NOT_REACHED("JSOP_NAME variable found in outer trace"); + } + stackOffset -= fi->callerHeight; + } +#endif + + /* + * Here we specifically want to check the call object of the trace entry frame. + */ + uint32 slot = cv->slot; + VOUCH_DOES_NOT_REQUIRE_STACK(); + if (cx->fp->callobj == call) { + slot = T::adj_slot(cx->fp, slot); + *result = state->stackBase[slot]; + return state->callstackBase[0]->get_typemap()[slot]; + } + + JSStackFrame* fp = (JSStackFrame*) call->getPrivate(); + jsval v; + if (fp) { + v = T::slots(fp)[slot]; + } else { + JS_ASSERT(cv->resolveFlags != JSRESOLVE_INFER); + JSAutoResolveFlags rf(cx, cv->resolveFlags); +#ifdef DEBUG + JSBool rv = +#endif + js_GetPropertyHelper(cx, call, cv->id, JS_FALSE, &v); + JS_ASSERT(rv); + } + JSTraceType type = getCoercedType(v); + ValueToNative(cx, v, type, result); + return type; +} + +struct ArgClosureTraits +{ + // See also UpvarArgTraits. + static inline uint32 adj_slot(JSStackFrame* fp, uint32 slot) { return 2 + slot; } + + // Generate the adj_slot computation in LIR. + static inline LIns* adj_slot_lir(LirWriter* lir, LIns* fp_ins, unsigned slot) { + return lir->insImm(2 + slot); + } + + // See also UpvarArgTraits. + static inline jsval* slots(JSStackFrame* fp) { return fp->argv; } +private: + ArgClosureTraits(); +}; + +uint32 JS_FASTCALL +GetClosureArg(JSContext* cx, JSObject* callee, const ClosureVarInfo* cv, double* result) +{ + return GetFromClosure(cx, callee, cv, result); +} + +struct VarClosureTraits +{ + // See also UpvarVarTraits. + static inline uint32 adj_slot(JSStackFrame* fp, uint32 slot) { return 3 + fp->argc + slot; } + + // See also UpvarVarTraits. + static inline LIns* adj_slot_lir(LirWriter* lir, LIns* fp_ins, unsigned slot) { + LIns *argc_ins = lir->insLoad(LIR_ld, fp_ins, offsetof(JSStackFrame, argc)); + return lir->ins2(LIR_add, lir->insImm(3 + slot), argc_ins); + } + + // See also UpvarVarTraits. + static inline jsval* slots(JSStackFrame* fp) { return fp->slots; } +private: + VarClosureTraits(); +}; + +uint32 JS_FASTCALL +GetClosureVar(JSContext* cx, JSObject* callee, const ClosureVarInfo* cv, double* result) +{ + return GetFromClosure(cx, callee, cv, result); +} + +/** + * Box the given native stack frame into the virtual machine stack. This + * is infallible. + * + * @param callDepth the distance between the entry frame into our trace and + * cx->fp when we make this call. If this is not called as a + * result of a nested exit, callDepth is 0. + * @param mp an array of JSTraceTypes that indicate what the types of the things + * on the stack are. + * @param np pointer to the native stack. We want to copy values from here to + * the JS stack as needed. + * @param stopFrame if non-null, this frame and everything above it should not + * be restored. + * @return the number of things we popped off of np. + */ +static JS_REQUIRES_STACK int +FlushNativeStackFrame(JSContext* cx, unsigned callDepth, JSTraceType* mp, double* np, + JSStackFrame* stopFrame) +{ + jsval* stopAt = stopFrame ? &stopFrame->argv[-2] : NULL; + + /* Root all string and object references first (we don't need to call the GC for this). */ + FlushNativeStackFrameVisitor visitor(cx, mp, np, stopAt); + VisitStackSlots(visitor, cx, callDepth); + + // Restore thisp from the now-restored argv[-1] in each pending frame. + // Keep in mind that we didn't restore frames at stopFrame and above! + // Scope to keep |fp| from leaking into the macros we're using. + { + unsigned n = callDepth+1; // +1 to make sure we restore the entry frame + JSStackFrame* fp = cx->fp; + if (stopFrame) { + for (; fp != stopFrame; fp = fp->down) { + JS_ASSERT(n != 0); + --n; + } + + // Skip over stopFrame itself. + JS_ASSERT(n != 0); + --n; + fp = fp->down; + } + for (; n != 0; fp = fp->down) { + --n; + if (fp->argv) { + if (fp->argsobj && + js_GetArgsPrivateNative(JSVAL_TO_OBJECT(fp->argsobj))) { + JSVAL_TO_OBJECT(fp->argsobj)->setPrivate(fp); + } + + /* + * We might return from trace with a different callee object, but it still + * has to be the same JSFunction (FIXME: bug 471425, eliminate fp->callee). + */ + JS_ASSERT(JSVAL_IS_OBJECT(fp->argv[-1])); + JS_ASSERT(HAS_FUNCTION_CLASS(JSVAL_TO_OBJECT(fp->argv[-2]))); + JS_ASSERT(GET_FUNCTION_PRIVATE(cx, JSVAL_TO_OBJECT(fp->argv[-2])) == + GET_FUNCTION_PRIVATE(cx, fp->callee())); + JS_ASSERT(GET_FUNCTION_PRIVATE(cx, fp->callee()) == fp->fun); + + /* + * SynthesizeFrame sets scopeChain to NULL, because we can't calculate the + * correct scope chain until we have the final callee. Calculate the real + * scope object here. + */ + if (!fp->scopeChain) { + fp->scopeChain = OBJ_GET_PARENT(cx, JSVAL_TO_OBJECT(fp->argv[-2])); + if (fp->fun->flags & JSFUN_HEAVYWEIGHT) { + /* + * Set hookData to null because the failure case for js_GetCallObject + * involves it calling the debugger hook. + * + * Allocating the Call object must not fail, so use an object + * previously reserved by ExecuteTree if needed. + */ + void* hookData = ((JSInlineFrame*)fp)->hookData; + ((JSInlineFrame*)fp)->hookData = NULL; + JS_ASSERT(!JS_TRACE_MONITOR(cx).useReservedObjects); + JS_TRACE_MONITOR(cx).useReservedObjects = JS_TRUE; +#ifdef DEBUG + JSObject *obj = +#endif + js_GetCallObject(cx, fp); + JS_ASSERT(obj); + JS_TRACE_MONITOR(cx).useReservedObjects = JS_FALSE; + ((JSInlineFrame*)fp)->hookData = hookData; + } + } + fp->thisp = JSVAL_TO_OBJECT(fp->argv[-1]); + if (fp->flags & JSFRAME_CONSTRUCTING) // constructors always compute 'this' + fp->flags |= JSFRAME_COMPUTED_THIS; + } + } + } + debug_only_print0(LC_TMTracer, "\n"); + return visitor.getTypeMap() - mp; +} + +/* Emit load instructions onto the trace that read the initial stack state. */ +JS_REQUIRES_STACK void +TraceRecorder::import(LIns* base, ptrdiff_t offset, jsval* p, JSTraceType t, + const char *prefix, uintN index, JSStackFrame *fp) +{ + LIns* ins; + if (t == TT_INT32) { /* demoted */ + JS_ASSERT(isInt32(*p)); + + /* + * Ok, we have a valid demotion attempt pending, so insert an integer + * read and promote it to double since all arithmetic operations expect + * to see doubles on entry. The first op to use this slot will emit a + * f2i cast which will cancel out the i2f we insert here. + */ + ins = lir->insLoad(LIR_ld, base, offset); + ins = lir->ins1(LIR_i2f, ins); + } else { + JS_ASSERT_IF(t != TT_JSVAL, isNumber(*p) == (t == TT_DOUBLE)); + if (t == TT_DOUBLE) { + ins = lir->insLoad(LIR_ldq, base, offset); + } else if (t == TT_PSEUDOBOOLEAN) { + ins = lir->insLoad(LIR_ld, base, offset); + } else { + ins = lir->insLoad(LIR_ldp, base, offset); + } + } + checkForGlobalObjectReallocation(); + tracker.set(p, ins); + +#ifdef DEBUG + char name[64]; + JS_ASSERT(strlen(prefix) < 10); + void* mark = NULL; + jsuword* localNames = NULL; + const char* funName = NULL; + if (*prefix == 'a' || *prefix == 'v') { + mark = JS_ARENA_MARK(&cx->tempPool); + if (fp->fun->hasLocalNames()) + localNames = js_GetLocalNameArray(cx, fp->fun, &cx->tempPool); + funName = fp->fun->atom ? js_AtomToPrintableString(cx, fp->fun->atom) : ""; + } + if (!strcmp(prefix, "argv")) { + if (index < fp->fun->nargs) { + JSAtom *atom = JS_LOCAL_NAME_TO_ATOM(localNames[index]); + JS_snprintf(name, sizeof name, "$%s.%s", funName, js_AtomToPrintableString(cx, atom)); + } else { + JS_snprintf(name, sizeof name, "$%s.", funName, index); + } + } else if (!strcmp(prefix, "vars")) { + JSAtom *atom = JS_LOCAL_NAME_TO_ATOM(localNames[fp->fun->nargs + index]); + JS_snprintf(name, sizeof name, "$%s.%s", funName, js_AtomToPrintableString(cx, atom)); + } else { + JS_snprintf(name, sizeof name, "$%s%d", prefix, index); + } + + if (mark) + JS_ARENA_RELEASE(&cx->tempPool, mark); + addName(ins, name); + + static const char* typestr[] = { + "object", "int", "double", "jsval", "string", "null", "boolean", "function" + }; + debug_only_printf(LC_TMTracer, "import vp=%p name=%s type=%s flags=%d\n", + (void*)p, name, typestr[t & 7], t >> 3); +#endif +} + +class ImportGlobalSlotVisitor : public SlotVisitorBase +{ + TraceRecorder &mRecorder; + LIns *mBase; + JSTraceType *mTypemap; +public: + ImportGlobalSlotVisitor(TraceRecorder &recorder, + LIns *base, + JSTraceType *typemap) : + mRecorder(recorder), + mBase(base), + mTypemap(typemap) + {} + + JS_REQUIRES_STACK JS_ALWAYS_INLINE void + visitGlobalSlot(jsval *vp, unsigned n, unsigned slot) { + JS_ASSERT(*mTypemap != TT_JSVAL); + mRecorder.import(mBase, mRecorder.nativeGlobalOffset(vp), + vp, *mTypemap++, "global", n, NULL); + } +}; + +class ImportBoxedStackSlotVisitor : public SlotVisitorBase +{ + TraceRecorder &mRecorder; + LIns *mBase; + ptrdiff_t mStackOffset; + JSTraceType *mTypemap; + JSStackFrame *mFp; +public: + ImportBoxedStackSlotVisitor(TraceRecorder &recorder, + LIns *base, + ptrdiff_t stackOffset, + JSTraceType *typemap) : + mRecorder(recorder), + mBase(base), + mStackOffset(stackOffset), + mTypemap(typemap) + {} + + JS_REQUIRES_STACK JS_ALWAYS_INLINE bool + visitStackSlots(jsval *vp, size_t count, JSStackFrame* fp) { + for (size_t i = 0; i < count; ++i) { + if (*mTypemap == TT_JSVAL) { + mRecorder.import(mBase, mStackOffset, vp, TT_JSVAL, + "jsval", i, fp); + LIns *vp_ins = mRecorder.unbox_jsval(*vp, mRecorder.get(vp), + mRecorder.copy(mRecorder.anchor)); + mRecorder.set(vp, vp_ins); + } + vp++; + mTypemap++; + mStackOffset += sizeof(double); + } + return true; + } +}; + +class ImportUnboxedStackSlotVisitor : public SlotVisitorBase +{ + TraceRecorder &mRecorder; + LIns *mBase; + ptrdiff_t mStackOffset; + JSTraceType *mTypemap; + JSStackFrame *mFp; +public: + ImportUnboxedStackSlotVisitor(TraceRecorder &recorder, + LIns *base, + ptrdiff_t stackOffset, + JSTraceType *typemap) : + mRecorder(recorder), + mBase(base), + mStackOffset(stackOffset), + mTypemap(typemap) + {} + + JS_REQUIRES_STACK JS_ALWAYS_INLINE bool + visitStackSlots(jsval *vp, size_t count, JSStackFrame* fp) { + for (size_t i = 0; i < count; ++i) { + if (*mTypemap != TT_JSVAL) { + mRecorder.import(mBase, mStackOffset, vp++, *mTypemap, + stackSlotKind(), i, fp); + } + mTypemap++; + mStackOffset += sizeof(double); + } + return true; + } +}; + +// Like ImportUnboxedStackSlotVisitor, except that this does not import +// slots past nfixed. It imports only the slots that belong totally to +// the given frame. +class ImportUnboxedFrameSlotVisitor : public ImportUnboxedStackSlotVisitor +{ +public: + ImportUnboxedFrameSlotVisitor(TraceRecorder &recorder, + LIns *base, + ptrdiff_t stackOffset, + JSTraceType *typemap) : + ImportUnboxedStackSlotVisitor(recorder, base, stackOffset, typemap) + {} + + JS_REQUIRES_STACK JS_ALWAYS_INLINE bool + visitStackSlots(jsval *vp, size_t count, JSStackFrame* fp) { + if (vp == &fp->slots[fp->script->nfixed]) + return false; + return ImportUnboxedStackSlotVisitor::visitStackSlots(vp, count, fp); + } +}; + +JS_REQUIRES_STACK void +TraceRecorder::import(TreeInfo* treeInfo, LIns* sp, unsigned stackSlots, unsigned ngslots, + unsigned callDepth, JSTraceType* typeMap) +{ + /* + * If we get a partial list that doesn't have all the types (i.e. recording + * from a side exit that was recorded but we added more global slots + * later), merge the missing types from the entry type map. This is safe + * because at the loop edge we verify that we have compatible types for all + * globals (entry type and loop edge type match). While a different trace + * of the tree might have had a guard with a different type map for these + * slots we just filled in here (the guard we continue from didn't know + * about them), since we didn't take that particular guard the only way we + * could have ended up here is if that other trace had at its end a + * compatible type distribution with the entry map. Since that's exactly + * what we used to fill in the types our current side exit didn't provide, + * this is always safe to do. + */ + + JSTraceType* globalTypeMap = typeMap + stackSlots; + unsigned length = treeInfo->nGlobalTypes(); + + /* + * This is potentially the typemap of the side exit and thus shorter than + * the tree's global type map. + */ + if (ngslots < length) { + MergeTypeMaps(&globalTypeMap /* out param */, &ngslots /* out param */, + treeInfo->globalTypeMap(), length, + (JSTraceType*)alloca(sizeof(JSTraceType) * length)); + } + JS_ASSERT(ngslots == treeInfo->nGlobalTypes()); + ptrdiff_t offset = -treeInfo->nativeStackBase; + + /* + * Check whether there are any values on the stack we have to unbox and do + * that first before we waste any time fetching the state from the stack. + */ + ImportBoxedStackSlotVisitor boxedStackVisitor(*this, sp, offset, typeMap); + VisitStackSlots(boxedStackVisitor, cx, callDepth); + + ImportGlobalSlotVisitor globalVisitor(*this, lirbuf->state, globalTypeMap); + VisitGlobalSlots(globalVisitor, cx, globalObj, ngslots, + treeInfo->globalSlots->data()); + + ImportUnboxedStackSlotVisitor unboxedStackVisitor(*this, sp, offset, + typeMap); + VisitStackSlots(unboxedStackVisitor, cx, callDepth); +} + +JS_REQUIRES_STACK bool +TraceRecorder::isValidSlot(JSScope* scope, JSScopeProperty* sprop) +{ + uint32 setflags = (js_CodeSpec[*cx->fp->regs->pc].format & (JOF_SET | JOF_INCDEC | JOF_FOR)); + + if (setflags) { + if (!SPROP_HAS_STUB_SETTER(sprop)) + ABORT_TRACE_RV("non-stub setter", false); + if (sprop->attrs & JSPROP_READONLY) + ABORT_TRACE_RV("writing to a read-only property", false); + } + + /* This check applies even when setflags == 0. */ + if (setflags != JOF_SET && !SPROP_HAS_STUB_GETTER(sprop)) + ABORT_TRACE_RV("non-stub getter", false); + + if (!SPROP_HAS_VALID_SLOT(sprop, scope)) + ABORT_TRACE_RV("slotless obj property", false); + + return true; +} + +/* Lazily import a global slot if we don't already have it in the tracker. */ +JS_REQUIRES_STACK bool +TraceRecorder::lazilyImportGlobalSlot(unsigned slot) +{ + if (slot != uint16(slot)) /* we use a table of 16-bit ints, bail out if that's not enough */ + return false; + + /* + * If the global object grows too large, alloca in ExecuteTree might fail, + * so abort tracing on global objects with unreasonably many slots. + */ + if (STOBJ_NSLOTS(globalObj) > MAX_GLOBAL_SLOTS) + return false; + jsval* vp = &STOBJ_GET_SLOT(globalObj, slot); + if (known(vp)) + return true; /* we already have it */ + unsigned index = treeInfo->globalSlots->length(); + + /* Add the slot to the list of interned global slots. */ + JS_ASSERT(treeInfo->nGlobalTypes() == treeInfo->globalSlots->length()); + treeInfo->globalSlots->add(slot); + JSTraceType type = getCoercedType(*vp); + if (type == TT_INT32 && oracle.isGlobalSlotUndemotable(cx, slot)) + type = TT_DOUBLE; + treeInfo->typeMap.add(type); + import(lirbuf->state, sizeof(struct InterpState) + slot*sizeof(double), + vp, type, "global", index, NULL); + SpecializeTreesToMissingGlobals(cx, globalObj, treeInfo); + return true; +} + +/* Write back a value onto the stack or global frames. */ +LIns* +TraceRecorder::writeBack(LIns* i, LIns* base, ptrdiff_t offset, bool demote) +{ + /* + * Sink all type casts targeting the stack into the side exit by simply storing the original + * (uncasted) value. Each guard generates the side exit map based on the types of the + * last stores to every stack location, so it's safe to not perform them on-trace. + */ + if (demote && isPromoteInt(i)) + i = ::demote(lir, i); + return lir->insStorei(i, base, offset); +} + +/* Update the tracker, then issue a write back store. */ +JS_REQUIRES_STACK void +TraceRecorder::set(jsval* p, LIns* i, bool initializing, bool demote) +{ + JS_ASSERT(i != NULL); + JS_ASSERT(initializing || known(p)); + checkForGlobalObjectReallocation(); + tracker.set(p, i); + + /* + * If we are writing to this location for the first time, calculate the + * offset into the native frame manually. Otherwise just look up the last + * load or store associated with the same source address (p) and use the + * same offset/base. + */ + LIns* x = nativeFrameTracker.get(p); + if (!x) { + if (isGlobal(p)) + x = writeBack(i, lirbuf->state, nativeGlobalOffset(p), demote); + else + x = writeBack(i, lirbuf->sp, -treeInfo->nativeStackBase + nativeStackOffset(p), demote); + nativeFrameTracker.set(p, x); + } else { + JS_ASSERT(x->isop(LIR_sti) || x->isop(LIR_stqi)); + + int disp; + LIns *base = x->oprnd2(); +#ifdef NANOJIT_ARM + if (base->isop(LIR_piadd)) { + disp = base->oprnd2()->imm32(); + base = base->oprnd1(); + } else +#endif + disp = x->disp(); + + JS_ASSERT(base == lirbuf->sp || base == lirbuf->state); + JS_ASSERT(disp == ((base == lirbuf->sp) ? + -treeInfo->nativeStackBase + nativeStackOffset(p) : + nativeGlobalOffset(p))); + + writeBack(i, base, disp, demote); + } +} + +JS_REQUIRES_STACK LIns* +TraceRecorder::get(jsval* p) +{ + JS_ASSERT(known(p)); + checkForGlobalObjectReallocation(); + return tracker.get(p); +} + +JS_REQUIRES_STACK LIns* +TraceRecorder::addr(jsval* p) +{ + return isGlobal(p) + ? lir->ins2(LIR_piadd, lirbuf->state, INS_CONSTWORD(nativeGlobalOffset(p))) + : lir->ins2(LIR_piadd, lirbuf->sp, + INS_CONSTWORD(-treeInfo->nativeStackBase + nativeStackOffset(p))); +} + +JS_REQUIRES_STACK bool +TraceRecorder::known(jsval* p) +{ + checkForGlobalObjectReallocation(); + return tracker.has(p); +} + +/* + * The dslots of the global object are sometimes reallocated by the interpreter. + * This function check for that condition and re-maps the entries of the tracker + * accordingly. + */ +JS_REQUIRES_STACK void +TraceRecorder::checkForGlobalObjectReallocation() +{ + if (global_dslots != globalObj->dslots) { + debug_only_print0(LC_TMTracer, + "globalObj->dslots relocated, updating tracker\n"); + jsval* src = global_dslots; + jsval* dst = globalObj->dslots; + jsuint length = globalObj->dslots[-1] - JS_INITIAL_NSLOTS; + LIns** map = (LIns**)alloca(sizeof(LIns*) * length); + for (jsuint n = 0; n < length; ++n) { + map[n] = tracker.get(src); + tracker.set(src++, NULL); + } + for (jsuint n = 0; n < length; ++n) + tracker.set(dst++, map[n]); + global_dslots = globalObj->dslots; + } +} + +/* Determine whether the current branch is a loop edge (taken or not taken). */ +static JS_REQUIRES_STACK bool +IsLoopEdge(jsbytecode* pc, jsbytecode* header) +{ + switch (*pc) { + case JSOP_IFEQ: + case JSOP_IFNE: + return ((pc + GET_JUMP_OFFSET(pc)) == header); + case JSOP_IFEQX: + case JSOP_IFNEX: + return ((pc + GET_JUMPX_OFFSET(pc)) == header); + default: + JS_ASSERT((*pc == JSOP_AND) || (*pc == JSOP_ANDX) || + (*pc == JSOP_OR) || (*pc == JSOP_ORX)); + } + return false; +} + +class AdjustCallerGlobalTypesVisitor : public SlotVisitorBase +{ + TraceRecorder &mRecorder; + JSContext *mCx; + nanojit::LirBuffer *mLirbuf; + nanojit::LirWriter *mLir; + JSTraceType *mTypeMap; +public: + AdjustCallerGlobalTypesVisitor(TraceRecorder &recorder, + JSTraceType *typeMap) : + mRecorder(recorder), + mCx(mRecorder.cx), + mLirbuf(mRecorder.lirbuf), + mLir(mRecorder.lir), + mTypeMap(typeMap) + {} + + JSTraceType* getTypeMap() + { + return mTypeMap; + } + + JS_REQUIRES_STACK JS_ALWAYS_INLINE void + visitGlobalSlot(jsval *vp, unsigned n, unsigned slot) { + LIns *ins = mRecorder.get(vp); + bool isPromote = isPromoteInt(ins); + if (isPromote && *mTypeMap == TT_DOUBLE) { + mLir->insStorei(mRecorder.get(vp), mLirbuf->state, + mRecorder.nativeGlobalOffset(vp)); + + /* + * Aggressively undo speculation so the inner tree will compile + * if this fails. + */ + oracle.markGlobalSlotUndemotable(mCx, slot); + } + JS_ASSERT(!(!isPromote && *mTypeMap == TT_INT32)); + ++mTypeMap; + } +}; + +class AdjustCallerStackTypesVisitor : public SlotVisitorBase +{ + TraceRecorder &mRecorder; + JSContext *mCx; + nanojit::LirBuffer *mLirbuf; + nanojit::LirWriter *mLir; + unsigned mSlotnum; + JSTraceType *mTypeMap; +public: + AdjustCallerStackTypesVisitor(TraceRecorder &recorder, + JSTraceType *typeMap) : + mRecorder(recorder), + mCx(mRecorder.cx), + mLirbuf(mRecorder.lirbuf), + mLir(mRecorder.lir), + mSlotnum(0), + mTypeMap(typeMap) + {} + + JSTraceType* getTypeMap() + { + return mTypeMap; + } + + JS_REQUIRES_STACK JS_ALWAYS_INLINE bool + visitStackSlots(jsval *vp, size_t count, JSStackFrame* fp) { + for (size_t i = 0; i < count; ++i) { + LIns *ins = mRecorder.get(vp); + bool isPromote = isPromoteInt(ins); + if (isPromote && *mTypeMap == TT_DOUBLE) { + mLir->insStorei(mRecorder.get(vp), mLirbuf->sp, + -mRecorder.treeInfo->nativeStackBase + + mRecorder.nativeStackOffset(vp)); + + /* + * Aggressively undo speculation so the inner tree will compile + * if this fails. + */ + oracle.markStackSlotUndemotable(mCx, mSlotnum); + } + JS_ASSERT(!(!isPromote && *mTypeMap == TT_INT32)); + ++vp; + ++mTypeMap; + ++mSlotnum; + } + return true; + } +}; + +/* + * Promote slots if necessary to match the called tree's type map. This + * function is infallible and must only be called if we are certain that it is + * possible to reconcile the types for each slot in the inner and outer trees. + */ +JS_REQUIRES_STACK void +TraceRecorder::adjustCallerTypes(Fragment* f) +{ + TreeInfo* ti = (TreeInfo*)f->vmprivate; + + AdjustCallerGlobalTypesVisitor globalVisitor(*this, ti->globalTypeMap()); + VisitGlobalSlots(globalVisitor, cx, *treeInfo->globalSlots); + + AdjustCallerStackTypesVisitor stackVisitor(*this, ti->stackTypeMap()); + VisitStackSlots(stackVisitor, cx, 0); + + JS_ASSERT(f == f->root); +} + +JS_REQUIRES_STACK JSTraceType +TraceRecorder::determineSlotType(jsval* vp) +{ + JSTraceType m; + LIns* i = get(vp); + if (isNumber(*vp)) { + m = isPromoteInt(i) ? TT_INT32 : TT_DOUBLE; + } else if (JSVAL_IS_OBJECT(*vp)) { + if (JSVAL_IS_NULL(*vp)) + m = TT_NULL; + else if (HAS_FUNCTION_CLASS(JSVAL_TO_OBJECT(*vp))) + m = TT_FUNCTION; + else + m = TT_OBJECT; + } else { + JS_ASSERT(JSVAL_TAG(*vp) == JSVAL_STRING || JSVAL_IS_SPECIAL(*vp)); + JS_STATIC_ASSERT(static_cast(TT_STRING) == JSVAL_STRING); + JS_STATIC_ASSERT(static_cast(TT_PSEUDOBOOLEAN) == JSVAL_SPECIAL); + m = JSTraceType(JSVAL_TAG(*vp)); + } + JS_ASSERT(m != TT_INT32 || isInt32(*vp)); + return m; +} + +class DetermineTypesVisitor : public SlotVisitorBase +{ + TraceRecorder &mRecorder; + JSTraceType *mTypeMap; +public: + DetermineTypesVisitor(TraceRecorder &recorder, + JSTraceType *typeMap) : + mRecorder(recorder), + mTypeMap(typeMap) + {} + + JS_REQUIRES_STACK JS_ALWAYS_INLINE void + visitGlobalSlot(jsval *vp, unsigned n, unsigned slot) { + *mTypeMap++ = mRecorder.determineSlotType(vp); + } + + JS_REQUIRES_STACK JS_ALWAYS_INLINE bool + visitStackSlots(jsval *vp, size_t count, JSStackFrame* fp) { + for (size_t i = 0; i < count; ++i) + *mTypeMap++ = mRecorder.determineSlotType(vp++); + return true; + } + + JSTraceType* getTypeMap() + { + return mTypeMap; + } +}; + +#if defined JS_JIT_SPEW +JS_REQUIRES_STACK static void +TreevisLogExit(JSContext* cx, VMSideExit* exit) +{ + debug_only_printf(LC_TMTreeVis, "TREEVIS ADDEXIT EXIT=%p TYPE=%s FRAG=%p PC=%p FILE=\"%s\"" + " LINE=%d OFFS=%d", (void*)exit, getExitName(exit->exitType), + (void*)exit->from, (void*)cx->fp->regs->pc, cx->fp->script->filename, + js_FramePCToLineNumber(cx, cx->fp), FramePCOffset(cx->fp)); + debug_only_print0(LC_TMTreeVis, " STACK=\""); + for (unsigned i = 0; i < exit->numStackSlots; i++) + debug_only_printf(LC_TMTreeVis, "%c", typeChar[exit->stackTypeMap()[i]]); + debug_only_print0(LC_TMTreeVis, "\" GLOBALS=\""); + for (unsigned i = 0; i < exit->numGlobalSlots; i++) + debug_only_printf(LC_TMTreeVis, "%c", typeChar[exit->globalTypeMap()[i]]); + debug_only_print0(LC_TMTreeVis, "\"\n"); +} +#endif + +JS_REQUIRES_STACK VMSideExit* +TraceRecorder::snapshot(ExitType exitType) +{ + JSStackFrame* fp = cx->fp; + JSFrameRegs* regs = fp->regs; + jsbytecode* pc = regs->pc; + + /* + * Check for a return-value opcode that needs to restart at the next + * instruction. + */ + const JSCodeSpec& cs = js_CodeSpec[*pc]; + + /* + * When calling a _FAIL native, make the snapshot's pc point to the next + * instruction after the CALL or APPLY. Even on failure, a _FAIL native + * must not be called again from the interpreter. + */ + bool resumeAfter = (pendingSpecializedNative && + JSTN_ERRTYPE(pendingSpecializedNative) == FAIL_STATUS); + if (resumeAfter) { + JS_ASSERT(*pc == JSOP_CALL || *pc == JSOP_APPLY || *pc == JSOP_NEW || + *pc == JSOP_SETPROP || *pc == JSOP_SETNAME); + pc += cs.length; + regs->pc = pc; + MUST_FLOW_THROUGH("restore_pc"); + } + + /* + * Generate the entry map for the (possibly advanced) pc and stash it in + * the trace. + */ + unsigned stackSlots = NativeStackSlots(cx, callDepth); + + /* + * It's sufficient to track the native stack use here since all stores + * above the stack watermark defined by guards are killed. + */ + trackNativeStackUse(stackSlots + 1); + + /* Capture the type map into a temporary location. */ + unsigned ngslots = treeInfo->globalSlots->length(); + unsigned typemap_size = (stackSlots + ngslots) * sizeof(JSTraceType); + + /* Use the recorder-local temporary type map. */ + JSTraceType* typemap = NULL; + if (tempTypeMap.resize(typemap_size)) + typemap = tempTypeMap.begin(); /* crash if resize() fails. */ + + /* + * Determine the type of a store by looking at the current type of the + * actual value the interpreter is using. For numbers we have to check what + * kind of store we used last (integer or double) to figure out what the + * side exit show reflect in its typemap. + */ + DetermineTypesVisitor detVisitor(*this, typemap); + VisitSlots(detVisitor, cx, callDepth, ngslots, + treeInfo->globalSlots->data()); + JS_ASSERT(unsigned(detVisitor.getTypeMap() - typemap) == + ngslots + stackSlots); + + /* + * If this snapshot is for a side exit that leaves a boxed jsval result on + * the stack, make a note of this in the typemap. Examples include the + * builtinStatus guard after calling a _FAIL builtin, a JSFastNative, or + * GetPropertyByName; and the type guard in unbox_jsval after such a call + * (also at the beginning of a trace branched from such a type guard). + */ + if (pendingUnboxSlot || + (pendingSpecializedNative && (pendingSpecializedNative->flags & JSTN_UNBOX_AFTER))) { + unsigned pos = stackSlots - 1; + if (pendingUnboxSlot == cx->fp->regs->sp - 2) + pos = stackSlots - 2; + typemap[pos] = TT_JSVAL; + } + + /* Now restore the the original pc (after which early returns are ok). */ + if (resumeAfter) { + MUST_FLOW_LABEL(restore_pc); + regs->pc = pc - cs.length; + } else { + /* + * If we take a snapshot on a goto, advance to the target address. This + * avoids inner trees returning on a break goto, which the outer + * recorder then would confuse with a break in the outer tree. + */ + if (*pc == JSOP_GOTO) + pc += GET_JUMP_OFFSET(pc); + else if (*pc == JSOP_GOTOX) + pc += GET_JUMPX_OFFSET(pc); + } + + /* + * Check if we already have a matching side exit; if so we can return that + * side exit instead of creating a new one. + */ + VMSideExit** exits = treeInfo->sideExits.data(); + unsigned nexits = treeInfo->sideExits.length(); + if (exitType == LOOP_EXIT) { + for (unsigned n = 0; n < nexits; ++n) { + VMSideExit* e = exits[n]; + if (e->pc == pc && e->imacpc == fp->imacpc && + ngslots == e->numGlobalSlots && + !memcmp(exits[n]->fullTypeMap(), typemap, typemap_size)) { + AUDIT(mergedLoopExits); +#if defined JS_JIT_SPEW + TreevisLogExit(cx, e); +#endif + return e; + } + } + } + + if (sizeof(VMSideExit) + (stackSlots + ngslots) * sizeof(JSTraceType) > + LirBuffer::MAX_SKIP_PAYLOAD_SZB) { + /* + * ::snapshot() is infallible in the sense that callers don't + * expect errors; but this is a trace-aborting error condition. So + * mangle the request to consume zero slots, and mark the tree as + * to-be-trashed. This should be safe as the trace will be aborted + * before assembly or execution due to the call to + * trackNativeStackUse above. + */ + stackSlots = 0; + ngslots = 0; + typemap_size = 0; + trashSelf = true; + } + + /* We couldn't find a matching side exit, so create a new one. */ + VMSideExit* exit = (VMSideExit*) + traceMonitor->dataAlloc->alloc(sizeof(VMSideExit) + + (stackSlots + ngslots) * sizeof(JSTraceType)); + + /* Setup side exit structure. */ + memset(exit, 0, sizeof(VMSideExit)); + exit->from = fragment; + exit->calldepth = callDepth; + exit->numGlobalSlots = ngslots; + exit->numStackSlots = stackSlots; + exit->numStackSlotsBelowCurrentFrame = cx->fp->argv ? + nativeStackOffset(&cx->fp->argv[-2]) / sizeof(double) : + 0; + exit->exitType = exitType; + exit->block = fp->blockChain; + if (fp->blockChain) + treeInfo->gcthings.addUnique(OBJECT_TO_JSVAL(fp->blockChain)); + exit->pc = pc; + exit->imacpc = fp->imacpc; + exit->sp_adj = (stackSlots * sizeof(double)) - treeInfo->nativeStackBase; + exit->rp_adj = exit->calldepth * sizeof(FrameInfo*); + exit->nativeCalleeWord = 0; + exit->lookupFlags = js_InferFlags(cx, 0); + memcpy(exit->fullTypeMap(), typemap, typemap_size); + +#if defined JS_JIT_SPEW + TreevisLogExit(cx, exit); +#endif + return exit; +} + +JS_REQUIRES_STACK GuardRecord* +TraceRecorder::createGuardRecord(VMSideExit* exit) +{ + GuardRecord* gr = new (*traceMonitor->dataAlloc) GuardRecord(); + + memset(gr, 0, sizeof(GuardRecord)); + gr->exit = exit; + exit->addGuard(gr); + + // gr->profCount is memset'd to zero + verbose_only( + gr->profGuardID = fragment->guardNumberer++; + gr->nextInFrag = fragment->guardsForFrag; + fragment->guardsForFrag = gr; + ) + + return gr; +} + +/* + * Emit a guard for condition (cond), expecting to evaluate to boolean result + * (expected) and using the supplied side exit if the conditon doesn't hold. + */ +JS_REQUIRES_STACK void +TraceRecorder::guard(bool expected, LIns* cond, VMSideExit* exit) +{ + debug_only_printf(LC_TMRecorder, + " About to try emitting guard code for " + "SideExit=%p exitType=%s\n", + (void*)exit, getExitName(exit->exitType)); + + GuardRecord* guardRec = createGuardRecord(exit); + + /* + * BIG FAT WARNING: If compilation fails we don't reset the lirbuf, so it's + * safe to keep references to the side exits here. If we ever start + * clearing those lirbufs, we have to make sure we purge the side exits + * that then no longer will be in valid memory. + */ + if (exit->exitType == LOOP_EXIT) + treeInfo->sideExits.add(exit); + + if (!cond->isCond()) { + expected = !expected; + cond = cond->isQuad() ? lir->ins_peq0(cond) : lir->ins_eq0(cond); + } + + LIns* guardIns = + lir->insGuard(expected ? LIR_xf : LIR_xt, cond, guardRec); + if (!guardIns) { + debug_only_print0(LC_TMRecorder, + " redundant guard, eliminated, no codegen\n"); + } +} + +JS_REQUIRES_STACK VMSideExit* +TraceRecorder::copy(VMSideExit* copy) +{ + size_t typemap_size = copy->numGlobalSlots + copy->numStackSlots; + VMSideExit* exit = (VMSideExit*) + traceMonitor->dataAlloc->alloc(sizeof(VMSideExit) + + typemap_size * sizeof(JSTraceType)); + + /* Copy side exit structure. */ + memcpy(exit, copy, sizeof(VMSideExit) + typemap_size * sizeof(JSTraceType)); + exit->guards = NULL; + exit->from = fragment; + exit->target = NULL; + + /* + * BIG FAT WARNING: If compilation fails we don't reset the lirbuf, so it's + * safe to keep references to the side exits here. If we ever start + * clearing those lirbufs, we have to make sure we purge the side exits + * that then no longer will be in valid memory. + */ + if (exit->exitType == LOOP_EXIT) + treeInfo->sideExits.add(exit); +#if defined JS_JIT_SPEW + TreevisLogExit(cx, exit); +#endif + return exit; +} + +/* + * Emit a guard for condition (cond), expecting to evaluate to boolean result + * (expected) and generate a side exit with type exitType to jump to if the + * condition does not hold. + */ +JS_REQUIRES_STACK void +TraceRecorder::guard(bool expected, LIns* cond, ExitType exitType) +{ + guard(expected, cond, snapshot(exitType)); +} + +/* + * Determine whether any context associated with the same thread as cx is + * executing native code. + */ +static inline bool +ProhibitFlush(JSContext* cx) +{ + if (cx->interpState) // early out if the given is in native code + return true; + + JSCList *cl; + +#ifdef JS_THREADSAFE + JSThread* thread = cx->thread; + for (cl = thread->contextList.next; cl != &thread->contextList; cl = cl->next) + if (CX_FROM_THREAD_LINKS(cl)->interpState) + return true; +#else + JSRuntime* rt = cx->runtime; + for (cl = rt->contextList.next; cl != &rt->contextList; cl = cl->next) + if (js_ContextFromLinkField(cl)->interpState) + return true; +#endif + return false; +} + +static void +ResetJITImpl(JSContext* cx) +{ + if (!TRACING_ENABLED(cx)) + return; + JSTraceMonitor* tm = &JS_TRACE_MONITOR(cx); + debug_only_print0(LC_TMTracer, "Flushing cache.\n"); + if (tm->recorder) { + JS_ASSERT_NOT_ON_TRACE(cx); + js_AbortRecording(cx, "flush cache"); + } + if (ProhibitFlush(cx)) { + debug_only_print0(LC_TMTracer, "Deferring JIT flush due to deep bail.\n"); + tm->needFlush = JS_TRUE; + return; + } + tm->flush(); +} + +#ifdef MOZ_TRACEVIS +static JS_INLINE void +ResetJIT(JSContext* cx, TraceVisFlushReason r) +{ + js_LogTraceVisEvent(cx, S_RESET, r); + ResetJITImpl(cx); +} +#else +#define ResetJIT(cx, r) ResetJITImpl(cx) +#endif + +void +js_FlushJITCache(JSContext *cx) +{ + ResetJIT(cx, FR_OOM); +} + +/* Compile the current fragment. */ +JS_REQUIRES_STACK bool +TraceRecorder::compile(JSTraceMonitor* tm) +{ +#ifdef MOZ_TRACEVIS + TraceVisStateObj tvso(cx, S_COMPILE); +#endif + + if (tm->needFlush) { + ResetJIT(cx, FR_DEEP_BAIL); + return false; + } + if (treeInfo->maxNativeStackSlots >= MAX_NATIVE_STACK_SLOTS) { + debug_only_print0(LC_TMTracer, "Blacklist: excessive stack use.\n"); + Blacklist((jsbytecode*) fragment->root->ip); + return false; + } + if (anchor && anchor->exitType != CASE_EXIT) + ++treeInfo->branchCount; + if (outOfMemory()) + return false; + + Assembler *assm = tm->assembler; + nanojit::compile(assm, fragment verbose_only(, tempAlloc, tm->labels)); + if (outOfMemory()) + return false; + + if (assm->error() != nanojit::None) { + debug_only_print0(LC_TMTracer, "Blacklisted: error during compilation\n"); + Blacklist((jsbytecode*) fragment->root->ip); + return false; + } + ResetRecordingAttempts(cx, (jsbytecode*) fragment->ip); + ResetRecordingAttempts(cx, (jsbytecode*) fragment->root->ip); + if (anchor) { +#ifdef NANOJIT_IA32 + if (anchor->exitType == CASE_EXIT) + assm->patch(anchor, anchor->switchInfo); + else +#endif + assm->patch(anchor); + } + JS_ASSERT(fragment->code()); + JS_ASSERT(!fragment->vmprivate); + if (fragment == fragment->root) + fragment->vmprivate = treeInfo; + + /* :TODO: windows support */ +#if defined DEBUG && !defined WIN32 + const char* filename = cx->fp->script->filename; + char* label = (char*)js_malloc((filename ? strlen(filename) : 7) + 16); + sprintf(label, "%s:%u", filename ? filename : "", + js_FramePCToLineNumber(cx, cx->fp)); + tm->labels->add(fragment, sizeof(Fragment), 0, label); + js_free(label); +#endif + AUDIT(traceCompleted); + return true; +} + +static void +JoinPeers(Assembler* assm, VMSideExit* exit, VMFragment* target) +{ + exit->target = target; + assm->patch(exit); + + debug_only_printf(LC_TMTreeVis, "TREEVIS JOIN ANCHOR=%p FRAG=%p\n", (void*)exit, (void*)target); + + if (exit->root() == target) + return; + + target->getTreeInfo()->dependentTrees.addUnique(exit->root()); + exit->root()->getTreeInfo()->linkedTrees.addUnique(target); +} + +/* Results of trying to connect an arbitrary type A with arbitrary type B */ +enum TypeCheckResult +{ + TypeCheck_Okay, /* Okay: same type */ + TypeCheck_Promote, /* Okay: Type A needs f2i() */ + TypeCheck_Demote, /* Okay: Type A needs i2f() */ + TypeCheck_Undemote, /* Bad: Slot is undemotable */ + TypeCheck_Bad /* Bad: incompatible types */ +}; + +class SlotMap : public SlotVisitorBase +{ + public: + struct SlotInfo + { + SlotInfo() + : v(0), promoteInt(false), lastCheck(TypeCheck_Bad) + {} + SlotInfo(jsval* v, bool promoteInt) + : v(v), promoteInt(promoteInt), lastCheck(TypeCheck_Bad) + {} + jsval *v; + bool promoteInt; + TypeCheckResult lastCheck; + }; + + SlotMap(TraceRecorder& rec, unsigned slotOffset) + : mRecorder(rec), + mCx(rec.cx), + slots(NULL), + slotOffset(slotOffset) + { + } + + JS_REQUIRES_STACK JS_ALWAYS_INLINE void + visitGlobalSlot(jsval *vp, unsigned n, unsigned slot) + { + addSlot(vp); + } + + JS_ALWAYS_INLINE SlotMap::SlotInfo& + operator [](unsigned i) + { + return slots[i]; + } + + JS_ALWAYS_INLINE SlotMap::SlotInfo& + get(unsigned i) + { + return slots[i]; + } + + JS_ALWAYS_INLINE unsigned + length() + { + return slots.length(); + } + + /** + * Possible return states: + * + * TypeConsensus_Okay: All types are compatible. Caller must go through slot list and handle + * promote/demotes. + * TypeConsensus_Bad: Types are not compatible. Individual type check results are undefined. + * TypeConsensus_Undemotes: Types would be compatible if slots were marked as undemotable + * before recording began. Caller can go through slot list and mark + * such slots as undemotable. + */ + JS_REQUIRES_STACK TypeConsensus + checkTypes(TreeInfo* ti) + { + if (ti->typeMap.length() < slotOffset || length() != ti->typeMap.length() - slotOffset) + return TypeConsensus_Bad; + + bool has_undemotes = false; + for (unsigned i = 0; i < length(); i++) { + TypeCheckResult result = checkType(i, ti->typeMap[i + slotOffset]); + if (result == TypeCheck_Bad) + return TypeConsensus_Bad; + if (result == TypeCheck_Undemote) + has_undemotes = true; + slots[i].lastCheck = result; + } + if (has_undemotes) + return TypeConsensus_Undemotes; + return TypeConsensus_Okay; + } + + JS_REQUIRES_STACK JS_ALWAYS_INLINE void + addSlot(jsval* vp) + { + slots.add(SlotInfo(vp, isNumber(*vp) && isPromoteInt(mRecorder.get(vp)))); + } + + JS_REQUIRES_STACK void + markUndemotes() + { + for (unsigned i = 0; i < length(); i++) { + if (get(i).lastCheck == TypeCheck_Undemote) + MarkSlotUndemotable(mRecorder.cx, mRecorder.treeInfo, slotOffset + i); + } + } + + JS_REQUIRES_STACK virtual void + adjustTypes() + { + for (unsigned i = 0; i < length(); i++) { + SlotInfo& info = get(i); + JS_ASSERT(info.lastCheck != TypeCheck_Undemote && info.lastCheck != TypeCheck_Bad); + if (info.lastCheck == TypeCheck_Promote) { + JS_ASSERT(isNumber(*info.v)); + mRecorder.set(info.v, mRecorder.f2i(mRecorder.get(info.v))); + } else if (info.lastCheck == TypeCheck_Demote) { + JS_ASSERT(isNumber(*info.v)); + JS_ASSERT(mRecorder.get(info.v)->isQuad()); + + /* Never demote this final i2f. */ + mRecorder.set(info.v, mRecorder.get(info.v), false, false); + } + } + } + private: + TypeCheckResult + checkType(unsigned i, JSTraceType t) + { + debug_only_printf(LC_TMTracer, + "checkType slot %d: interp=%c typemap=%c isNum=%d promoteInt=%d\n", + i, + typeChar[getCoercedType(*slots[i].v)], + typeChar[t], + isNumber(*slots[i].v), + slots[i].promoteInt); + switch (t) { + case TT_INT32: + if (!isNumber(*slots[i].v)) + return TypeCheck_Bad; /* Not a number? Type mismatch. */ + /* This is always a type mismatch, we can't close a double to an int. */ + if (!slots[i].promoteInt) + return TypeCheck_Undemote; + /* Looks good, slot is an int32, the last instruction should be promotable. */ + JS_ASSERT(isInt32(*slots[i].v) && slots[i].promoteInt); + return TypeCheck_Promote; + case TT_DOUBLE: + if (!isNumber(*slots[i].v)) + return TypeCheck_Bad; /* Not a number? Type mismatch. */ + if (slots[i].promoteInt) + return TypeCheck_Demote; + return TypeCheck_Okay; + case TT_NULL: + return JSVAL_IS_NULL(*slots[i].v) ? TypeCheck_Okay : TypeCheck_Bad; + case TT_FUNCTION: + return !JSVAL_IS_PRIMITIVE(*slots[i].v) && + HAS_FUNCTION_CLASS(JSVAL_TO_OBJECT(*slots[i].v)) ? + TypeCheck_Okay : TypeCheck_Bad; + case TT_OBJECT: + return !JSVAL_IS_PRIMITIVE(*slots[i].v) && + !HAS_FUNCTION_CLASS(JSVAL_TO_OBJECT(*slots[i].v)) ? + TypeCheck_Okay : TypeCheck_Bad; + default: + return getCoercedType(*slots[i].v) == t ? TypeCheck_Okay : TypeCheck_Bad; + } + JS_NOT_REACHED("shouldn't fall through type check switch"); + } + protected: + TraceRecorder& mRecorder; + JSContext* mCx; + Queue slots; + unsigned slotOffset; +}; + +class DefaultSlotMap : public SlotMap +{ + public: + DefaultSlotMap(TraceRecorder& tr) : SlotMap(tr, 0) + { + } + + JS_REQUIRES_STACK JS_ALWAYS_INLINE bool + visitStackSlots(jsval *vp, size_t count, JSStackFrame* fp) + { + for (size_t i = 0; i < count; i++) + addSlot(&vp[i]); + return true; + } +}; + +JS_REQUIRES_STACK TypeConsensus +TraceRecorder::selfTypeStability(SlotMap& slotMap) +{ + debug_only_printf(LC_TMTracer, "Checking type stability against self=%p\n", (void*)fragment); + TypeConsensus consensus = slotMap.checkTypes(treeInfo); + + /* Best case: loop jumps back to its own header */ + if (consensus == TypeConsensus_Okay) + return TypeConsensus_Okay; + + /* If the only thing keeping this loop from being stable is undemotions, then mark relevant + * slots as undemotable. + */ + if (consensus == TypeConsensus_Undemotes) + slotMap.markUndemotes(); + + return consensus; +} + +JS_REQUIRES_STACK TypeConsensus +TraceRecorder::peerTypeStability(SlotMap& slotMap, VMFragment** pPeer) +{ + /* See if there are any peers that would make this stable */ + VMFragment* root = (VMFragment*)fragment->root; + VMFragment* peer = getLoop(traceMonitor, root->ip, root->globalObj, root->globalShape, + root->argc); + JS_ASSERT(peer != NULL); + bool onlyUndemotes = false; + for (; peer != NULL; peer = (VMFragment*)peer->peer) { + if (!peer->vmprivate || peer == fragment) + continue; + debug_only_printf(LC_TMTracer, "Checking type stability against peer=%p\n", (void*)peer); + TypeConsensus consensus = slotMap.checkTypes((TreeInfo*)peer->vmprivate); + if (consensus == TypeConsensus_Okay) { + *pPeer = peer; + /* Return this even though there will be linkage; the trace itself is not stable. + * Caller should inspect ppeer to check for a compatible peer. + */ + return TypeConsensus_Okay; + } + if (consensus == TypeConsensus_Undemotes) + onlyUndemotes = true; + } + + return onlyUndemotes ? TypeConsensus_Undemotes : TypeConsensus_Bad; +} + +JS_REQUIRES_STACK bool +TraceRecorder::closeLoop(TypeConsensus &consensus) +{ + DefaultSlotMap slotMap(*this); + VisitSlots(slotMap, cx, 0, *treeInfo->globalSlots); + return closeLoop(slotMap, snapshot(UNSTABLE_LOOP_EXIT), consensus); +} + +/* Complete and compile a trace and link it to the existing tree if appropriate. + * Returns true if something was compiled. Outparam is always set. + */ +JS_REQUIRES_STACK bool +TraceRecorder::closeLoop(SlotMap& slotMap, VMSideExit* exit, TypeConsensus& consensus) +{ + /* + * We should have arrived back at the loop header, and hence we don't want + * to be in an imacro here and the opcode should be either JSOP_TRACE or, in + * case this loop was blacklisted in the meantime, JSOP_NOP. + */ + JS_ASSERT((*cx->fp->regs->pc == JSOP_TRACE || *cx->fp->regs->pc == JSOP_NOP) && + !cx->fp->imacpc); + + if (callDepth != 0) { + debug_only_print0(LC_TMTracer, + "Blacklisted: stack depth mismatch, possible recursion.\n"); + Blacklist((jsbytecode*) fragment->root->ip); + trashSelf = true; + consensus = TypeConsensus_Bad; + return false; + } + + JS_ASSERT(exit->exitType == UNSTABLE_LOOP_EXIT); + JS_ASSERT(exit->numStackSlots == treeInfo->nStackTypes); + + VMFragment* peer = NULL; + VMFragment* root = (VMFragment*)fragment->root; + + consensus = selfTypeStability(slotMap); + if (consensus != TypeConsensus_Okay) { + TypeConsensus peerConsensus = peerTypeStability(slotMap, &peer); + /* If there was a semblance of a stable peer (even if not linkable), keep the result. */ + if (peerConsensus != TypeConsensus_Bad) + consensus = peerConsensus; + } + +#if DEBUG + if (consensus != TypeConsensus_Okay || peer) + AUDIT(unstableLoopVariable); +#endif + + JS_ASSERT(!trashSelf); + + /* This exit is indeed linkable to something now. Process any promote/demotes that + * are pending in the slot map. + */ + if (consensus == TypeConsensus_Okay) + slotMap.adjustTypes(); + + if (consensus != TypeConsensus_Okay || peer) { + fragment->lastIns = lir->insGuard(LIR_x, NULL, createGuardRecord(exit)); + + /* If there is a peer, there must have been an "Okay" consensus. */ + JS_ASSERT_IF(peer, consensus == TypeConsensus_Okay); + + /* Compile as a type-unstable loop, and hope for a connection later. */ + if (!peer) { + /* + * If such a fragment does not exist, let's compile the loop ahead + * of time anyway. Later, if the loop becomes type stable, we will + * connect these two fragments together. + */ + debug_only_print0(LC_TMTracer, + "Trace has unstable loop variable with no stable peer, " + "compiling anyway.\n"); + UnstableExit* uexit = new (*traceMonitor->dataAlloc) UnstableExit; + uexit->fragment = fragment; + uexit->exit = exit; + uexit->next = treeInfo->unstableExits; + treeInfo->unstableExits = uexit; + } else { + JS_ASSERT(peer->code()); + exit->target = peer; + debug_only_printf(LC_TMTracer, + "Joining type-unstable trace to target fragment %p.\n", + (void*)peer); + ((TreeInfo*)peer->vmprivate)->dependentTrees.addUnique(fragment->root); + treeInfo->linkedTrees.addUnique(peer); + } + } else { + exit->exitType = LOOP_EXIT; + debug_only_printf(LC_TMTreeVis, "TREEVIS CHANGEEXIT EXIT=%p TYPE=%s\n", (void*)exit, + getExitName(LOOP_EXIT)); + + JS_ASSERT((fragment == fragment->root) == !!loopLabel); + if (loopLabel) { + lir->insBranch(LIR_j, NULL, loopLabel); + lir->ins1(LIR_live, lirbuf->state); + } + + exit->target = fragment->root; + fragment->lastIns = lir->insGuard(LIR_x, NULL, createGuardRecord(exit)); + } + if (!compile(traceMonitor)) + return false; + + debug_only_printf(LC_TMTreeVis, "TREEVIS CLOSELOOP EXIT=%p PEER=%p\n", (void*)exit, (void*)peer); + + peer = getLoop(traceMonitor, root->ip, root->globalObj, root->globalShape, root->argc); + JS_ASSERT(peer); + joinEdgesToEntry(peer); + + debug_only_stmt(DumpPeerStability(traceMonitor, peer->ip, peer->globalObj, + peer->globalShape, peer->argc);) + + debug_only_print0(LC_TMTracer, + "updating specializations on dependent and linked trees\n"); + if (fragment->root->vmprivate) + SpecializeTreesToMissingGlobals(cx, globalObj, (TreeInfo*)fragment->root->vmprivate); + + /* + * If this is a newly formed tree, and the outer tree has not been compiled yet, we + * should try to compile the outer tree again. + */ + if (outer) + AttemptCompilation(cx, traceMonitor, globalObj, outer, outerArgc); +#ifdef JS_JIT_SPEW + debug_only_printf(LC_TMMinimal, + "Recording completed at %s:%u@%u via closeLoop (FragID=%06u)\n", + cx->fp->script->filename, + js_FramePCToLineNumber(cx, cx->fp), + FramePCOffset(cx->fp), + fragment->profFragID); + debug_only_print0(LC_TMMinimal, "\n"); +#endif + + return true; +} + +static void +FullMapFromExit(TypeMap& typeMap, VMSideExit* exit) +{ + typeMap.setLength(0); + typeMap.fromRaw(exit->stackTypeMap(), exit->numStackSlots); + typeMap.fromRaw(exit->globalTypeMap(), exit->numGlobalSlots); + /* Include globals that were later specialized at the root of the tree. */ + if (exit->numGlobalSlots < exit->root()->getTreeInfo()->nGlobalTypes()) { + typeMap.fromRaw(exit->root()->getTreeInfo()->globalTypeMap() + exit->numGlobalSlots, + exit->root()->getTreeInfo()->nGlobalTypes() - exit->numGlobalSlots); + } +} + +static JS_REQUIRES_STACK TypeConsensus +TypeMapLinkability(JSContext* cx, const TypeMap& typeMap, VMFragment* peer) +{ + const TypeMap& peerMap = peer->getTreeInfo()->typeMap; + unsigned minSlots = JS_MIN(typeMap.length(), peerMap.length()); + TypeConsensus consensus = TypeConsensus_Okay; + for (unsigned i = 0; i < minSlots; i++) { + if (typeMap[i] == peerMap[i]) + continue; + if (typeMap[i] == TT_INT32 && peerMap[i] == TT_DOUBLE && + IsSlotUndemotable(cx, peer->getTreeInfo(), i)) { + consensus = TypeConsensus_Undemotes; + } else { + return TypeConsensus_Bad; + } + } + return consensus; +} + +static JS_REQUIRES_STACK unsigned +FindUndemotesInTypemaps(JSContext* cx, const TypeMap& typeMap, TreeInfo* treeInfo, + Queue& undemotes) +{ + undemotes.setLength(0); + unsigned minSlots = JS_MIN(typeMap.length(), treeInfo->typeMap.length()); + for (unsigned i = 0; i < minSlots; i++) { + if (typeMap[i] == TT_INT32 && treeInfo->typeMap[i] == TT_DOUBLE) { + undemotes.add(i); + } else if (typeMap[i] != treeInfo->typeMap[i]) { + return 0; + } + } + for (unsigned i = 0; i < undemotes.length(); i++) + MarkSlotUndemotable(cx, treeInfo, undemotes[i]); + return undemotes.length(); +} + +JS_REQUIRES_STACK void +TraceRecorder::joinEdgesToEntry(VMFragment* peer_root) +{ + if (fragment->root != fragment) + return; + + TypeMap typeMap(NULL); + Queue undemotes(NULL); + + for (VMFragment* peer = peer_root; peer; peer = (VMFragment*)peer->peer) { + TreeInfo* ti = peer->getTreeInfo(); + if (!ti) + continue; + UnstableExit* uexit = ti->unstableExits; + while (uexit != NULL) { + /* Build the full typemap for this unstable exit */ + FullMapFromExit(typeMap, uexit->exit); + /* Check its compatibility against this tree */ + TypeConsensus consensus = TypeMapLinkability(cx, typeMap, (VMFragment*)fragment->root); + JS_ASSERT_IF(consensus == TypeConsensus_Okay, peer != fragment); + if (consensus == TypeConsensus_Okay) { + debug_only_printf(LC_TMTracer, + "Joining type-stable trace to target exit %p->%p.\n", + (void*)uexit->fragment, (void*)uexit->exit); + + /* + * See bug 531513. Before linking these trees, make sure the + * peer's dependency graph is up to date. + */ + TreeInfo* from = (TreeInfo*)uexit->exit->root()->vmprivate; + if (from->nGlobalTypes() < treeInfo->nGlobalTypes()) { + SpecializeTreesToLateGlobals(cx, from, treeInfo->globalTypeMap(), + treeInfo->nGlobalTypes()); + } + + /* It's okay! Link together and remove the unstable exit. */ + JoinPeers(traceMonitor->assembler, uexit->exit, (VMFragment*)fragment); + uexit = ti->removeUnstableExit(uexit->exit); + } else { + /* Check for int32->double slots that suggest trashing. */ + if (FindUndemotesInTypemaps(cx, typeMap, treeInfo, undemotes)) { + JS_ASSERT(peer == uexit->fragment->root); + if (fragment == peer) + trashSelf = true; + else + whichTreesToTrash.addUnique(uexit->fragment->root); + return; + } + uexit = uexit->next; + } + } + } +} + +JS_REQUIRES_STACK void +TraceRecorder::endLoop() +{ + endLoop(snapshot(LOOP_EXIT)); +} + +/* Emit an always-exit guard and compile the tree (used for break statements. */ +JS_REQUIRES_STACK void +TraceRecorder::endLoop(VMSideExit* exit) +{ + if (callDepth != 0) { + debug_only_print0(LC_TMTracer, "Blacklisted: stack depth mismatch, possible recursion.\n"); + Blacklist((jsbytecode*) fragment->root->ip); + trashSelf = true; + return; + } + + fragment->lastIns = + lir->insGuard(LIR_x, NULL, createGuardRecord(exit)); + if (!compile(traceMonitor)) + return; + + debug_only_printf(LC_TMTreeVis, "TREEVIS ENDLOOP EXIT=%p\n", (void*)exit); + + VMFragment* root = (VMFragment*)fragment->root; + joinEdgesToEntry(getLoop(traceMonitor, + root->ip, + root->globalObj, + root->globalShape, + root->argc)); + debug_only_stmt(DumpPeerStability(traceMonitor, root->ip, root->globalObj, + root->globalShape, root->argc);) + + /* + * Note: this must always be done, in case we added new globals on trace + * and haven't yet propagated those to linked and dependent trees. + */ + debug_only_print0(LC_TMTracer, + "updating specializations on dependent and linked trees\n"); + if (fragment->root->vmprivate) + SpecializeTreesToMissingGlobals(cx, globalObj, (TreeInfo*)fragment->root->vmprivate); + + /* + * If this is a newly formed tree, and the outer tree has not been compiled + * yet, we should try to compile the outer tree again. + */ + if (outer) + AttemptCompilation(cx, traceMonitor, globalObj, outer, outerArgc); +#ifdef JS_JIT_SPEW + debug_only_printf(LC_TMMinimal, + "Recording completed at %s:%u@%u via endLoop (FragID=%06u)\n", + cx->fp->script->filename, + js_FramePCToLineNumber(cx, cx->fp), + FramePCOffset(cx->fp), + fragment->profFragID); + debug_only_print0(LC_TMTracer, "\n"); +#endif +} + +/* Emit code to adjust the stack to match the inner tree's stack expectations. */ +JS_REQUIRES_STACK void +TraceRecorder::prepareTreeCall(VMFragment* inner) +{ + TreeInfo* ti = (TreeInfo*)inner->vmprivate; + inner_sp_ins = lirbuf->sp; + VMSideExit* exit = snapshot(OOM_EXIT); + + /* + * The inner tree expects to be called from the current frame. If the outer + * tree (this trace) is currently inside a function inlining code + * (calldepth > 0), we have to advance the native stack pointer such that + * we match what the inner trace expects to see. We move it back when we + * come out of the inner tree call. + */ + if (callDepth > 0) { + /* + * Calculate the amount we have to lift the native stack pointer by to + * compensate for any outer frames that the inner tree doesn't expect + * but the outer tree has. + */ + ptrdiff_t sp_adj = nativeStackOffset(&cx->fp->argv[-2]); + + /* Calculate the amount we have to lift the call stack by. */ + ptrdiff_t rp_adj = callDepth * sizeof(FrameInfo*); + + /* + * Guard that we have enough stack space for the tree we are trying to + * call on top of the new value for sp. + */ + debug_only_printf(LC_TMTracer, + "sp_adj=%lld outer=%lld inner=%lld\n", + (long long int)sp_adj, + (long long int)treeInfo->nativeStackBase, + (long long int)ti->nativeStackBase); + ptrdiff_t sp_offset = + - treeInfo->nativeStackBase /* rebase sp to beginning of outer tree's stack */ + + sp_adj /* adjust for stack in outer frame inner tree can't see */ + + ti->maxNativeStackSlots * sizeof(double); /* plus the inner tree's stack */ + LIns* sp_top = lir->ins2(LIR_piadd, lirbuf->sp, INS_CONSTWORD(sp_offset)); + guard(true, lir->ins2(LIR_plt, sp_top, eos_ins), exit); + + /* Guard that we have enough call stack space. */ + ptrdiff_t rp_offset = rp_adj + ti->maxCallDepth * sizeof(FrameInfo*); + LIns* rp_top = lir->ins2(LIR_piadd, lirbuf->rp, INS_CONSTWORD(rp_offset)); + guard(true, lir->ins2(LIR_plt, rp_top, eor_ins), exit); + + sp_offset = + - treeInfo->nativeStackBase /* rebase sp to beginning of outer tree's stack */ + + sp_adj /* adjust for stack in outer frame inner tree can't see */ + + ti->nativeStackBase; /* plus the inner tree's stack base */ + /* We have enough space, so adjust sp and rp to their new level. */ + lir->insStorei(inner_sp_ins = lir->ins2(LIR_piadd, lirbuf->sp, INS_CONSTWORD(sp_offset)), + lirbuf->state, offsetof(InterpState, sp)); + lir->insStorei(lir->ins2(LIR_piadd, lirbuf->rp, INS_CONSTWORD(rp_adj)), + lirbuf->state, offsetof(InterpState, rp)); + } + + /* + * The inner tree will probably access stack slots. So tell nanojit not to + * discard or defer stack writes before calling js_CallTree. + * + * (The ExitType of this snapshot is nugatory. The exit can't be taken.) + */ + GuardRecord* guardRec = createGuardRecord(exit); + lir->insGuard(LIR_xbarrier, NULL, guardRec); +} + +static unsigned +BuildGlobalTypeMapFromInnerTree(Queue& typeMap, VMSideExit* inner) +{ +#if defined DEBUG + unsigned initialSlots = typeMap.length(); +#endif + /* First, use the innermost exit's global typemap. */ + typeMap.add(inner->globalTypeMap(), inner->numGlobalSlots); + + /* Add missing global types from the innermost exit's tree. */ + TreeInfo* innerTree = inner->root()->getTreeInfo(); + unsigned slots = inner->numGlobalSlots; + if (slots < innerTree->nGlobalTypes()) { + typeMap.add(innerTree->globalTypeMap() + slots, innerTree->nGlobalTypes() - slots); + slots = innerTree->nGlobalTypes(); + } + JS_ASSERT(typeMap.length() - initialSlots == slots); + return slots; +} + +/* Record a call to an inner tree. */ +JS_REQUIRES_STACK void +TraceRecorder::emitTreeCall(VMFragment* inner, VMSideExit* exit) +{ + TreeInfo* ti = (TreeInfo*)inner->vmprivate; + + /* Invoke the inner tree. */ + LIns* args[] = { INS_CONSTPTR(inner), lirbuf->state }; /* reverse order */ + LIns* ret = lir->insCall(&js_CallTree_ci, args); + + /* Read back all registers, in case the called tree changed any of them. */ +#ifdef DEBUG + JSTraceType* map; + size_t i; + map = exit->globalTypeMap(); + for (i = 0; i < exit->numGlobalSlots; i++) + JS_ASSERT(map[i] != TT_JSVAL); + map = exit->stackTypeMap(); + for (i = 0; i < exit->numStackSlots; i++) + JS_ASSERT(map[i] != TT_JSVAL); +#endif + /* + * Bug 502604 - It is illegal to extend from the outer typemap without + * first extending from the inner. Make a new typemap here. + */ + TypeMap fullMap(NULL); + fullMap.add(exit->stackTypeMap(), exit->numStackSlots); + BuildGlobalTypeMapFromInnerTree(fullMap, exit); + import(ti, inner_sp_ins, exit->numStackSlots, fullMap.length() - exit->numStackSlots, + exit->calldepth, fullMap.data()); + + /* Restore sp and rp to their original values (we still have them in a register). */ + if (callDepth > 0) { + lir->insStorei(lirbuf->sp, lirbuf->state, offsetof(InterpState, sp)); + lir->insStorei(lirbuf->rp, lirbuf->state, offsetof(InterpState, rp)); + } + + // Create snapshot now so that the following block has an updated type map. + VMSideExit* nested = snapshot(NESTED_EXIT); + + // If the outer-trace entry frame is not the same as the inner-trace entry frame, + // then we must reimport the outer trace entry frame in case the inner trace set + // upvars defined in that frame. + if (callDepth > 0) { + ptrdiff_t offset = -treeInfo->nativeStackBase; + JSStackFrame *fp = cx->fp; + for (unsigned i = 0; i < callDepth; ++i) + fp = fp->down; + ImportUnboxedFrameSlotVisitor frameVisitor(*this, lirbuf->sp, offset, + nested->stackTypeMap()); + VisitFrameSlots(frameVisitor, 0, fp, NULL); + } + + /* + * Guard that we come out of the inner tree along the same side exit we came out when + * we called the inner tree at recording time. + */ + guard(true, lir->ins2(LIR_peq, ret, INS_CONSTPTR(exit)), nested); + debug_only_printf(LC_TMTreeVis, "TREEVIS TREECALL INNER=%p EXIT=%p GUARD=%p\n", (void*)inner, + (void*)nested, (void*)exit); + + /* Register us as a dependent tree of the inner tree. */ + ((TreeInfo*)inner->vmprivate)->dependentTrees.addUnique(fragment->root); + treeInfo->linkedTrees.addUnique(inner); +} + +/* Add a if/if-else control-flow merge point to the list of known merge points. */ +JS_REQUIRES_STACK void +TraceRecorder::trackCfgMerges(jsbytecode* pc) +{ + /* If we hit the beginning of an if/if-else, then keep track of the merge point after it. */ + JS_ASSERT((*pc == JSOP_IFEQ) || (*pc == JSOP_IFEQX)); + jssrcnote* sn = js_GetSrcNote(cx->fp->script, pc); + if (sn != NULL) { + if (SN_TYPE(sn) == SRC_IF) { + cfgMerges.add((*pc == JSOP_IFEQ) + ? pc + GET_JUMP_OFFSET(pc) + : pc + GET_JUMPX_OFFSET(pc)); + } else if (SN_TYPE(sn) == SRC_IF_ELSE) + cfgMerges.add(pc + js_GetSrcNoteOffset(sn, 0)); + } +} + +/* + * Invert the direction of the guard if this is a loop edge that is not + * taken (thin loop). + */ +JS_REQUIRES_STACK void +TraceRecorder::emitIf(jsbytecode* pc, bool cond, LIns* x) +{ + ExitType exitType; + if (IsLoopEdge(pc, (jsbytecode*)fragment->root->ip)) { + exitType = LOOP_EXIT; + + /* + * If we are about to walk out of the loop, generate code for the + * inverse loop condition, pretending we recorded the case that stays + * on trace. + */ + if ((*pc == JSOP_IFEQ || *pc == JSOP_IFEQX) == cond) { + JS_ASSERT(*pc == JSOP_IFNE || *pc == JSOP_IFNEX || *pc == JSOP_IFEQ || *pc == JSOP_IFEQX); + debug_only_print0(LC_TMTracer, + "Walking out of the loop, terminating it anyway.\n"); + cond = !cond; + } + + /* + * Conditional guards do not have to be emitted if the condition is + * constant. We make a note whether the loop condition is true or false + * here, so we later know whether to emit a loop edge or a loop end. + */ + if (x->isconst()) { + loop = (x->imm32() == cond); + return; + } + } else { + exitType = BRANCH_EXIT; + } + if (!x->isconst()) + guard(cond, x, exitType); +} + +/* Emit code for a fused IFEQ/IFNE. */ +JS_REQUIRES_STACK void +TraceRecorder::fuseIf(jsbytecode* pc, bool cond, LIns* x) +{ + if (*pc == JSOP_IFEQ || *pc == JSOP_IFNE) { + emitIf(pc, cond, x); + if (*pc == JSOP_IFEQ) + trackCfgMerges(pc); + } +} + +/* Check whether we have reached the end of the trace. */ +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::checkTraceEnd(jsbytecode *pc) +{ + if (IsLoopEdge(pc, (jsbytecode*)fragment->root->ip)) { + /* + * If we compile a loop, the trace should have a zero stack balance at + * the loop edge. Currently we are parked on a comparison op or + * IFNE/IFEQ, so advance pc to the loop header and adjust the stack + * pointer and pretend we have reached the loop header. + */ + if (loop) { + JS_ASSERT(!cx->fp->imacpc && (pc == cx->fp->regs->pc || pc == cx->fp->regs->pc + 1)); + bool fused = pc != cx->fp->regs->pc; + JSFrameRegs orig = *cx->fp->regs; + + cx->fp->regs->pc = (jsbytecode*)fragment->root->ip; + cx->fp->regs->sp -= fused ? 2 : 1; + + TypeConsensus consensus; + closeLoop(consensus); + + *cx->fp->regs = orig; + } else { + endLoop(); + } + return JSRS_STOP; + } + return JSRS_CONTINUE; +} + +bool +TraceRecorder::hasMethod(JSObject* obj, jsid id) +{ + if (!obj) + return false; + + JSObject* pobj; + JSProperty* prop; + int protoIndex = obj->lookupProperty(cx, id, &pobj, &prop); + if (protoIndex < 0 || !prop) + return false; + + bool found = false; + if (OBJ_IS_NATIVE(pobj)) { + JSScope* scope = OBJ_SCOPE(pobj); + JSScopeProperty* sprop = (JSScopeProperty*) prop; + + if (SPROP_HAS_STUB_GETTER(sprop) && + SPROP_HAS_VALID_SLOT(sprop, scope)) { + jsval v = LOCKED_OBJ_GET_SLOT(pobj, sprop->slot); + if (VALUE_IS_FUNCTION(cx, v)) { + found = true; + if (!scope->branded()) { + scope->brandingShapeChange(cx, sprop->slot, v); + scope->setBranded(); + } + } + } + } + + pobj->dropProperty(cx, prop); + return found; +} + +JS_REQUIRES_STACK bool +TraceRecorder::hasIteratorMethod(JSObject* obj) +{ + JS_ASSERT(cx->fp->regs->sp + 2 <= cx->fp->slots + cx->fp->script->nslots); + + return hasMethod(obj, ATOM_TO_JSID(cx->runtime->atomState.iteratorAtom)); +} + +void +nanojit::StackFilter::getTops(LIns* guard, int& spTop, int& rpTop) +{ + VMSideExit* e = (VMSideExit*)guard->record()->exit; + spTop = e->sp_adj; + rpTop = e->rp_adj; +} + +#if defined NJ_VERBOSE +void +nanojit::LirNameMap::formatGuard(LIns *i, char *out) +{ + VMSideExit *x; + + x = (VMSideExit *)i->record()->exit; + sprintf(out, + "%s: %s %s -> pc=%p imacpc=%p sp%+ld rp%+ld (GuardID=%03d)", + formatRef(i), + lirNames[i->opcode()], + i->oprnd1() ? formatRef(i->oprnd1()) : "", + (void *)x->pc, + (void *)x->imacpc, + (long int)x->sp_adj, + (long int)x->rp_adj, + i->record()->profGuardID); +} +#endif + +static JS_REQUIRES_STACK bool +DeleteRecorder(JSContext* cx) +{ + JSTraceMonitor* tm = &JS_TRACE_MONITOR(cx); + + /* Aborting and completing a trace end up here. */ + delete tm->recorder; + tm->recorder = NULL; + + /* If we ran out of memory, flush the code cache. */ + if (tm->dataAlloc->outOfMemory() || + js_OverfullJITCache(tm)) { + ResetJIT(cx, FR_OOM); + return false; + } + + return true; +} + +/* Check whether the shape of the global object has changed. */ +static JS_REQUIRES_STACK bool +CheckGlobalObjectShape(JSContext* cx, JSTraceMonitor* tm, JSObject* globalObj, + uint32 *shape = NULL, SlotList** slots = NULL) +{ + if (tm->needFlush) { + ResetJIT(cx, FR_DEEP_BAIL); + return false; + } + + if (STOBJ_NSLOTS(globalObj) > MAX_GLOBAL_SLOTS) + return false; + + uint32 globalShape = OBJ_SHAPE(globalObj); + + if (tm->recorder) { + VMFragment* root = (VMFragment*)tm->recorder->getFragment()->root; + TreeInfo* ti = tm->recorder->getTreeInfo(); + + /* Check the global shape matches the recorder's treeinfo's shape. */ + if (globalObj != root->globalObj || globalShape != root->globalShape) { + AUDIT(globalShapeMismatchAtEntry); + debug_only_printf(LC_TMTracer, + "Global object/shape mismatch (%p/%u vs. %p/%u), flushing cache.\n", + (void*)globalObj, globalShape, (void*)root->globalObj, + root->globalShape); + Backoff(cx, (jsbytecode*) root->ip); + ResetJIT(cx, FR_GLOBAL_SHAPE_MISMATCH); + return false; + } + if (shape) + *shape = globalShape; + if (slots) + *slots = ti->globalSlots; + return true; + } + + /* No recorder, search for a tracked global-state (or allocate one). */ + for (size_t i = 0; i < MONITOR_N_GLOBAL_STATES; ++i) { + GlobalState &state = tm->globalStates[i]; + + if (state.globalShape == uint32(-1)) { + state.globalObj = globalObj; + state.globalShape = globalShape; + JS_ASSERT(state.globalSlots); + JS_ASSERT(state.globalSlots->length() == 0); + } + + if (state.globalObj == globalObj && state.globalShape == globalShape) { + if (shape) + *shape = globalShape; + if (slots) + *slots = state.globalSlots; + return true; + } + } + + /* No currently-tracked-global found and no room to allocate, abort. */ + AUDIT(globalShapeMismatchAtEntry); + debug_only_printf(LC_TMTracer, + "No global slotlist for global shape %u, flushing cache.\n", + globalShape); + ResetJIT(cx, FR_GLOBALS_FULL); + return false; +} + +static JS_REQUIRES_STACK bool +StartRecorder(JSContext* cx, VMSideExit* anchor, Fragment* f, TreeInfo* ti, + unsigned stackSlots, unsigned ngslots, JSTraceType* typeMap, + VMSideExit* expectedInnerExit, jsbytecode* outer, uint32 outerArgc) +{ + JSTraceMonitor* tm = &JS_TRACE_MONITOR(cx); + if (JS_TRACE_MONITOR(cx).needFlush) { + ResetJIT(cx, FR_DEEP_BAIL); + return false; + } + + JS_ASSERT(f->root != f || !cx->fp->imacpc); + + /* Start recording if no exception during construction. */ + tm->recorder = new TraceRecorder(cx, anchor, f, ti, + stackSlots, ngslots, typeMap, + expectedInnerExit, outer, outerArgc); + + if (cx->throwing) { + js_AbortRecording(cx, "setting up recorder failed"); + return false; + } + + /* Clear any leftover error state. */ + Assembler *assm = JS_TRACE_MONITOR(cx).assembler; + assm->setError(None); + return true; +} + +static void +TrashTree(JSContext* cx, Fragment* f) +{ + JS_ASSERT((!f->code()) == (!f->vmprivate)); + JS_ASSERT(f == f->root); + debug_only_printf(LC_TMTreeVis, "TREEVIS TRASH FRAG=%p\n", (void*)f); + + if (!f->code()) + return; + AUDIT(treesTrashed); + debug_only_print0(LC_TMTracer, "Trashing tree info.\n"); + TreeInfo* ti = (TreeInfo*)f->vmprivate; + f->vmprivate = NULL; + f->setCode(NULL); + Fragment** data = ti->dependentTrees.data(); + unsigned length = ti->dependentTrees.length(); + for (unsigned n = 0; n < length; ++n) + TrashTree(cx, data[n]); + data = ti->linkedTrees.data(); + length = ti->linkedTrees.length(); + for (unsigned n = 0; n < length; ++n) + TrashTree(cx, data[n]); +} + +static int +SynthesizeFrame(JSContext* cx, const FrameInfo& fi, JSObject* callee) +{ + VOUCH_DOES_NOT_REQUIRE_STACK(); + + JSFunction* fun = GET_FUNCTION_PRIVATE(cx, callee); + JS_ASSERT(FUN_INTERPRETED(fun)); + + /* Assert that we have a correct sp distance from cx->fp->slots in fi. */ + JSStackFrame* fp = cx->fp; + JS_ASSERT_IF(!fi.imacpc, + js_ReconstructStackDepth(cx, fp->script, fi.pc) == + uintN(fi.spdist - fp->script->nfixed)); + + uintN nframeslots = JS_HOWMANY(sizeof(JSInlineFrame), sizeof(jsval)); + JSScript* script = fun->u.i.script; + size_t nbytes = (nframeslots + script->nslots) * sizeof(jsval); + + /* Code duplicated from inline_call: case in js_Interpret (FIXME). */ + JSArena* a = cx->stackPool.current; + void* newmark = (void*) a->avail; + uintN argc = fi.get_argc(); + jsval* vp = fp->slots + fi.spdist - (2 + argc); + uintN missing = 0; + jsval* newsp; + + if (fun->nargs > argc) { + const JSFrameRegs& regs = *fp->regs; + + newsp = vp + 2 + fun->nargs; + JS_ASSERT(newsp > regs.sp); + if ((jsuword) newsp <= a->limit) { + if ((jsuword) newsp > a->avail) + a->avail = (jsuword) newsp; + jsval* argsp = newsp; + do { + *--argsp = JSVAL_VOID; + } while (argsp != regs.sp); + missing = 0; + } else { + missing = fun->nargs - argc; + nbytes += (2 + fun->nargs) * sizeof(jsval); + } + } + + /* Allocate the inline frame with its vars and operands. */ + if (a->avail + nbytes <= a->limit) { + newsp = (jsval *) a->avail; + a->avail += nbytes; + JS_ASSERT(missing == 0); + } else { + /* + * This allocation is infallible: ExecuteTree reserved enough stack. + * (But see bug 491023.) + */ + JS_ARENA_ALLOCATE_CAST(newsp, jsval *, &cx->stackPool, nbytes); + JS_ASSERT(newsp); + + /* + * Move args if the missing ones overflow arena a, then push + * undefined for the missing args. + */ + if (missing) { + memcpy(newsp, vp, (2 + argc) * sizeof(jsval)); + vp = newsp; + newsp = vp + 2 + argc; + do { + *newsp++ = JSVAL_VOID; + } while (--missing != 0); + } + } + + /* Claim space for the stack frame and initialize it. */ + JSInlineFrame* newifp = (JSInlineFrame *) newsp; + newsp += nframeslots; + + newifp->frame.callobj = NULL; + newifp->frame.argsobj = NULL; + newifp->frame.varobj = NULL; + newifp->frame.script = script; + newifp->frame.fun = fun; + + bool constructing = fi.is_constructing(); + newifp->frame.argc = argc; + newifp->callerRegs.pc = fi.pc; + newifp->callerRegs.sp = fp->slots + fi.spdist; + fp->imacpc = fi.imacpc; + +#ifdef DEBUG + if (fi.block != fp->blockChain) { + for (JSObject* obj = fi.block; obj != fp->blockChain; obj = STOBJ_GET_PARENT(obj)) + JS_ASSERT(obj); + } +#endif + fp->blockChain = fi.block; + + newifp->frame.argv = newifp->callerRegs.sp - argc; + JS_ASSERT(newifp->frame.argv); +#ifdef DEBUG + // Initialize argv[-1] to a known-bogus value so we'll catch it if + // someone forgets to initialize it later. + newifp->frame.argv[-1] = JSVAL_HOLE; +#endif + JS_ASSERT(newifp->frame.argv >= StackBase(fp) + 2); + + newifp->frame.rval = JSVAL_VOID; + newifp->frame.down = fp; + newifp->frame.annotation = NULL; + newifp->frame.scopeChain = NULL; // will be updated in FlushNativeStackFrame + newifp->frame.sharpDepth = 0; + newifp->frame.sharpArray = NULL; + newifp->frame.flags = constructing ? JSFRAME_CONSTRUCTING : 0; + newifp->frame.dormantNext = NULL; + newifp->frame.blockChain = NULL; + newifp->mark = newmark; + newifp->frame.thisp = NULL; // will be updated in FlushNativeStackFrame + + newifp->frame.regs = fp->regs; + newifp->frame.regs->pc = script->code; + newifp->frame.regs->sp = newsp + script->nfixed; + newifp->frame.imacpc = NULL; + newifp->frame.slots = newsp; + if (script->staticLevel < JS_DISPLAY_SIZE) { + JSStackFrame **disp = &cx->display[script->staticLevel]; + newifp->frame.displaySave = *disp; + *disp = &newifp->frame; + } + + /* + * Note that fp->script is still the caller's script; set the callee + * inline frame's idea of caller version from its version. + */ + newifp->callerVersion = (JSVersion) fp->script->version; + + // After this paragraph, fp and cx->fp point to the newly synthesized frame. + fp->regs = &newifp->callerRegs; + fp = cx->fp = &newifp->frame; + + /* + * If there's a call hook, invoke it to compute the hookData used by + * debuggers that cooperate with the interpreter. + */ + JSInterpreterHook hook = cx->debugHooks->callHook; + if (hook) { + newifp->hookData = hook(cx, fp, JS_TRUE, 0, cx->debugHooks->callHookData); + } else { + newifp->hookData = NULL; + } + + /* + * Duplicate native stack layout computation: see VisitFrameSlots header comment. + * + * FIXME - We must count stack slots from caller's operand stack up to (but + * not including) callee's, including missing arguments. Could we shift + * everything down to the caller's fp->slots (where vars start) and avoid + * some of the complexity? + */ + return (fi.spdist - fp->down->script->nfixed) + + ((fun->nargs > fp->argc) ? fun->nargs - fp->argc : 0) + + script->nfixed + 1/*argsobj*/; +} + +static void +SynthesizeSlowNativeFrame(InterpState& state, JSContext *cx, VMSideExit *exit) +{ + VOUCH_DOES_NOT_REQUIRE_STACK(); + + void *mark; + JSInlineFrame *ifp; + + /* This allocation is infallible: ExecuteTree reserved enough stack. */ + mark = JS_ARENA_MARK(&cx->stackPool); + JS_ARENA_ALLOCATE_CAST(ifp, JSInlineFrame *, &cx->stackPool, sizeof(JSInlineFrame)); + JS_ASSERT(ifp); + + JSStackFrame *fp = &ifp->frame; + fp->regs = NULL; + fp->imacpc = NULL; + fp->slots = NULL; + fp->callobj = NULL; + fp->argsobj = NULL; + fp->varobj = cx->fp->varobj; + fp->script = NULL; + // fp->thisp is really a jsval, so reinterpret_cast here, not JSVAL_TO_OBJECT. + fp->thisp = (JSObject *) state.nativeVp[1]; + fp->argc = state.nativeVpLen - 2; + fp->argv = state.nativeVp + 2; + fp->fun = GET_FUNCTION_PRIVATE(cx, JSVAL_TO_OBJECT(fp->argv[-2])); + fp->rval = JSVAL_VOID; + fp->down = cx->fp; + fp->annotation = NULL; + JS_ASSERT(cx->fp->scopeChain); + fp->scopeChain = cx->fp->scopeChain; + fp->blockChain = NULL; + fp->sharpDepth = 0; + fp->sharpArray = NULL; + fp->flags = exit->constructing() ? JSFRAME_CONSTRUCTING : 0; + fp->dormantNext = NULL; + fp->displaySave = NULL; + + ifp->mark = mark; + cx->fp = fp; +} + +static JS_REQUIRES_STACK bool +RecordTree(JSContext* cx, JSTraceMonitor* tm, VMFragment* f, jsbytecode* outer, + uint32 outerArgc, JSObject* globalObj, uint32 globalShape, + SlotList* globalSlots, uint32 argc) +{ + JS_ASSERT(f->root == f); + + /* save a local copy for use after JIT flush */ + const void* localRootIP = f->root->ip; + + /* Make sure the global type map didn't change on us. */ + if (!CheckGlobalObjectShape(cx, tm, globalObj)) { + Backoff(cx, (jsbytecode*) localRootIP); + return false; + } + + AUDIT(recorderStarted); + + /* Try to find an unused peer fragment, or allocate a new one. */ + while (f->code() && f->peer) + f = f->peer; + if (f->code()) + f = getAnchor(&JS_TRACE_MONITOR(cx), f->root->ip, globalObj, globalShape, argc); + + if (!f) { + ResetJIT(cx, FR_OOM); + return false; + } + + f->root = f; + f->lirbuf = tm->lirbuf; + + if (tm->dataAlloc->outOfMemory() || js_OverfullJITCache(tm)) { + Backoff(cx, (jsbytecode*) f->root->ip); + ResetJIT(cx, FR_OOM); + debug_only_print0(LC_TMTracer, + "Out of memory recording new tree, flushing cache.\n"); + return false; + } + + JS_ASSERT(!f->code() && !f->vmprivate); + + /* Set up the VM-private treeInfo structure for this fragment. */ + TreeInfo* ti = new (*tm->dataAlloc) TreeInfo(tm->dataAlloc, f, globalSlots); + + /* Capture the coerced type of each active slot in the type map. */ + ti->typeMap.captureTypes(cx, globalObj, *globalSlots, 0 /* callDepth */); + ti->nStackTypes = ti->typeMap.length() - globalSlots->length(); + +#ifdef DEBUG + AssertTreeIsUnique(tm, (VMFragment*)f, ti); + ti->treeFileName = cx->fp->script->filename; + ti->treeLineNumber = js_FramePCToLineNumber(cx, cx->fp); + ti->treePCOffset = FramePCOffset(cx->fp); +#endif +#ifdef JS_JIT_SPEW + debug_only_printf(LC_TMTreeVis, "TREEVIS CREATETREE ROOT=%p PC=%p FILE=\"%s\" LINE=%d OFFS=%d", + (void*)f, f->ip, ti->treeFileName, ti->treeLineNumber, + FramePCOffset(cx->fp)); + debug_only_print0(LC_TMTreeVis, " STACK=\""); + for (unsigned i = 0; i < ti->nStackTypes; i++) + debug_only_printf(LC_TMTreeVis, "%c", typeChar[ti->typeMap[i]]); + debug_only_print0(LC_TMTreeVis, "\" GLOBALS=\""); + for (unsigned i = 0; i < ti->nGlobalTypes(); i++) + debug_only_printf(LC_TMTreeVis, "%c", typeChar[ti->typeMap[ti->nStackTypes + i]]); + debug_only_print0(LC_TMTreeVis, "\"\n"); +#endif + + /* Determine the native frame layout at the entry point. */ + unsigned entryNativeStackSlots = ti->nStackTypes; + JS_ASSERT(entryNativeStackSlots == NativeStackSlots(cx, 0 /* callDepth */)); + ti->nativeStackBase = (entryNativeStackSlots - + (cx->fp->regs->sp - StackBase(cx->fp))) * sizeof(double); + ti->maxNativeStackSlots = entryNativeStackSlots; + ti->maxCallDepth = 0; + ti->script = cx->fp->script; + + /* Recording primary trace. */ + if (!StartRecorder(cx, NULL, f, ti, + ti->nStackTypes, + ti->globalSlots->length(), + ti->typeMap.data(), NULL, outer, outerArgc)) { + return false; + } + + return true; +} + +static JS_REQUIRES_STACK TypeConsensus +FindLoopEdgeTarget(JSContext* cx, VMSideExit* exit, VMFragment** peerp) +{ + VMFragment* from = exit->root(); + TreeInfo* from_ti = from->getTreeInfo(); + + JS_ASSERT(from->code()); + + TypeMap typeMap(NULL); + FullMapFromExit(typeMap, exit); + JS_ASSERT(typeMap.length() - exit->numStackSlots == from_ti->nGlobalTypes()); + + /* Mark all double slots as undemotable */ + for (unsigned i = 0; i < typeMap.length(); i++) { + if (typeMap[i] == TT_DOUBLE) + MarkSlotUndemotable(cx, from_ti, i); + } + + VMFragment* firstPeer = ((VMFragment*)from)->first; + for (VMFragment* peer = firstPeer; peer; peer = peer->peer) { + TreeInfo* peer_ti = peer->getTreeInfo(); + if (!peer_ti) + continue; + JS_ASSERT(peer->argc == from->argc); + JS_ASSERT(exit->numStackSlots == peer_ti->nStackTypes); + TypeConsensus consensus = TypeMapLinkability(cx, typeMap, peer); + if (consensus == TypeConsensus_Okay || consensus == TypeConsensus_Undemotes) { + *peerp = peer; + return consensus; + } + } + + return TypeConsensus_Bad; +} + +UnstableExit* +TreeInfo::removeUnstableExit(VMSideExit* exit) +{ + /* Now erase this exit from the unstable exit list. */ + UnstableExit** tail = &this->unstableExits; + for (UnstableExit* uexit = this->unstableExits; uexit != NULL; uexit = uexit->next) { + if (uexit->exit == exit) { + *tail = uexit->next; + return *tail; + } + tail = &uexit->next; + } + JS_NOT_REACHED("exit not in unstable exit list"); + return NULL; +} + +static JS_REQUIRES_STACK bool +AttemptToStabilizeTree(JSContext* cx, JSObject* globalObj, VMSideExit* exit, jsbytecode* outer, + uint32 outerArgc) +{ + JSTraceMonitor* tm = &JS_TRACE_MONITOR(cx); + if (tm->needFlush) { + ResetJIT(cx, FR_DEEP_BAIL); + return false; + } + + VMFragment* from = exit->root(); + TreeInfo* from_ti = from->getTreeInfo(); + + VMFragment* peer = NULL; + TypeConsensus consensus = FindLoopEdgeTarget(cx, exit, &peer); + if (consensus == TypeConsensus_Okay) { + TreeInfo* peer_ti = peer->getTreeInfo(); + JS_ASSERT(from_ti->globalSlots == peer_ti->globalSlots); + JS_ASSERT(from_ti->nStackTypes == peer_ti->nStackTypes); + /* Patch this exit to its peer */ + JoinPeers(tm->assembler, exit, peer); + /* + * Update peer global types. The |from| fragment should already be updated because it on + * the execution path, and somehow connected to the entry trace. + */ + if (peer_ti->nGlobalTypes() < peer_ti->globalSlots->length()) + SpecializeTreesToMissingGlobals(cx, globalObj, peer_ti); + JS_ASSERT(from_ti->nGlobalTypes() == from_ti->globalSlots->length()); + /* This exit is no longer unstable, so remove it. */ + from_ti->removeUnstableExit(exit); + debug_only_stmt(DumpPeerStability(tm, peer->ip, from->globalObj, from->globalShape, from->argc);) + return false; + } else if (consensus == TypeConsensus_Undemotes) { + /* The original tree is unconnectable, so trash it. */ + TrashTree(cx, peer); + return false; + } + + return RecordTree(cx, tm, from->first, outer, outerArgc, from->globalObj, + from->globalShape, from_ti->globalSlots, cx->fp->argc); +} + +static JS_REQUIRES_STACK bool +AttemptToExtendTree(JSContext* cx, VMSideExit* anchor, VMSideExit* exitedFrom, jsbytecode* outer +#ifdef MOZ_TRACEVIS + , TraceVisStateObj* tvso = NULL +#endif + ) +{ + JSTraceMonitor* tm = &JS_TRACE_MONITOR(cx); + if (tm->needFlush) { + ResetJIT(cx, FR_DEEP_BAIL); +#ifdef MOZ_TRACEVIS + if (tvso) tvso->r = R_FAIL_EXTEND_FLUSH; +#endif + return false; + } + + Fragment* f = anchor->root(); + JS_ASSERT(f->vmprivate); + TreeInfo* ti = (TreeInfo*)f->vmprivate; + + /* + * Don't grow trees above a certain size to avoid code explosion due to + * tail duplication. + */ + if (ti->branchCount >= MAX_BRANCHES) { +#ifdef MOZ_TRACEVIS + if (tvso) tvso->r = R_FAIL_EXTEND_MAX_BRANCHES; +#endif + return false; + } + + Fragment* c; + if (!(c = anchor->target)) { + JSTraceMonitor *tm = &JS_TRACE_MONITOR(cx); + Allocator& alloc = *tm->dataAlloc; + verbose_only( + uint32_t profFragID = (js_LogController.lcbits & LC_FragProfile) + ? (++(tm->lastFragID)) : 0; + ) + c = new (alloc) Fragment(cx->fp->regs->pc verbose_only(, profFragID)); + c->root = anchor->from->root; + debug_only_printf(LC_TMTreeVis, "TREEVIS CREATEBRANCH ROOT=%p FRAG=%p PC=%p FILE=\"%s\"" + " LINE=%d ANCHOR=%p OFFS=%d\n", + (void*)f, (void*)c, (void*)cx->fp->regs->pc, cx->fp->script->filename, + js_FramePCToLineNumber(cx, cx->fp), (void*)anchor, + FramePCOffset(cx->fp)); + anchor->target = c; + c->root = f; + verbose_only( tm->branches = new (alloc) Seq(c, tm->branches); ) + } + + /* + * If we are recycling a fragment, it might have a different ip so reset it + * here. This can happen when attaching a branch to a NESTED_EXIT, which + * might extend along separate paths (i.e. after the loop edge, and after a + * return statement). + */ + c->ip = cx->fp->regs->pc; + + debug_only_printf(LC_TMTracer, + "trying to attach another branch to the tree (hits = %d)\n", c->hits()); + + int32_t& hits = c->hits(); + if (outer || (hits++ >= HOTEXIT && hits <= HOTEXIT+MAXEXIT)) { + /* start tracing secondary trace from this point */ + c->lirbuf = f->lirbuf; + unsigned stackSlots; + unsigned ngslots; + JSTraceType* typeMap; + TypeMap fullMap(NULL); + if (exitedFrom == NULL) { + /* + * If we are coming straight from a simple side exit, just use that + * exit's type map as starting point. + */ + ngslots = anchor->numGlobalSlots; + stackSlots = anchor->numStackSlots; + typeMap = anchor->fullTypeMap(); + } else { + /* + * If we side-exited on a loop exit and continue on a nesting + * guard, the nesting guard (anchor) has the type information for + * everything below the current scope, and the actual guard we + * exited from has the types for everything in the current scope + * (and whatever it inlined). We have to merge those maps here. + */ + VMSideExit* e1 = anchor; + VMSideExit* e2 = exitedFrom; + fullMap.add(e1->stackTypeMap(), e1->numStackSlotsBelowCurrentFrame); + fullMap.add(e2->stackTypeMap(), e2->numStackSlots); + stackSlots = fullMap.length(); + ngslots = BuildGlobalTypeMapFromInnerTree(fullMap, e2); + JS_ASSERT(ngslots >= e1->numGlobalSlots); // inner tree must have all globals + JS_ASSERT(ngslots == fullMap.length() - stackSlots); + typeMap = fullMap.data(); + } + JS_ASSERT(ngslots >= anchor->numGlobalSlots); + bool rv = StartRecorder(cx, anchor, c, (TreeInfo*)f->vmprivate, stackSlots, + ngslots, typeMap, exitedFrom, outer, cx->fp->argc); +#ifdef MOZ_TRACEVIS + if (!rv && tvso) + tvso->r = R_FAIL_EXTEND_START; +#endif + return rv; + } +#ifdef MOZ_TRACEVIS + if (tvso) tvso->r = R_FAIL_EXTEND_COLD; +#endif + return false; +} + +static JS_REQUIRES_STACK VMSideExit* +ExecuteTree(JSContext* cx, Fragment* f, uintN& inlineCallCount, + VMSideExit** innermostNestedGuardp); + +static JS_REQUIRES_STACK bool +RecordLoopEdge(JSContext* cx, TraceRecorder* r, uintN& inlineCallCount) +{ +#ifdef JS_THREADSAFE + if (OBJ_SCOPE(JS_GetGlobalForObject(cx, cx->fp->scopeChain))->title.ownercx != cx) { + js_AbortRecording(cx, "Global object not owned by this context"); + return false; /* we stay away from shared global objects */ + } +#endif + + JSTraceMonitor* tm = &JS_TRACE_MONITOR(cx); + + /* Process needFlush and deep abort requests. */ + if (tm->needFlush) { + ResetJIT(cx, FR_DEEP_BAIL); + return false; + } + + JS_ASSERT(r->getFragment() && !r->getFragment()->lastIns); + VMFragment* root = (VMFragment*)r->getFragment()->root; + + /* Does this branch go to an inner loop? */ + VMFragment* first = getLoop(&JS_TRACE_MONITOR(cx), cx->fp->regs->pc, + root->globalObj, root->globalShape, cx->fp->argc); + if (!first) { + /* Not an inner loop we can call, abort trace. */ + AUDIT(returnToDifferentLoopHeader); + JS_ASSERT(!cx->fp->imacpc); + debug_only_printf(LC_TMTracer, + "loop edge to %lld, header %lld\n", + (long long int)(cx->fp->regs->pc - cx->fp->script->code), + (long long int)((jsbytecode*)r->getFragment()->root->ip - cx->fp->script->code)); + js_AbortRecording(cx, "Loop edge does not return to header"); + return false; + } + + /* Make sure inner tree call will not run into an out-of-memory condition. */ + if (tm->reservedDoublePoolPtr < (tm->reservedDoublePool + MAX_NATIVE_STACK_SLOTS) && + !ReplenishReservedPool(cx, tm)) { + js_AbortRecording(cx, "Couldn't call inner tree (out of memory)"); + return false; + } + + /* + * Make sure the shape of the global object still matches (this might flush + * the JIT cache). + */ + JSObject* globalObj = JS_GetGlobalForObject(cx, cx->fp->scopeChain); + uint32 globalShape = -1; + SlotList* globalSlots = NULL; + if (!CheckGlobalObjectShape(cx, tm, globalObj, &globalShape, &globalSlots)) + return false; + + debug_only_printf(LC_TMTracer, + "Looking for type-compatible peer (%s:%d@%d)\n", + cx->fp->script->filename, + js_FramePCToLineNumber(cx, cx->fp), + FramePCOffset(cx->fp)); + + // Find a matching inner tree. If none can be found, compile one. + VMFragment* f = r->findNestedCompatiblePeer(first); + if (!f || !f->code()) { + AUDIT(noCompatInnerTrees); + + VMFragment* outerFragment = (VMFragment*) tm->recorder->getFragment()->root; + jsbytecode* outer = (jsbytecode*) outerFragment->ip; + uint32 outerArgc = outerFragment->argc; + uint32 argc = cx->fp->argc; + js_AbortRecording(cx, "No compatible inner tree"); + + // Find an empty fragment we can recycle, or allocate a new one. + for (f = first; f != NULL; f = f->peer) { + if (!f->code()) + break; + } + if (!f || f->code()) { + f = getAnchor(tm, cx->fp->regs->pc, globalObj, globalShape, argc); + if (!f) { + ResetJIT(cx, FR_OOM); + return false; + } + } + return RecordTree(cx, tm, f, outer, outerArgc, globalObj, globalShape, globalSlots, argc); + } + + r->adjustCallerTypes(f); + r->prepareTreeCall(f); + + VMSideExit* innermostNestedGuard = NULL; + VMSideExit* lr = ExecuteTree(cx, f, inlineCallCount, &innermostNestedGuard); + + /* ExecuteTree can reenter the interpreter and kill |this|. */ + if (!TRACE_RECORDER(cx)) + return false; + + if (!lr) { + js_AbortRecording(cx, "Couldn't call inner tree"); + return false; + } + + VMFragment* outerFragment = (VMFragment*) tm->recorder->getFragment()->root; + jsbytecode* outer = (jsbytecode*) outerFragment->ip; + switch (lr->exitType) { + case LOOP_EXIT: + /* If the inner tree exited on an unknown loop exit, grow the tree around it. */ + if (innermostNestedGuard) { + js_AbortRecording(cx, "Inner tree took different side exit, abort current " + "recording and grow nesting tree"); + return AttemptToExtendTree(cx, innermostNestedGuard, lr, outer); + } + + /* Emit a call to the inner tree and continue recording the outer tree trace. */ + r->emitTreeCall(f, lr); + return true; + + case UNSTABLE_LOOP_EXIT: + /* Abort recording so the inner loop can become type stable. */ + js_AbortRecording(cx, "Inner tree is trying to stabilize, abort outer recording"); + return AttemptToStabilizeTree(cx, globalObj, lr, outer, outerFragment->argc); + + case OVERFLOW_EXIT: + oracle.markInstructionUndemotable(cx->fp->regs->pc); + /* FALL THROUGH */ + case BRANCH_EXIT: + case CASE_EXIT: + /* Abort recording the outer tree, extend the inner tree. */ + js_AbortRecording(cx, "Inner tree is trying to grow, abort outer recording"); + return AttemptToExtendTree(cx, lr, NULL, outer); + + default: + debug_only_printf(LC_TMTracer, "exit_type=%s\n", getExitName(lr->exitType)); + js_AbortRecording(cx, "Inner tree not suitable for calling"); + return false; + } +} + +static bool +IsEntryTypeCompatible(jsval* vp, JSTraceType* m) +{ + unsigned tag = JSVAL_TAG(*vp); + + debug_only_printf(LC_TMTracer, "%c/%c ", tagChar[tag], typeChar[*m]); + + switch (*m) { + case TT_OBJECT: + if (tag == JSVAL_OBJECT && !JSVAL_IS_NULL(*vp) && + !HAS_FUNCTION_CLASS(JSVAL_TO_OBJECT(*vp))) { + return true; + } + debug_only_printf(LC_TMTracer, "object != tag%u ", tag); + return false; + case TT_INT32: + jsint i; + if (JSVAL_IS_INT(*vp)) + return true; + if (tag == JSVAL_DOUBLE && JSDOUBLE_IS_INT(*JSVAL_TO_DOUBLE(*vp), i)) + return true; + debug_only_printf(LC_TMTracer, "int != tag%u(value=%lu) ", tag, (unsigned long)*vp); + return false; + case TT_DOUBLE: + if (JSVAL_IS_INT(*vp) || tag == JSVAL_DOUBLE) + return true; + debug_only_printf(LC_TMTracer, "double != tag%u ", tag); + return false; + case TT_JSVAL: + JS_NOT_REACHED("shouldn't see jsval type in entry"); + return false; + case TT_STRING: + if (tag == JSVAL_STRING) + return true; + debug_only_printf(LC_TMTracer, "string != tag%u ", tag); + return false; + case TT_NULL: + if (JSVAL_IS_NULL(*vp)) + return true; + debug_only_printf(LC_TMTracer, "null != tag%u ", tag); + return false; + case TT_PSEUDOBOOLEAN: + if (tag == JSVAL_SPECIAL) + return true; + debug_only_printf(LC_TMTracer, "bool != tag%u ", tag); + return false; + default: + JS_ASSERT(*m == TT_FUNCTION); + if (tag == JSVAL_OBJECT && !JSVAL_IS_NULL(*vp) && + HAS_FUNCTION_CLASS(JSVAL_TO_OBJECT(*vp))) { + return true; + } + debug_only_printf(LC_TMTracer, "fun != tag%u ", tag); + return false; + } +} + +class TypeCompatibilityVisitor : public SlotVisitorBase +{ + TraceRecorder &mRecorder; + JSContext *mCx; + JSTraceType *mTypeMap; + unsigned mStackSlotNum; + bool mOk; +public: + TypeCompatibilityVisitor (TraceRecorder &recorder, + JSTraceType *typeMap) : + mRecorder(recorder), + mCx(mRecorder.cx), + mTypeMap(typeMap), + mStackSlotNum(0), + mOk(true) + {} + + JS_REQUIRES_STACK JS_ALWAYS_INLINE void + visitGlobalSlot(jsval *vp, unsigned n, unsigned slot) { + debug_only_printf(LC_TMTracer, "global%d=", n); + if (!IsEntryTypeCompatible(vp, mTypeMap)) { + mOk = false; + } else if (!isPromoteInt(mRecorder.get(vp)) && *mTypeMap == TT_INT32) { + oracle.markGlobalSlotUndemotable(mCx, slot); + mOk = false; + } else if (JSVAL_IS_INT(*vp) && *mTypeMap == TT_DOUBLE) { + oracle.markGlobalSlotUndemotable(mCx, slot); + } + mTypeMap++; + } + + JS_REQUIRES_STACK JS_ALWAYS_INLINE bool + visitStackSlots(jsval *vp, size_t count, JSStackFrame* fp) { + for (size_t i = 0; i < count; ++i) { + debug_only_printf(LC_TMTracer, "%s%u=", stackSlotKind(), unsigned(i)); + if (!IsEntryTypeCompatible(vp, mTypeMap)) { + mOk = false; + } else if (!isPromoteInt(mRecorder.get(vp)) && *mTypeMap == TT_INT32) { + oracle.markStackSlotUndemotable(mCx, mStackSlotNum); + mOk = false; + } else if (JSVAL_IS_INT(*vp) && *mTypeMap == TT_DOUBLE) { + oracle.markStackSlotUndemotable(mCx, mStackSlotNum); + } + vp++; + mTypeMap++; + mStackSlotNum++; + } + return true; + } + + bool isOk() { + return mOk; + } +}; + +JS_REQUIRES_STACK VMFragment* +TraceRecorder::findNestedCompatiblePeer(VMFragment* f) +{ + JSTraceMonitor* tm; + + tm = &JS_TRACE_MONITOR(cx); + unsigned int ngslots = treeInfo->globalSlots->length(); + + TreeInfo* ti; + for (; f != NULL; f = f->peer) { + if (!f->code()) + continue; + + ti = (TreeInfo*)f->vmprivate; + + debug_only_printf(LC_TMTracer, "checking nested types %p: ", (void*)f); + + if (ngslots > ti->nGlobalTypes()) + SpecializeTreesToMissingGlobals(cx, globalObj, ti); + + /* + * Determine whether the typemap of the inner tree matches the outer + * tree's current state. If the inner tree expects an integer, but the + * outer tree doesn't guarantee an integer for that slot, we mark the + * slot undemotable and mismatch here. This will force a new tree to be + * compiled that accepts a double for the slot. If the inner tree + * expects a double, but the outer tree has an integer, we can proceed, + * but we mark the location undemotable. + */ + TypeCompatibilityVisitor visitor(*this, ti->typeMap.data()); + VisitSlots(visitor, cx, 0, *treeInfo->globalSlots); + + debug_only_printf(LC_TMTracer, " %s\n", visitor.isOk() ? "match" : ""); + if (visitor.isOk()) + return f; + } + + return NULL; +} + +class CheckEntryTypeVisitor : public SlotVisitorBase +{ + bool mOk; + JSTraceType *mTypeMap; +public: + CheckEntryTypeVisitor(JSTraceType *typeMap) : + mOk(true), + mTypeMap(typeMap) + {} + + JS_ALWAYS_INLINE void checkSlot(jsval *vp, char const *name, int i) { + debug_only_printf(LC_TMTracer, "%s%d=", name, i); + JS_ASSERT(*(uint8_t*)mTypeMap != 0xCD); + mOk = IsEntryTypeCompatible(vp, mTypeMap++); + } + + JS_REQUIRES_STACK JS_ALWAYS_INLINE void + visitGlobalSlot(jsval *vp, unsigned n, unsigned slot) { + if (mOk) + checkSlot(vp, "global", n); + } + + JS_REQUIRES_STACK JS_ALWAYS_INLINE bool + visitStackSlots(jsval *vp, size_t count, JSStackFrame* fp) { + for (size_t i = 0; i < count; ++i) { + if (!mOk) + break; + checkSlot(vp++, stackSlotKind(), i); + } + return mOk; + } + + bool isOk() { + return mOk; + } +}; + +/** + * Check if types are usable for trace execution. + * + * @param cx Context. + * @param ti Tree info of peer we're testing. + * @return True if compatible (with or without demotions), false otherwise. + */ +static JS_REQUIRES_STACK bool +CheckEntryTypes(JSContext* cx, JSObject* globalObj, TreeInfo* ti) +{ + unsigned int ngslots = ti->globalSlots->length(); + + JS_ASSERT(ti->nStackTypes == NativeStackSlots(cx, 0)); + + if (ngslots > ti->nGlobalTypes()) + SpecializeTreesToMissingGlobals(cx, globalObj, ti); + + JS_ASSERT(ti->typeMap.length() == NativeStackSlots(cx, 0) + ngslots); + JS_ASSERT(ti->typeMap.length() == ti->nStackTypes + ngslots); + JS_ASSERT(ti->nGlobalTypes() == ngslots); + + CheckEntryTypeVisitor visitor(ti->typeMap.data()); + VisitSlots(visitor, cx, 0, *ti->globalSlots); + + debug_only_print0(LC_TMTracer, "\n"); + return visitor.isOk(); +} + +/** + * Find an acceptable entry tree given a PC. + * + * @param cx Context. + * @param globalObj Global object. + * @param f First peer fragment. + * @param nodemote If true, will try to find a peer that does not require demotion. + * @out count Number of fragments consulted. + */ +static JS_REQUIRES_STACK VMFragment* +FindVMCompatiblePeer(JSContext* cx, JSObject* globalObj, VMFragment* f, uintN& count) +{ + count = 0; + for (; f != NULL; f = f->peer) { + if (f->vmprivate == NULL) + continue; + debug_only_printf(LC_TMTracer, + "checking vm types %p (ip: %p): ", (void*)f, f->ip); + if (CheckEntryTypes(cx, globalObj, (TreeInfo*)f->vmprivate)) + return f; + ++count; + } + return NULL; +} + +static void +LeaveTree(InterpState&, VMSideExit* lr); + +static JS_REQUIRES_STACK VMSideExit* +ExecuteTree(JSContext* cx, Fragment* f, uintN& inlineCallCount, + VMSideExit** innermostNestedGuardp) +{ +#ifdef MOZ_TRACEVIS + TraceVisStateObj tvso(cx, S_EXECUTE); +#endif + + JS_ASSERT(f->root == f && f->code() && f->vmprivate); + + /* + * The JIT records and expects to execute with two scope-chain + * assumptions baked-in: + * + * 1. That the bottom of the scope chain is global, in the sense of + * JSCLASS_IS_GLOBAL. + * + * 2. That the scope chain between fp and the global is free of + * "unusual" native objects such as HTML forms or other funny + * things. + * + * #2 is checked here while following the scope-chain links, via + * js_IsCacheableNonGlobalScope, which consults a whitelist of known + * class types; once a global is found, it's checked for #1. Failing + * either check causes an early return from execution. + */ + JSObject* parent; + JSObject* child = cx->fp->scopeChain; + while ((parent = OBJ_GET_PARENT(cx, child)) != NULL) { + if (!js_IsCacheableNonGlobalScope(child)) { + debug_only_print0(LC_TMTracer,"Blacklist: non-cacheable object on scope chain.\n"); + Blacklist((jsbytecode*) f->root->ip); + return NULL; + } + child = parent; + } + JSObject* globalObj = child; + if (!(OBJ_GET_CLASS(cx, globalObj)->flags & JSCLASS_IS_GLOBAL)) { + debug_only_print0(LC_TMTracer, "Blacklist: non-global at root of scope chain.\n"); + Blacklist((jsbytecode*) f->root->ip); + return NULL; + } + + JSTraceMonitor* tm = &JS_TRACE_MONITOR(cx); + TreeInfo* ti = (TreeInfo*)f->vmprivate; + unsigned ngslots = ti->globalSlots->length(); + uint16* gslots = ti->globalSlots->data(); + unsigned globalFrameSize = STOBJ_NSLOTS(globalObj); + + /* Make sure the global object is sane. */ + JS_ASSERT_IF(ngslots != 0, + OBJ_SHAPE(JS_GetGlobalForObject(cx, cx->fp->scopeChain)) == + ((VMFragment*)f)->globalShape); + + /* Make sure our caller replenished the double pool. */ + JS_ASSERT(tm->reservedDoublePoolPtr >= tm->reservedDoublePool + MAX_NATIVE_STACK_SLOTS); + + /* Reserve objects and stack space now, to make leaving the tree infallible. */ + if (!js_ReserveObjects(cx, MAX_CALL_STACK_ENTRIES)) + return NULL; + + /* Set up the interpreter state block, which is followed by the native global frame. */ + InterpState* state = (InterpState*)alloca(sizeof(InterpState) + (globalFrameSize+1)*sizeof(double)); + state->cx = cx; + state->inlineCallCountp = &inlineCallCount; + state->innermostNestedGuardp = innermostNestedGuardp; + state->outermostTree = ti; + state->lastTreeExitGuard = NULL; + state->lastTreeCallGuard = NULL; + state->rpAtLastTreeCall = NULL; + state->nativeVp = NULL; + state->builtinStatus = 0; + + /* Set up the native global frame. */ + double* global = (double*)(state+1); + + /* Set up the native stack frame. */ + double stack_buffer[MAX_NATIVE_STACK_SLOTS]; + state->stackBase = stack_buffer; + state->sp = stack_buffer + (ti->nativeStackBase/sizeof(double)); + state->eos = stack_buffer + MAX_NATIVE_STACK_SLOTS; + + /* Set up the native call stack frame. */ + FrameInfo* callstack_buffer[MAX_CALL_STACK_ENTRIES]; + state->callstackBase = callstack_buffer; + state->rp = callstack_buffer; + state->eor = callstack_buffer + MAX_CALL_STACK_ENTRIES; + + void *reserve; + state->stackMark = JS_ARENA_MARK(&cx->stackPool); + JS_ARENA_ALLOCATE(reserve, &cx->stackPool, MAX_INTERP_STACK_BYTES); + if (!reserve) + return NULL; + +#ifdef DEBUG + memset(stack_buffer, 0xCD, sizeof(stack_buffer)); + memset(global, 0xCD, (globalFrameSize+1)*sizeof(double)); + JS_ASSERT(globalFrameSize <= MAX_GLOBAL_SLOTS); +#endif + + debug_only_stmt(*(uint64*)&global[globalFrameSize] = 0xdeadbeefdeadbeefLL;) + debug_only_printf(LC_TMTracer, + "entering trace at %s:%u@%u, native stack slots: %u code: %p\n", + cx->fp->script->filename, + js_FramePCToLineNumber(cx, cx->fp), + FramePCOffset(cx->fp), + ti->maxNativeStackSlots, + f->code()); + + JS_ASSERT(ti->nGlobalTypes() == ngslots); + BuildNativeFrame(cx, globalObj, 0 /* callDepth */, ngslots, gslots, + ti->typeMap.data(), global, stack_buffer); + + union { NIns *code; GuardRecord* (FASTCALL *func)(InterpState*, Fragment*); } u; + u.code = f->code(); + +#ifdef EXECUTE_TREE_TIMER + state->startTime = rdtsc(); +#endif + + JS_ASSERT(!tm->tracecx); + tm->tracecx = cx; + state->prev = cx->interpState; + cx->interpState = state; + + debug_only_stmt(fflush(NULL)); + GuardRecord* rec; + + // Note that the block scoping is crucial here for TraceVis; the + // TraceVisStateObj constructors and destructors must run at the right times. + { +#ifdef MOZ_TRACEVIS + TraceVisStateObj tvso_n(cx, S_NATIVE); +#endif +#if defined(JS_NO_FASTCALL) && defined(NANOJIT_IA32) + SIMULATE_FASTCALL(rec, state, NULL, u.func); +#else + rec = u.func(state, NULL); +#endif + } + + JS_ASSERT(*(uint64*)&global[globalFrameSize] == 0xdeadbeefdeadbeefLL); + JS_ASSERT(!state->nativeVp); + + VMSideExit* lr = (VMSideExit*)rec->exit; + + AUDIT(traceTriggered); + + cx->interpState = state->prev; + + JS_ASSERT(!cx->bailExit); + JS_ASSERT(lr->exitType != LOOP_EXIT || !lr->calldepth); + tm->tracecx = NULL; + LeaveTree(*state, lr); + return state->innermost; +} + +static JS_FORCES_STACK void +LeaveTree(InterpState& state, VMSideExit* lr) +{ + VOUCH_DOES_NOT_REQUIRE_STACK(); + + JSContext* cx = state.cx; + FrameInfo** callstack = state.callstackBase; + double* stack = state.stackBase; + + /* + * Except if we find that this is a nested bailout, the guard the call + * returned is the one we have to use to adjust pc and sp. + */ + VMSideExit* innermost = lr; + + /* + * While executing a tree we do not update state.sp and state.rp even if + * they grow. Instead, guards tell us by how much sp and rp should be + * incremented in case of a side exit. When calling a nested tree, however, + * we actively adjust sp and rp. If we have such frames from outer trees on + * the stack, then rp will have been adjusted. Before we can process the + * stack of the frames of the tree we directly exited from, we have to + * first work our way through the outer frames and generate interpreter + * frames for them. Once the call stack (rp) is empty, we can process the + * final frames (which again are not directly visible and only the guard we + * exited on will tells us about). + */ + FrameInfo** rp = (FrameInfo**)state.rp; + if (lr->exitType == NESTED_EXIT) { + VMSideExit* nested = state.lastTreeCallGuard; + if (!nested) { + /* + * If lastTreeCallGuard is not set in state, we only have a single + * level of nesting in this exit, so lr itself is the innermost and + * outermost nested guard, and hence we set nested to lr. The + * calldepth of the innermost guard is not added to state.rp, so we + * do it here manually. For a nesting depth greater than 1 the + * CallTree builtin already added the innermost guard's calldepth + * to state.rpAtLastTreeCall. + */ + nested = lr; + rp += lr->calldepth; + } else { + /* + * During unwinding state.rp gets overwritten at every step and we + * restore it here to its state at the innermost nested guard. The + * builtin already added the calldepth of that innermost guard to + * rpAtLastTreeCall. + */ + rp = (FrameInfo**)state.rpAtLastTreeCall; + } + innermost = state.lastTreeExitGuard; + if (state.innermostNestedGuardp) + *state.innermostNestedGuardp = nested; + JS_ASSERT(nested); + JS_ASSERT(nested->exitType == NESTED_EXIT); + JS_ASSERT(state.lastTreeExitGuard); + JS_ASSERT(state.lastTreeExitGuard->exitType != NESTED_EXIT); + } + + int32_t bs = state.builtinStatus; + bool bailed = innermost->exitType == STATUS_EXIT && (bs & JSBUILTIN_BAILED); + if (bailed) { + /* + * Deep-bail case. + * + * A _FAIL native already called LeaveTree. We already reconstructed + * the interpreter stack, in pre-call state, with pc pointing to the + * CALL/APPLY op, for correctness. Then we continued in native code. + * + * First, if we just returned from a slow native, pop its stack frame. + */ + if (!cx->fp->script) { + JSStackFrame *fp = cx->fp; + JS_ASSERT(FUN_SLOW_NATIVE(fp->fun)); + JS_ASSERT(fp->regs == NULL); + JS_ASSERT(fp->down->regs != &((JSInlineFrame *) fp)->callerRegs); + cx->fp = fp->down; + JS_ARENA_RELEASE(&cx->stackPool, ((JSInlineFrame *) fp)->mark); + } + JS_ASSERT(cx->fp->script); + + if (!(bs & JSBUILTIN_ERROR)) { + /* + * The builtin or native deep-bailed but finished successfully + * (no exception or error). + * + * After it returned, the JIT code stored the results of the + * builtin or native at the top of the native stack and then + * immediately flunked the guard on state->builtinStatus. + * + * Now LeaveTree has been called again from the tail of + * ExecuteTree. We are about to return to the interpreter. Adjust + * the top stack frame to resume on the next op. + */ + JSFrameRegs* regs = cx->fp->regs; + JSOp op = (JSOp) *regs->pc; + JS_ASSERT(op == JSOP_CALL || op == JSOP_APPLY || op == JSOP_NEW || + op == JSOP_GETPROP || op == JSOP_GETTHISPROP || op == JSOP_GETARGPROP || + op == JSOP_GETLOCALPROP || op == JSOP_LENGTH || + op == JSOP_GETELEM || op == JSOP_CALLELEM || + op == JSOP_SETPROP || op == JSOP_SETNAME || + op == JSOP_SETELEM || op == JSOP_INITELEM || + op == JSOP_INSTANCEOF); + + /* + * JSOP_SETELEM can be coalesced with a JSOP_POP in the interpeter. + * Since this doesn't re-enter the recorder, the post-state snapshot + * is invalid. Fix it up here. + */ + if (op == JSOP_SETELEM && JSOp(regs->pc[JSOP_SETELEM_LENGTH]) == JSOP_POP) { + regs->sp -= js_CodeSpec[JSOP_SETELEM].nuses; + regs->sp += js_CodeSpec[JSOP_SETELEM].ndefs; + regs->pc += JSOP_SETELEM_LENGTH; + op = JSOP_POP; + } + + const JSCodeSpec& cs = js_CodeSpec[op]; + regs->sp -= (cs.format & JOF_INVOKE) ? GET_ARGC(regs->pc) + 2 : cs.nuses; + regs->sp += cs.ndefs; + regs->pc += cs.length; + JS_ASSERT_IF(!cx->fp->imacpc, + cx->fp->slots + cx->fp->script->nfixed + + js_ReconstructStackDepth(cx, cx->fp->script, regs->pc) == + regs->sp); + + /* + * If there's a tree call around the point that we deep exited at, + * then state.sp and state.rp were restored to their original + * values before the tree call and sp might be less than deepBailSp, + * which we sampled when we were told to deep bail. + */ + JS_ASSERT(state.deepBailSp >= state.stackBase && state.sp <= state.deepBailSp); + + /* + * As explained above, the JIT code stored a result value or values + * on the native stack. Transfer them to the interpreter stack now. + * (Some opcodes, like JSOP_CALLELEM, produce two values, hence the + * loop.) + */ + JSTraceType* typeMap = innermost->stackTypeMap(); + for (int i = 1; i <= cs.ndefs; i++) { + NativeToValue(cx, + regs->sp[-i], + typeMap[innermost->numStackSlots - i], + (jsdouble *) state.deepBailSp + + innermost->sp_adj / sizeof(jsdouble) - i); + } + } + return; + } + + JS_ARENA_RELEASE(&cx->stackPool, state.stackMark); + while (callstack < rp) { + FrameInfo* fi = *callstack; + /* Peek at the callee native slot in the not-yet-synthesized down frame. */ + JSObject* callee = *(JSObject**)&stack[fi->callerHeight]; + + /* + * Synthesize a stack frame and write out the values in it using the + * type map pointer on the native call stack. + */ + SynthesizeFrame(cx, *fi, callee); + int slots = FlushNativeStackFrame(cx, 1 /* callDepth */, (JSTraceType*)(fi + 1), + stack, cx->fp); +#ifdef DEBUG + JSStackFrame* fp = cx->fp; + debug_only_printf(LC_TMTracer, + "synthesized deep frame for %s:%u@%u, slots=%d\n", + fp->script->filename, + js_FramePCToLineNumber(cx, fp), + FramePCOffset(fp), + slots); +#endif + /* + * Keep track of the additional frames we put on the interpreter stack + * and the native stack slots we consumed. + */ + ++*state.inlineCallCountp; + ++callstack; + stack += slots; + } + + /* + * We already synthesized the frames around the innermost guard. Here we + * just deal with additional frames inside the tree we are bailing out + * from. + */ + JS_ASSERT(rp == callstack); + unsigned calldepth = innermost->calldepth; + unsigned calldepth_slots = 0; + unsigned calleeOffset = 0; + for (unsigned n = 0; n < calldepth; ++n) { + /* Peek at the callee native slot in the not-yet-synthesized down frame. */ + calleeOffset += callstack[n]->callerHeight; + JSObject* callee = *(JSObject**)&stack[calleeOffset]; + + /* Reconstruct the frame. */ + calldepth_slots += SynthesizeFrame(cx, *callstack[n], callee); + ++*state.inlineCallCountp; +#ifdef DEBUG + JSStackFrame* fp = cx->fp; + debug_only_printf(LC_TMTracer, + "synthesized shallow frame for %s:%u@%u\n", + fp->script->filename, js_FramePCToLineNumber(cx, fp), + FramePCOffset(fp)); +#endif + } + + /* + * Adjust sp and pc relative to the tree we exited from (not the tree we + * entered into). These are our final values for sp and pc since + * SynthesizeFrame has already taken care of all frames in between. But + * first we recover fp->blockChain, which comes from the side exit + * struct. + */ + JSStackFrame* fp = cx->fp; + + fp->blockChain = innermost->block; + + /* + * If we are not exiting from an inlined frame, the state->sp is spbase. + * Otherwise spbase is whatever slots frames around us consume. + */ + fp->regs->pc = innermost->pc; + fp->imacpc = innermost->imacpc; + fp->regs->sp = StackBase(fp) + (innermost->sp_adj / sizeof(double)) - calldepth_slots; + JS_ASSERT_IF(!fp->imacpc, + fp->slots + fp->script->nfixed + + js_ReconstructStackDepth(cx, fp->script, fp->regs->pc) == fp->regs->sp); + +#ifdef EXECUTE_TREE_TIMER + uint64 cycles = rdtsc() - state.startTime; +#elif defined(JS_JIT_SPEW) + uint64 cycles = 0; +#endif + + debug_only_printf(LC_TMTracer, + "leaving trace at %s:%u@%u, op=%s, lr=%p, exitType=%s, sp=%lld, " + "calldepth=%d, cycles=%llu\n", + fp->script->filename, + js_FramePCToLineNumber(cx, fp), + FramePCOffset(fp), + js_CodeName[fp->imacpc ? *fp->imacpc : *fp->regs->pc], + (void*)lr, + getExitName(lr->exitType), + (long long int)(fp->regs->sp - StackBase(fp)), + calldepth, + (unsigned long long int)cycles); + + /* + * If this trace is part of a tree, later branches might have added + * additional globals for which we don't have any type information + * available in the side exit. We merge in this information from the entry + * type-map. See also the comment in the constructor of TraceRecorder + * regarding why this is always safe to do. + */ + TreeInfo* outermostTree = state.outermostTree; + uint16* gslots = outermostTree->globalSlots->data(); + unsigned ngslots = outermostTree->globalSlots->length(); + JS_ASSERT(ngslots == outermostTree->nGlobalTypes()); + JSTraceType* globalTypeMap; + + /* Are there enough globals? */ + Queue typeMap(0); + if (innermost->numGlobalSlots == ngslots) { + /* Yes. This is the ideal fast path. */ + globalTypeMap = innermost->globalTypeMap(); + } else { + /* + * No. Merge the typemap of the innermost entry and exit together. This + * should always work because it is invalid for nested trees or linked + * trees to have incompatible types. Thus, whenever a new global type + * is lazily added into a tree, all dependent and linked trees are + * immediately specialized (see bug 476653). + */ + JS_ASSERT(innermost->root()->getTreeInfo()->nGlobalTypes() == ngslots); + JS_ASSERT(innermost->root()->getTreeInfo()->nGlobalTypes() > innermost->numGlobalSlots); + typeMap.ensure(ngslots); +#ifdef DEBUG + unsigned check_ngslots = +#endif + BuildGlobalTypeMapFromInnerTree(typeMap, innermost); + JS_ASSERT(check_ngslots == ngslots); + globalTypeMap = typeMap.data(); + } + + /* Write back the topmost native stack frame. */ +#ifdef DEBUG + int slots = +#endif + FlushNativeStackFrame(cx, innermost->calldepth, + innermost->stackTypeMap(), + stack, NULL); + JS_ASSERT(unsigned(slots) == innermost->numStackSlots); + + if (innermost->nativeCalleeWord) + SynthesizeSlowNativeFrame(state, cx, innermost); + + /* Write back interned globals. */ + double* global = (double*)(&state + 1); + FlushNativeGlobalFrame(cx, global, + ngslots, gslots, globalTypeMap); +#ifdef DEBUG + /* Verify that our state restoration worked. */ + for (JSStackFrame* fp = cx->fp; fp; fp = fp->down) { + JS_ASSERT_IF(fp->argv, JSVAL_IS_OBJECT(fp->argv[-1])); + } +#endif +#ifdef JS_JIT_SPEW + if (innermost->exitType != TIMEOUT_EXIT) + AUDIT(sideExitIntoInterpreter); + else + AUDIT(timeoutIntoInterpreter); +#endif + + state.innermost = innermost; +} + +JS_REQUIRES_STACK bool +js_MonitorLoopEdge(JSContext* cx, uintN& inlineCallCount) +{ +#ifdef MOZ_TRACEVIS + TraceVisStateObj tvso(cx, S_MONITOR); +#endif + + JSTraceMonitor* tm = &JS_TRACE_MONITOR(cx); + + /* Is the recorder currently active? */ + if (tm->recorder) { + jsbytecode* innerLoopHeaderPC = cx->fp->regs->pc; + + if (RecordLoopEdge(cx, tm->recorder, inlineCallCount)) + return true; + + /* + * RecordLoopEdge will invoke an inner tree if we have a matching + * one. If we arrive here, that tree didn't run to completion and + * instead we mis-matched or the inner tree took a side exit other than + * the loop exit. We are thus no longer guaranteed to be parked on the + * same loop header js_MonitorLoopEdge was called for. In fact, this + * might not even be a loop header at all. Hence if the program counter + * no longer hovers over the inner loop header, return to the + * interpreter and do not attempt to trigger or record a new tree at + * this location. + */ + if (innerLoopHeaderPC != cx->fp->regs->pc) { +#ifdef MOZ_TRACEVIS + tvso.r = R_INNER_SIDE_EXIT; +#endif + return false; + } + } + JS_ASSERT(!tm->recorder); + + /* Check the pool of reserved doubles (this might trigger a GC). */ + if (tm->reservedDoublePoolPtr < (tm->reservedDoublePool + MAX_NATIVE_STACK_SLOTS) && + !ReplenishReservedPool(cx, tm)) { +#ifdef MOZ_TRACEVIS + tvso.r = R_DOUBLES; +#endif + return false; /* Out of memory, don't try to record now. */ + } + + /* + * Make sure the shape of the global object still matches (this might flush + * the JIT cache). + */ + JSObject* globalObj = JS_GetGlobalForObject(cx, cx->fp->scopeChain); + uint32 globalShape = -1; + SlotList* globalSlots = NULL; + + if (!CheckGlobalObjectShape(cx, tm, globalObj, &globalShape, &globalSlots)) { + Backoff(cx, cx->fp->regs->pc); + return false; + } + + /* Do not enter the JIT code with a pending operation callback. */ + if (cx->operationCallbackFlag) { +#ifdef MOZ_TRACEVIS + tvso.r = R_CALLBACK_PENDING; +#endif + return false; + } + + jsbytecode* pc = cx->fp->regs->pc; + uint32 argc = cx->fp->argc; + + VMFragment* f = getLoop(tm, pc, globalObj, globalShape, argc); + if (!f) + f = getAnchor(tm, pc, globalObj, globalShape, argc); + + if (!f) { + ResetJIT(cx, FR_OOM); +#ifdef MOZ_TRACEVIS + tvso.r = R_OOM_GETANCHOR; +#endif + return false; + } + + /* + * If we have no code in the anchor and no peers, we definitively won't be + * able to activate any trees, so start compiling. + */ + if (!f->code() && !f->peer) { + record: + if (++f->hits() < HOTLOOP) { +#ifdef MOZ_TRACEVIS + tvso.r = f->hits() < 1 ? R_BACKED_OFF : R_COLD; +#endif + return false; + } + + /* + * We can give RecordTree the root peer. If that peer is already taken, + * it will walk the peer list and find us a free slot or allocate a new + * tree if needed. + */ + bool rv = RecordTree(cx, tm, f->first, NULL, 0, globalObj, globalShape, + globalSlots, argc); +#ifdef MOZ_TRACEVIS + if (!rv) + tvso.r = R_FAIL_RECORD_TREE; +#endif + return rv; + } + + debug_only_printf(LC_TMTracer, + "Looking for compat peer %d@%d, from %p (ip: %p)\n", + js_FramePCToLineNumber(cx, cx->fp), + FramePCOffset(cx->fp), (void*)f, f->ip); + + uintN count; + Fragment* match = FindVMCompatiblePeer(cx, globalObj, f, count); + if (!match) { + if (count < MAXPEERS) + goto record; + + /* + * If we hit the max peers ceiling, don't try to lookup fragments all + * the time. That's expensive. This must be a rather type-unstable loop. + */ + debug_only_print0(LC_TMTracer, "Blacklisted: too many peer trees.\n"); + Blacklist((jsbytecode*) f->root->ip); +#ifdef MOZ_TRACEVIS + tvso.r = R_MAX_PEERS; +#endif + return false; + } + + VMSideExit* lr = NULL; + VMSideExit* innermostNestedGuard = NULL; + + lr = ExecuteTree(cx, match, inlineCallCount, &innermostNestedGuard); + if (!lr) { +#ifdef MOZ_TRACEVIS + tvso.r = R_FAIL_EXECUTE_TREE; +#endif + return false; + } + + /* + * If we exit on a branch, or on a tree call guard, try to grow the inner + * tree (in case of a branch exit), or the tree nested around the tree we + * exited from (in case of the tree call guard). + */ + bool rv; + switch (lr->exitType) { + case UNSTABLE_LOOP_EXIT: + rv = AttemptToStabilizeTree(cx, globalObj, lr, NULL, NULL); +#ifdef MOZ_TRACEVIS + if (!rv) + tvso.r = R_FAIL_STABILIZE; +#endif + return rv; + + case OVERFLOW_EXIT: + oracle.markInstructionUndemotable(cx->fp->regs->pc); + /* FALL THROUGH */ + case BRANCH_EXIT: + case CASE_EXIT: + return AttemptToExtendTree(cx, lr, NULL, NULL +#ifdef MOZ_TRACEVIS + , &tvso +#endif + ); + + case LOOP_EXIT: + if (innermostNestedGuard) + return AttemptToExtendTree(cx, innermostNestedGuard, lr, NULL +#ifdef MOZ_TRACEVIS + , &tvso +#endif + ); +#ifdef MOZ_TRACEVIS + tvso.r = R_NO_EXTEND_OUTER; +#endif + return false; + +#ifdef MOZ_TRACEVIS + case MISMATCH_EXIT: tvso.r = R_MISMATCH_EXIT; return false; + case OOM_EXIT: tvso.r = R_OOM_EXIT; return false; + case TIMEOUT_EXIT: tvso.r = R_TIMEOUT_EXIT; return false; + case DEEP_BAIL_EXIT: tvso.r = R_DEEP_BAIL_EXIT; return false; + case STATUS_EXIT: tvso.r = R_STATUS_EXIT; return false; +#endif + + default: + /* + * No, this was an unusual exit (i.e. out of memory/GC), so just resume + * interpretation. + */ +#ifdef MOZ_TRACEVIS + tvso.r = R_OTHER_EXIT; +#endif + return false; + } +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::monitorRecording(JSContext* cx, TraceRecorder* tr, JSOp op) +{ + Assembler *assm = JS_TRACE_MONITOR(cx).assembler; + JSTraceMonitor &localtm = JS_TRACE_MONITOR(cx); + + /* Process needFlush requests now. */ + if (localtm.needFlush) { + ResetJIT(cx, FR_DEEP_BAIL); + return JSRS_STOP; + } + JS_ASSERT(!tr->fragment->lastIns); + + /* + * Clear one-shot state used to communicate between record_JSOP_CALL and post- + * opcode-case-guts record hook (record_NativeCallComplete). + */ + tr->pendingSpecializedNative = NULL; + tr->newobj_ins = NULL; + + /* Handle one-shot request from finishGetProp or INSTANCEOF to snapshot post-op state and guard. */ + if (tr->pendingGuardCondition) { + tr->guard(true, tr->pendingGuardCondition, STATUS_EXIT); + tr->pendingGuardCondition = NULL; + } + + /* Handle one-shot request to unbox the result of a property get. */ + if (tr->pendingUnboxSlot) { + LIns* val_ins = tr->get(tr->pendingUnboxSlot); + val_ins = tr->unbox_jsval(*tr->pendingUnboxSlot, val_ins, tr->snapshot(BRANCH_EXIT)); + tr->set(tr->pendingUnboxSlot, val_ins); + tr->pendingUnboxSlot = 0; + } + + debug_only_stmt( + if (js_LogController.lcbits & LC_TMRecorder) { + js_Disassemble1(cx, cx->fp->script, cx->fp->regs->pc, + cx->fp->imacpc + ? 0 : cx->fp->regs->pc - cx->fp->script->code, + !cx->fp->imacpc, stdout); + } + ) + + /* + * If op is not a break or a return from a loop, continue recording and + * follow the trace. We check for imacro-calling bytecodes inside each + * switch case to resolve the if (JSOP_IS_IMACOP(x)) conditions at compile + * time. + */ + + JSRecordingStatus status; +#ifdef DEBUG + bool wasInImacro = (cx->fp->imacpc != NULL); +#endif + switch (op) { + default: + status = JSRS_ERROR; + goto stop_recording; +# define OPDEF(x,val,name,token,length,nuses,ndefs,prec,format) \ + case x: \ + status = tr->record_##x(); \ + if (JSOP_IS_IMACOP(x)) \ + goto imacro; \ + break; +# include "jsopcode.tbl" +# undef OPDEF + } + + /* record_JSOP_X can reenter the interpreter and kill |tr|. */ + if (!localtm.recorder) + return JSRS_STOP; + + JS_ASSERT(status != JSRS_IMACRO); + JS_ASSERT_IF(!wasInImacro, cx->fp->imacpc == NULL); + + if (assm->error()) { + js_AbortRecording(cx, "error during recording"); + return JSRS_STOP; + } + + if (tr->outOfMemory() || js_OverfullJITCache(&localtm)) { + js_AbortRecording(cx, "no more memory"); + ResetJIT(cx, FR_OOM); + return JSRS_STOP; + } + + imacro: + /* record_JSOP_X can reenter the interpreter and kill |tr|. */ + if (!localtm.recorder) + return JSRS_STOP; + + if (!STATUS_ABORTS_RECORDING(status)) + return status; + + stop_recording: + /* If we recorded the end of the trace, destroy the recorder now. */ + if (tr->fragment->lastIns) { + DeleteRecorder(cx); + return status; + } + + /* Looks like we encountered an error condition. Abort recording. */ + js_AbortRecording(cx, js_CodeName[op]); + return status; +} + +JS_REQUIRES_STACK void +js_AbortRecording(JSContext* cx, const char* reason) +{ + JSTraceMonitor* tm = &JS_TRACE_MONITOR(cx); + JS_ASSERT(tm->recorder != NULL); + + /* Abort the trace and blacklist its starting point. */ + Fragment* f = tm->recorder->getFragment(); + + /* + * If the recorder already had its fragment disposed, or we actually + * finished recording and this recorder merely is passing through the deep + * abort state to the next recorder on the stack, just destroy the + * recorder. There is nothing to abort. + */ + if (!f || f->lastIns) { + DeleteRecorder(cx); + return; + } + + AUDIT(recorderAborted); + + JS_ASSERT(!f->vmprivate); +#ifdef DEBUG + TreeInfo* ti = tm->recorder->getTreeInfo(); + debug_only_printf(LC_TMAbort, + "Abort recording of tree %s:%d@%d at %s:%d@%d: %s.\n", + ti->treeFileName, + ti->treeLineNumber, + ti->treePCOffset, + cx->fp->script->filename, + js_FramePCToLineNumber(cx, cx->fp), + FramePCOffset(cx->fp), + reason); +#endif + + Backoff(cx, (jsbytecode*) f->root->ip, f->root); + + /* If DeleteRecorder flushed the code cache, we can't rely on f any more. */ + if (!DeleteRecorder(cx)) + return; + + /* + * If this is the primary trace and we didn't succeed compiling, trash the + * TreeInfo object. + */ + if (!f->code() && (f->root == f)) + TrashTree(cx, f); +} + +#if defined NANOJIT_IA32 +static bool +CheckForSSE2() +{ + char *c = getenv("X86_FORCE_SSE2"); + if (c) + return (!strcmp(c, "true") || + !strcmp(c, "1") || + !strcmp(c, "yes")); + + int features = 0; +#if defined _MSC_VER + __asm + { + pushad + mov eax, 1 + cpuid + mov features, edx + popad + } +#elif defined __GNUC__ + asm("xchg %%esi, %%ebx\n" /* we can't clobber ebx on gcc (PIC register) */ + "mov $0x01, %%eax\n" + "cpuid\n" + "mov %%edx, %0\n" + "xchg %%esi, %%ebx\n" + : "=m" (features) + : /* We have no inputs */ + : "%eax", "%esi", "%ecx", "%edx" + ); +#elif defined __SUNPRO_C || defined __SUNPRO_CC + asm("push %%ebx\n" + "mov $0x01, %%eax\n" + "cpuid\n" + "pop %%ebx\n" + : "=d" (features) + : /* We have no inputs */ + : "%eax", "%ecx" + ); +#endif + return (features & (1<<26)) != 0; +} +#endif + +#if defined(NANOJIT_ARM) + +#if defined(_MSC_VER) && defined(WINCE) + +// these come in from jswince.asm +extern "C" int js_arm_try_thumb_op(); +extern "C" int js_arm_try_armv6t2_op(); +extern "C" int js_arm_try_armv5_op(); +extern "C" int js_arm_try_armv6_op(); +extern "C" int js_arm_try_armv7_op(); +extern "C" int js_arm_try_vfp_op(); + +static bool +js_arm_check_thumb() { + bool ret = false; + __try { + js_arm_try_thumb_op(); + ret = true; + } __except(GetExceptionCode() == EXCEPTION_ILLEGAL_INSTRUCTION) { + ret = false; + } + return ret; +} + +static bool +js_arm_check_thumb2() { + bool ret = false; + __try { + js_arm_try_armv6t2_op(); + ret = true; + } __except(GetExceptionCode() == EXCEPTION_ILLEGAL_INSTRUCTION) { + ret = false; + } + return ret; +} + +static unsigned int +js_arm_check_arch() { + unsigned int arch = 4; + __try { + js_arm_try_armv5_op(); + arch = 5; + js_arm_try_armv6_op(); + arch = 6; + js_arm_try_armv7_op(); + arch = 7; + } __except(GetExceptionCode() == EXCEPTION_ILLEGAL_INSTRUCTION) { + } + return arch; +} + +static bool +js_arm_check_vfp() { +#ifdef WINCE_WINDOWS_MOBILE + return false; +#else + bool ret = false; + __try { + js_arm_try_vfp_op(); + ret = true; + } __except(GetExceptionCode() == EXCEPTION_ILLEGAL_INSTRUCTION) { + ret = false; + } + return ret; +#endif +} + +#define HAVE_ENABLE_DISABLE_DEBUGGER_EXCEPTIONS 1 + +/* See "Suppressing Exception Notifications while Debugging", at + * http://msdn.microsoft.com/en-us/library/ms924252.aspx + */ +static void +js_disable_debugger_exceptions() +{ + // 2 == TLSSLOT_KERNEL + DWORD kctrl = (DWORD) TlsGetValue(2); + // 0x12 = TLSKERN_NOFAULT | TLSKERN_NOFAULTMSG + kctrl |= 0x12; + TlsSetValue(2, (LPVOID) kctrl); +} + +static void +js_enable_debugger_exceptions() +{ + // 2 == TLSSLOT_KERNEL + DWORD kctrl = (DWORD) TlsGetValue(2); + // 0x12 = TLSKERN_NOFAULT | TLSKERN_NOFAULTMSG + kctrl &= ~0x12; + TlsSetValue(2, (LPVOID) kctrl); +} + +#elif defined(__GNUC__) && defined(AVMPLUS_LINUX) + +#include +#include +#include +#include +#include +#include +#include +#include + +// Assume ARMv4 by default. +static unsigned int arm_arch = 4; +static bool arm_has_thumb = false; +static bool arm_has_vfp = false; +static bool arm_has_neon = false; +static bool arm_has_iwmmxt = false; +static bool arm_tests_initialized = false; + +static void +arm_read_auxv() { + int fd; + Elf32_auxv_t aux; + + fd = open("/proc/self/auxv", O_RDONLY); + if (fd > 0) { + while (read(fd, &aux, sizeof(Elf32_auxv_t))) { + if (aux.a_type == AT_HWCAP) { + uint32_t hwcap = aux.a_un.a_val; + if (getenv("ARM_FORCE_HWCAP")) + hwcap = strtoul(getenv("ARM_FORCE_HWCAP"), NULL, 0); + else if (getenv("_SBOX_DIR")) + continue; // Ignore the rest, if we're running in scratchbox + // hardcode these values to avoid depending on specific versions + // of the hwcap header, e.g. HWCAP_NEON + arm_has_thumb = (hwcap & 4) != 0; + arm_has_vfp = (hwcap & 64) != 0; + arm_has_iwmmxt = (hwcap & 512) != 0; + // this flag is only present on kernel 2.6.29 + arm_has_neon = (hwcap & 4096) != 0; + } else if (aux.a_type == AT_PLATFORM) { + const char *plat = (const char*) aux.a_un.a_val; + if (getenv("ARM_FORCE_PLATFORM")) + plat = getenv("ARM_FORCE_PLATFORM"); + else if (getenv("_SBOX_DIR")) + continue; // Ignore the rest, if we're running in scratchbox + // The platform string has the form "v[0-9][lb]". The "l" or "b" indicate little- + // or big-endian variants and the digit indicates the version of the platform. + // We can only accept ARMv4 and above, but allow anything up to ARMv9 for future + // processors. Architectures newer than ARMv7 are assumed to be + // backwards-compatible with ARMv7. + if ((plat[0] == 'v') && + (plat[1] >= '4') && (plat[1] <= '9') && + ((plat[2] == 'l') || (plat[2] == 'b'))) + { + arm_arch = plat[1] - '0'; + } + } + } + close (fd); + + // if we don't have 2.6.29, we have to do this hack; set + // the env var to trust HWCAP. + if (!getenv("ARM_TRUST_HWCAP") && (arm_arch >= 7)) + arm_has_neon = true; + } + + arm_tests_initialized = true; +} + +static bool +js_arm_check_thumb() { + if (!arm_tests_initialized) + arm_read_auxv(); + + return arm_has_thumb; +} + +static bool +js_arm_check_thumb2() { + if (!arm_tests_initialized) + arm_read_auxv(); + + // ARMv6T2 also supports Thumb2, but Linux doesn't provide an easy way to test for this as + // there is no associated bit in auxv. ARMv7 always supports Thumb2, and future architectures + // are assumed to be backwards-compatible. + return (arm_arch >= 7); +} + +static unsigned int +js_arm_check_arch() { + if (!arm_tests_initialized) + arm_read_auxv(); + + return arm_arch; +} + +static bool +js_arm_check_vfp() { + if (!arm_tests_initialized) + arm_read_auxv(); + + return arm_has_vfp; +} + +#else +#warning Not sure how to check for architecture variant on your platform. Assuming ARMv4. +static bool +js_arm_check_thumb() { return false; } +static bool +js_arm_check_thumb2() { return false; } +static unsigned int +js_arm_check_arch() { return 4; } +static bool +js_arm_check_vfp() { return false; } +#endif + +#ifndef HAVE_ENABLE_DISABLE_DEBUGGER_EXCEPTIONS +static void +js_enable_debugger_exceptions() { } +static void +js_disable_debugger_exceptions() { } +#endif + +#endif /* NANOJIT_ARM */ + +#define K *1024 +#define M K K +#define G K M + +void +js_SetMaxCodeCacheBytes(JSContext* cx, uint32 bytes) +{ + JSTraceMonitor* tm = &JS_THREAD_DATA(cx)->traceMonitor; + JS_ASSERT(tm->codeAlloc && tm->dataAlloc); + if (bytes > 1 G) + bytes = 1 G; + if (bytes < 128 K) + bytes = 128 K; + tm->maxCodeCacheBytes = bytes; +} + +void +js_InitJIT(JSTraceMonitor *tm) +{ +#if defined JS_JIT_SPEW + tm->profAlloc = NULL; + /* Set up debug logging. */ + if (!did_we_set_up_debug_logging) { + InitJITLogController(); + did_we_set_up_debug_logging = true; + } + /* Set up fragprofiling, if required. */ + if (js_LogController.lcbits & LC_FragProfile) { + tm->profAlloc = new VMAllocator(); + tm->profTab = new (*tm->profAlloc) FragStatsMap(*tm->profAlloc); + } + tm->lastFragID = 0; +#else + memset(&js_LogController, 0, sizeof(js_LogController)); +#endif + + if (!did_we_check_processor_features) { +#if defined NANOJIT_IA32 + avmplus::AvmCore::config.use_cmov = + avmplus::AvmCore::config.sse2 = CheckForSSE2(); +#endif +#if defined NANOJIT_ARM + + js_disable_debugger_exceptions(); + + bool arm_vfp = js_arm_check_vfp(); + bool arm_thumb = js_arm_check_thumb(); + bool arm_thumb2 = js_arm_check_thumb2(); + unsigned int arm_arch = js_arm_check_arch(); + + js_enable_debugger_exceptions(); + + avmplus::AvmCore::config.vfp = arm_vfp; + avmplus::AvmCore::config.soft_float = !arm_vfp; + avmplus::AvmCore::config.thumb = arm_thumb; + avmplus::AvmCore::config.thumb2 = arm_thumb2; + avmplus::AvmCore::config.arch = arm_arch; + + // Sanity-check the configuration detection. + // * We don't understand architectures prior to ARMv4. + JS_ASSERT(arm_arch >= 4); + // * All architectures support Thumb with the possible exception of ARMv4. + JS_ASSERT((arm_thumb) || (arm_arch == 4)); + // * Only ARMv6T2 and ARMv7(+) support Thumb2, but ARMv6 does not. + JS_ASSERT((arm_thumb2) || (arm_arch <= 6)); + // * All architectures that support Thumb2 also support Thumb. + JS_ASSERT((arm_thumb2 && arm_thumb) || (!arm_thumb2)); +#endif + did_we_check_processor_features = true; + } + + /* Set the default size for the code cache to 16MB. */ + tm->maxCodeCacheBytes = 16 M; + + if (!tm->recordAttempts.ops) { + JS_DHashTableInit(&tm->recordAttempts, JS_DHashGetStubOps(), + NULL, sizeof(PCHashEntry), + JS_DHASH_DEFAULT_CAPACITY(PC_HASH_COUNT)); + } + + JS_ASSERT(!tm->dataAlloc && !tm->codeAlloc); + tm->dataAlloc = new VMAllocator(); + tm->tempAlloc = new VMAllocator(); + tm->reTempAlloc = new VMAllocator(); + tm->codeAlloc = new CodeAlloc(); + tm->flush(); + verbose_only( tm->branches = NULL; ) + + JS_ASSERT(!tm->reservedDoublePool); + tm->reservedDoublePoolPtr = tm->reservedDoublePool = new jsval[MAX_NATIVE_STACK_SLOTS]; + +#if !defined XP_WIN + debug_only(memset(&jitstats, 0, sizeof(jitstats))); +#endif + +#ifdef JS_JIT_SPEW + /* Architecture properties used by test cases. */ + jitstats.archIsIA32 = 0; + jitstats.archIs64BIT = 0; + jitstats.archIsARM = 0; + jitstats.archIsSPARC = 0; + jitstats.archIsPPC = 0; +#if defined NANOJIT_IA32 + jitstats.archIsIA32 = 1; +#endif +#if defined NANOJIT_64BIT + jitstats.archIs64BIT = 1; +#endif +#if defined NANOJIT_ARM + jitstats.archIsARM = 1; +#endif +#if defined NANOJIT_SPARC + jitstats.archIsSPARC = 1; +#endif +#if defined NANOJIT_PPC + jitstats.archIsPPC = 1; +#endif +#if defined NANOJIT_X64 + jitstats.archIsAMD64 = 1; +#endif +#endif +} + +void +js_FinishJIT(JSTraceMonitor *tm) +{ +#ifdef JS_JIT_SPEW + if (jitstats.recorderStarted) { + char sep = ':'; + debug_only_print0(LC_TMStats, "recorder"); +#define RECORDER_JITSTAT(_ident, _name) \ + debug_only_printf(LC_TMStats, "%c " _name "(%llu)", sep, \ + (unsigned long long int)jitstats._ident); \ + sep = ','; +#define JITSTAT(x) /* nothing */ +#include "jitstats.tbl" +#undef JITSTAT +#undef RECORDER_JITSTAT + debug_only_print0(LC_TMStats, "\n"); + + sep = ':'; + debug_only_print0(LC_TMStats, "monitor"); +#define MONITOR_JITSTAT(_ident, _name) \ + debug_only_printf(LC_TMStats, "%c " _name "(%llu)", sep, \ + (unsigned long long int)jitstats._ident); \ + sep = ','; +#define JITSTAT(x) /* nothing */ +#include "jitstats.tbl" +#undef JITSTAT +#undef MONITOR_JITSTAT + debug_only_print0(LC_TMStats, "\n"); + } +#endif + JS_ASSERT(tm->reservedDoublePool); + + if (tm->recordAttempts.ops) + JS_DHashTableFinish(&tm->recordAttempts); + +#ifdef DEBUG + // Recover profiling data from expiring Fragments, and display + // final results. + if (js_LogController.lcbits & LC_FragProfile) { + for (Seq* f = tm->branches; f; f = f->tail) { + js_FragProfiling_FragFinalizer(f->head, tm); + } + for (size_t i = 0; i < FRAGMENT_TABLE_SIZE; ++i) { + for (VMFragment *f = tm->vmfragments[i]; f; f = f->next) { + JS_ASSERT(f->root == f); + for (VMFragment *p = f; p; p = p->peer) + js_FragProfiling_FragFinalizer(p, tm); + } + } + REHashMap::Iter iter(*(tm->reFragments)); + while (iter.next()) { + nanojit::Fragment* frag = iter.value(); + js_FragProfiling_FragFinalizer(frag, tm); + } + + js_FragProfiling_showResults(tm); + delete tm->profAlloc; + + } else { + NanoAssert(!tm->profTab); + NanoAssert(!tm->profAlloc); + } +#endif + + memset(&tm->vmfragments[0], 0, FRAGMENT_TABLE_SIZE * sizeof(VMFragment*)); + + delete[] tm->reservedDoublePool; + tm->reservedDoublePool = tm->reservedDoublePoolPtr = NULL; + + if (tm->codeAlloc) { + delete tm->codeAlloc; + tm->codeAlloc = NULL; + } + + if (tm->dataAlloc) { + delete tm->dataAlloc; + tm->dataAlloc = NULL; + } + + if (tm->tempAlloc) { + delete tm->tempAlloc; + tm->tempAlloc = NULL; + } + + if (tm->reTempAlloc) { + delete tm->reTempAlloc; + tm->reTempAlloc = NULL; + } +} + +void +js_PurgeJITOracle() +{ + oracle.clear(); +} + +static JSDHashOperator +PurgeScriptRecordingAttempts(JSDHashTable *table, JSDHashEntryHdr *hdr, uint32 number, void *arg) +{ + PCHashEntry *e = (PCHashEntry *)hdr; + JSScript *script = (JSScript *)arg; + jsbytecode *pc = (jsbytecode *)e->key; + + if (JS_UPTRDIFF(pc, script->code) < script->length) + return JS_DHASH_REMOVE; + return JS_DHASH_NEXT; +} + + +JS_REQUIRES_STACK void +js_PurgeScriptFragments(JSContext* cx, JSScript* script) +{ + if (!TRACING_ENABLED(cx)) + return; + debug_only_printf(LC_TMTracer, + "Purging fragments for JSScript %p.\n", (void*)script); + + JSTraceMonitor* tm = &JS_TRACE_MONITOR(cx); + for (size_t i = 0; i < FRAGMENT_TABLE_SIZE; ++i) { + VMFragment** fragp = &tm->vmfragments[i]; + while (VMFragment* frag = *fragp) { + if (JS_UPTRDIFF(frag->ip, script->code) < script->length) { + /* This fragment is associated with the script. */ + debug_only_printf(LC_TMTracer, + "Disconnecting VMFragment %p " + "with ip %p, in range [%p,%p).\n", + (void*)frag, frag->ip, script->code, + script->code + script->length); + + JS_ASSERT(frag->root == frag); + *fragp = frag->next; + do { + verbose_only( js_FragProfiling_FragFinalizer(frag, tm); ) + TrashTree(cx, frag); + } while ((frag = frag->peer) != NULL); + continue; + } + fragp = &frag->next; + } + } + + JS_DHashTableEnumerate(&tm->recordAttempts, PurgeScriptRecordingAttempts, script); +} + +bool +js_OverfullJITCache(JSTraceMonitor* tm) +{ + /* + * You might imagine the outOfMemory flag on the allocator is sufficient + * to model the notion of "running out of memory", but there are actually + * two separate issues involved: + * + * 1. The process truly running out of memory: malloc() or mmap() + * failed. + * + * 2. The limit we put on the "intended size" of the tracemonkey code + * cache, in pages, has been exceeded. + * + * Condition 1 doesn't happen very often, but we're obliged to try to + * safely shut down and signal the rest of spidermonkey when it + * does. Condition 2 happens quite regularly. + * + * Presently, the code in this file doesn't check the outOfMemory condition + * often enough, and frequently misuses the unchecked results of + * lirbuffer insertions on the asssumption that it will notice the + * outOfMemory flag "soon enough" when it returns to the monitorRecording + * function. This turns out to be a false assumption if we use outOfMemory + * to signal condition 2: we regularly provoke "passing our intended + * size" and regularly fail to notice it in time to prevent writing + * over the end of an artificially self-limited LIR buffer. + * + * To mitigate, though not completely solve, this problem, we're + * modeling the two forms of memory exhaustion *separately* for the + * time being: condition 1 is handled by the outOfMemory flag inside + * nanojit, and condition 2 is being handled independently *here*. So + * we construct our allocators to use all available memory they like, + * and only report outOfMemory to us when there is literally no OS memory + * left. Merely purging our cache when we hit our highwater mark is + * handled by the (few) callers of this function. + * + */ + jsuint maxsz = tm->maxCodeCacheBytes; + VMAllocator *dataAlloc = tm->dataAlloc; + CodeAlloc *codeAlloc = tm->codeAlloc; + + return (codeAlloc->size() + dataAlloc->size() > maxsz); +} + +JS_FORCES_STACK JS_FRIEND_API(void) +js_DeepBail(JSContext *cx) +{ + JS_ASSERT(JS_ON_TRACE(cx)); + + /* + * Exactly one context on the current thread is on trace. Find out which + * one. (Most callers cannot guarantee that it's cx.) + */ + JSTraceMonitor *tm = &JS_TRACE_MONITOR(cx); + JSContext *tracecx = tm->tracecx; + + /* It's a bug if a non-FAIL_STATUS builtin gets here. */ + JS_ASSERT(tracecx->bailExit); + + tm->tracecx = NULL; + debug_only_print0(LC_TMTracer, "Deep bail.\n"); + LeaveTree(*tracecx->interpState, tracecx->bailExit); + tracecx->bailExit = NULL; + + InterpState* state = tracecx->interpState; + state->builtinStatus |= JSBUILTIN_BAILED; + state->deepBailSp = state->sp; +} + +JS_REQUIRES_STACK jsval& +TraceRecorder::argval(unsigned n) const +{ + JS_ASSERT(n < cx->fp->fun->nargs); + return cx->fp->argv[n]; +} + +JS_REQUIRES_STACK jsval& +TraceRecorder::varval(unsigned n) const +{ + JS_ASSERT(n < cx->fp->script->nslots); + return cx->fp->slots[n]; +} + +JS_REQUIRES_STACK jsval& +TraceRecorder::stackval(int n) const +{ + jsval* sp = cx->fp->regs->sp; + return sp[n]; +} + +JS_REQUIRES_STACK LIns* +TraceRecorder::scopeChain() const +{ + return lir->insLoad(LIR_ldp, + lir->insLoad(LIR_ldp, cx_ins, offsetof(JSContext, fp)), + offsetof(JSStackFrame, scopeChain)); +} + +/* + * Return the frame of a call object if that frame is part of the current + * trace. |depthp| is an optional outparam: if it is non-null, it will be + * filled in with the depth of the call object's frame relevant to cx->fp. + */ +JS_REQUIRES_STACK JSStackFrame* +TraceRecorder::frameIfInRange(JSObject* obj, unsigned* depthp) const +{ + JSStackFrame* ofp = (JSStackFrame*) obj->getPrivate(); + JSStackFrame* fp = cx->fp; + for (unsigned depth = 0; depth <= callDepth; ++depth) { + if (fp == ofp) { + if (depthp) + *depthp = depth; + return ofp; + } + if (!(fp = fp->down)) + break; + } + return NULL; +} + +JS_DEFINE_CALLINFO_4(extern, UINT32, GetClosureVar, CONTEXT, OBJECT, CVIPTR, DOUBLEPTR, 0, 0) +JS_DEFINE_CALLINFO_4(extern, UINT32, GetClosureArg, CONTEXT, OBJECT, CVIPTR, DOUBLEPTR, 0, 0) + +/* + * Search the scope chain for a property lookup operation at the current PC and + * generate LIR to access the given property. Return JSRS_CONTINUE on success, + * otherwise abort and return JSRS_STOP. There are 3 outparams: + * + * vp the address of the current property value + * ins LIR instruction representing the property value on trace + * NameResult describes how to look up name; see comment for NameResult in jstracer.h + */ +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::scopeChainProp(JSObject* obj, jsval*& vp, LIns*& ins, NameResult& nr) +{ + JS_ASSERT(obj != globalObj); + + JSTraceMonitor &localtm = *traceMonitor; + + JSAtom* atom = atoms[GET_INDEX(cx->fp->regs->pc)]; + JSObject* obj2; + JSProperty* prop; + bool ok = js_FindProperty(cx, ATOM_TO_JSID(atom), &obj, &obj2, &prop); + + /* js_FindProperty can reenter the interpreter and kill |this|. */ + if (!localtm.recorder) + return JSRS_STOP; + + if (!ok) + ABORT_TRACE_ERROR("error in js_FindProperty"); + + if (!prop) + ABORT_TRACE("failed to find name in non-global scope chain"); + + if (obj == globalObj) { + // Even if the property is on the global object, we must guard against + // the creation of properties that shadow the property in the middle + // of the scope chain if we are in a function. + if (cx->fp->argv) { + LIns* obj_ins; + JSObject* parent = STOBJ_GET_PARENT(JSVAL_TO_OBJECT(cx->fp->argv[-2])); + LIns* parent_ins = stobj_get_parent(get(&cx->fp->argv[-2])); + CHECK_STATUS(traverseScopeChain(parent, parent_ins, obj, obj_ins)); + } + + JSScopeProperty* sprop = (JSScopeProperty*) prop; + + if (obj2 != obj) { + obj2->dropProperty(cx, prop); + ABORT_TRACE("prototype property"); + } + if (!isValidSlot(OBJ_SCOPE(obj), sprop)) { + obj2->dropProperty(cx, prop); + return JSRS_STOP; + } + if (!lazilyImportGlobalSlot(sprop->slot)) { + obj2->dropProperty(cx, prop); + ABORT_TRACE("lazy import of global slot failed"); + } + vp = &STOBJ_GET_SLOT(obj, sprop->slot); + ins = get(vp); + obj2->dropProperty(cx, prop); + nr.tracked = true; + return JSRS_CONTINUE; + } + + if (obj == obj2 && OBJ_GET_CLASS(cx, obj) == &js_CallClass) + return callProp(obj, obj2, prop, ATOM_TO_JSID(atom), vp, ins, nr); + + obj2->dropProperty(cx, prop); + ABORT_TRACE("fp->scopeChain is not global or active call object"); +} + +/* + * Generate LIR to access a property of a Call object. + */ +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::callProp(JSObject* obj, JSObject* obj2, JSProperty* prop, jsid id, jsval*& vp, + LIns*& ins, NameResult& nr) +{ + JSScopeProperty *sprop = (JSScopeProperty*) prop; + + uint32 setflags = (js_CodeSpec[*cx->fp->regs->pc].format & (JOF_SET | JOF_INCDEC | JOF_FOR)); + if (setflags && (sprop->attrs & JSPROP_READONLY)) + ABORT_TRACE("writing to a read-only property"); + + uintN slot = sprop->shortid; + + vp = NULL; + uintN upvar_slot = SPROP_INVALID_SLOT; + JSStackFrame* cfp = (JSStackFrame*) obj->getPrivate(); + if (cfp) { + if (sprop->getter == js_GetCallArg) { + JS_ASSERT(slot < cfp->fun->nargs); + vp = &cfp->argv[slot]; + upvar_slot = slot; + nr.v = *vp; + } else if (sprop->getter == js_GetCallVar) { + JS_ASSERT(slot < cfp->script->nslots); + vp = &cfp->slots[slot]; + upvar_slot = cx->fp->fun->nargs + slot; + nr.v = *vp; + } else { + ABORT_TRACE("dynamic property of Call object"); + } + obj2->dropProperty(cx, prop); + + if (frameIfInRange(obj)) { + // At this point we are guaranteed to be looking at an active call oject + // whose properties are stored in the corresponding JSStackFrame. + ins = get(vp); + nr.tracked = true; + return JSRS_CONTINUE; + } + } else { +#ifdef DEBUG + JSBool rv = +#endif + js_GetPropertyHelper(cx, obj, sprop->id, JS_FALSE, &nr.v); + JS_ASSERT(rv); + obj2->dropProperty(cx, prop); + } + + LIns* obj_ins; + JSObject* parent = STOBJ_GET_PARENT(JSVAL_TO_OBJECT(cx->fp->argv[-2])); + LIns* parent_ins = stobj_get_parent(get(&cx->fp->argv[-2])); + CHECK_STATUS(traverseScopeChain(parent, parent_ins, obj, obj_ins)); + + ClosureVarInfo* cv = new (traceMonitor->dataAlloc) ClosureVarInfo(); + cv->id = id; + cv->slot = slot; + cv->callDepth = callDepth; + cv->resolveFlags = cx->resolveFlags == JSRESOLVE_INFER + ? js_InferFlags(cx, 0) + : cx->resolveFlags; + + LIns* outp = lir->insAlloc(sizeof(double)); + LIns* args[] = { + outp, + INS_CONSTPTR(cv), + obj_ins, + cx_ins + }; + const CallInfo* ci; + if (sprop->getter == js_GetCallArg) + ci = &GetClosureArg_ci; + else + ci = &GetClosureVar_ci; + + LIns* call_ins = lir->insCall(ci, args); + JSTraceType type = getCoercedType(nr.v); + guard(true, + addName(lir->ins2(LIR_eq, call_ins, lir->insImm(type)), + "guard(type-stable name access)"), + BRANCH_EXIT); + ins = stackLoad(outp, type); + nr.tracked = false; + nr.obj = obj; + nr.obj_ins = obj_ins; + nr.sprop = sprop; + return JSRS_CONTINUE; +} + +JS_REQUIRES_STACK LIns* +TraceRecorder::arg(unsigned n) +{ + return get(&argval(n)); +} + +JS_REQUIRES_STACK void +TraceRecorder::arg(unsigned n, LIns* i) +{ + set(&argval(n), i); +} + +JS_REQUIRES_STACK LIns* +TraceRecorder::var(unsigned n) +{ + return get(&varval(n)); +} + +JS_REQUIRES_STACK void +TraceRecorder::var(unsigned n, LIns* i) +{ + set(&varval(n), i); +} + +JS_REQUIRES_STACK LIns* +TraceRecorder::stack(int n) +{ + return get(&stackval(n)); +} + +JS_REQUIRES_STACK void +TraceRecorder::stack(int n, LIns* i) +{ + set(&stackval(n), i, n >= 0); +} + +JS_REQUIRES_STACK LIns* +TraceRecorder::alu(LOpcode v, jsdouble v0, jsdouble v1, LIns* s0, LIns* s1) +{ + /* + * To even consider this operation for demotion, both operands have to be + * integers and the oracle must not give us a negative hint for the + * instruction. + */ + if (oracle.isInstructionUndemotable(cx->fp->regs->pc) || !isPromoteInt(s0) || !isPromoteInt(s1)) { + out: + if (v == LIR_fmod) { + LIns* args[] = { s1, s0 }; + return lir->insCall(&js_dmod_ci, args); + } + LIns* result = lir->ins2(v, s0, s1); + JS_ASSERT_IF(s0->isconstf() && s1->isconstf(), result->isconstf()); + return result; + } + + jsdouble r; + switch (v) { + case LIR_fadd: + r = v0 + v1; + break; + case LIR_fsub: + r = v0 - v1; + break; +#if !defined NANOJIT_ARM + case LIR_fmul: + r = v0 * v1; + if (r == 0.0) + goto out; + break; +#endif +#if defined NANOJIT_IA32 || defined NANOJIT_X64 + case LIR_fdiv: + if (v1 == 0) + goto out; + r = v0 / v1; + break; + case LIR_fmod: + if (v0 < 0 || v1 == 0 || (s1->isconstf() && v1 < 0)) + goto out; + r = js_dmod(v0, v1); + break; +#endif + default: + goto out; + } + + /* + * The result must be an integer at record time, otherwise there is no + * point in trying to demote it. + */ + if (jsint(r) != r || JSDOUBLE_IS_NEGZERO(r)) + goto out; + + LIns* d0 = ::demote(lir, s0); + LIns* d1 = ::demote(lir, s1); + + /* + * Speculatively emit an integer operation, betting that at runtime we + * will get integer results again. + */ + VMSideExit* exit; + LIns* result; + switch (v) { +#if defined NANOJIT_IA32 || defined NANOJIT_X64 + case LIR_fdiv: + if (d0->isconst() && d1->isconst()) + return lir->ins1(LIR_i2f, lir->insImm(jsint(r))); + + exit = snapshot(OVERFLOW_EXIT); + + /* + * If the divisor is greater than zero its always safe to execute + * the division. If not, we have to make sure we are not running + * into -2147483648 / -1, because it can raise an overflow exception. + */ + if (!d1->isconst()) { + LIns* gt = lir->insBranch(LIR_jt, lir->ins2i(LIR_gt, d1, 0), NULL); + guard(false, lir->ins_eq0(d1), exit); + guard(false, lir->ins2(LIR_and, + lir->ins2i(LIR_eq, d0, 0x80000000), + lir->ins2i(LIR_eq, d1, -1)), exit); + gt->setTarget(lir->ins0(LIR_label)); + } else { + if (d1->imm32() == -1) + guard(false, lir->ins2i(LIR_eq, d0, 0x80000000), exit); + } + result = lir->ins2(v = LIR_div, d0, d1); + + /* As long the modulus is zero, the result is an integer. */ + guard(true, lir->ins_eq0(lir->ins1(LIR_mod, result)), exit); + + /* Don't lose a -0. */ + guard(false, lir->ins_eq0(result), exit); + break; + + case LIR_fmod: { + if (d0->isconst() && d1->isconst()) + return lir->ins1(LIR_i2f, lir->insImm(jsint(r))); + + exit = snapshot(OVERFLOW_EXIT); + + /* Make sure we don't trigger division by zero at runtime. */ + if (!d1->isconst()) + guard(false, lir->ins_eq0(d1), exit); + result = lir->ins1(v = LIR_mod, lir->ins2(LIR_div, d0, d1)); + + /* If the result is not 0, it is always within the integer domain. */ + LIns* branch = lir->insBranch(LIR_jf, lir->ins_eq0(result), NULL); + + /* + * If the result is zero, we must exit if the lhs is negative since + * the result is -0 in this case, which is not in the integer domain. + */ + guard(false, lir->ins2i(LIR_lt, d0, 0), exit); + branch->setTarget(lir->ins0(LIR_label)); + break; + } +#endif + + default: + v = (LOpcode)((int)v & ~LIR64); + result = lir->ins2(v, d0, d1); + + /* + * If the operands guarantee that the result will be an integer (i.e. + * z = x + y with 0 <= (x|y) <= 0xffff guarantees z <= fffe0001), we + * don't have to guard against an overflow. Otherwise we emit a guard + * that will inform the oracle and cause a non-demoted trace to be + * attached that uses floating-point math for this operation. + */ + if (!result->isconst() && (!IsOverflowSafe(v, d0) || !IsOverflowSafe(v, d1))) { + exit = snapshot(OVERFLOW_EXIT); + guard(false, lir->ins1(LIR_ov, result), exit); + if (v == LIR_mul) // make sure we don't lose a -0 + guard(false, lir->ins_eq0(result), exit); + } + break; + } + JS_ASSERT_IF(d0->isconst() && d1->isconst(), + result->isconst() && result->imm32() == jsint(r)); + return lir->ins1(LIR_i2f, result); +} + +LIns* +TraceRecorder::f2i(LIns* f) +{ + return lir->insCall(&js_DoubleToInt32_ci, &f); +} + +JS_REQUIRES_STACK LIns* +TraceRecorder::makeNumberInt32(LIns* f) +{ + JS_ASSERT(f->isQuad()); + LIns* x; + if (!isPromote(f)) { + x = f2i(f); + guard(true, lir->ins2(LIR_feq, f, lir->ins1(LIR_i2f, x)), MISMATCH_EXIT); + } else { + x = ::demote(lir, f); + } + return x; +} + +JS_REQUIRES_STACK LIns* +TraceRecorder::stringify(jsval& v) +{ + LIns* v_ins = get(&v); + if (JSVAL_IS_STRING(v)) + return v_ins; + + LIns* args[] = { v_ins, cx_ins }; + const CallInfo* ci; + if (JSVAL_IS_NUMBER(v)) { + ci = &js_NumberToString_ci; + } else if (JSVAL_IS_SPECIAL(v)) { + ci = &js_BooleanOrUndefinedToString_ci; + } else { + /* + * Callers must deal with non-primitive (non-null object) values by + * calling an imacro. We don't try to guess about which imacro, with + * what valueOf hint, here. + */ + JS_ASSERT(JSVAL_IS_NULL(v)); + return INS_ATOM(cx->runtime->atomState.nullAtom); + } + + v_ins = lir->insCall(ci, args); + guard(false, lir->ins_peq0(v_ins), OOM_EXIT); + return v_ins; +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::call_imacro(jsbytecode* imacro) +{ + JSStackFrame* fp = cx->fp; + JSFrameRegs* regs = fp->regs; + + /* We cannot nest imacros, only tail-call. */ + if (fp->imacpc) { + /* Dereference is safe since imacros are JSOP_STOP-terminated. */ + if (regs->pc[js_CodeSpec[*regs->pc].length] != JSOP_STOP) + return JSRS_STOP; + regs->pc = imacro; + return JSRS_IMACRO; + } + + fp->imacpc = regs->pc; + regs->pc = imacro; + atoms = COMMON_ATOMS_START(&cx->runtime->atomState); + return JSRS_IMACRO; +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::ifop() +{ + jsval& v = stackval(-1); + LIns* v_ins = get(&v); + bool cond; + LIns* x; + + if (JSVAL_IS_NULL(v)) { + cond = false; + x = lir->insImm(0); + } else if (!JSVAL_IS_PRIMITIVE(v)) { + cond = true; + x = lir->insImm(1); + } else if (JSVAL_IS_SPECIAL(v)) { + /* Test for boolean is true, negate later if we are testing for false. */ + cond = JSVAL_TO_SPECIAL(v) == JS_TRUE; + x = lir->ins2i(LIR_eq, v_ins, 1); + } else if (isNumber(v)) { + jsdouble d = asNumber(v); + cond = !JSDOUBLE_IS_NaN(d) && d; + x = lir->ins2(LIR_and, + lir->ins2(LIR_feq, v_ins, v_ins), + lir->ins_eq0(lir->ins2(LIR_feq, v_ins, lir->insImmf(0)))); + } else if (JSVAL_IS_STRING(v)) { + cond = JSVAL_TO_STRING(v)->length() != 0; + x = lir->ins2(LIR_piand, + lir->insLoad(LIR_ldp, + v_ins, + (int)offsetof(JSString, mLength)), + INS_CONSTWORD(JSString::LENGTH_MASK)); + } else { + JS_NOT_REACHED("ifop"); + return JSRS_STOP; + } + + jsbytecode* pc = cx->fp->regs->pc; + emitIf(pc, cond, x); + return checkTraceEnd(pc); +} + +#ifdef NANOJIT_IA32 +/* + * Record LIR for a tableswitch or tableswitchx op. We record LIR only the + * "first" time we hit the op. Later, when we start traces after exiting that + * trace, we just patch. + */ +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::tableswitch() +{ + jsval& v = stackval(-1); + + /* No need to guard if the condition can't match any of the cases. */ + if (!isNumber(v)) + return JSRS_CONTINUE; + + /* No need to guard if the condition is constant. */ + LIns* v_ins = f2i(get(&v)); + if (v_ins->isconst() || v_ins->isconstq()) + return JSRS_CONTINUE; + + jsbytecode* pc = cx->fp->regs->pc; + /* Starting a new trace after exiting a trace via switch. */ + if (anchor && + (anchor->exitType == CASE_EXIT || anchor->exitType == DEFAULT_EXIT) && + fragment->ip == pc) { + return JSRS_CONTINUE; + } + + /* Decode jsop. */ + jsint low, high; + if (*pc == JSOP_TABLESWITCH) { + pc += JUMP_OFFSET_LEN; + low = GET_JUMP_OFFSET(pc); + pc += JUMP_OFFSET_LEN; + high = GET_JUMP_OFFSET(pc); + } else { + pc += JUMPX_OFFSET_LEN; + low = GET_JUMPX_OFFSET(pc); + pc += JUMPX_OFFSET_LEN; + high = GET_JUMPX_OFFSET(pc); + } + + /* + * Really large tables won't fit in a page. This is a conservative check. + * If it matters in practice we need to go off-page. + */ + if ((high + 1 - low) * sizeof(intptr_t*) + 128 > (unsigned) LARGEST_UNDERRUN_PROT) + return switchop(); + + /* Generate switch LIR. */ + SwitchInfo* si = new (*traceMonitor->dataAlloc) SwitchInfo(); + si->count = high + 1 - low; + si->table = 0; + si->index = (uint32) -1; + LIns* diff = lir->ins2(LIR_sub, v_ins, lir->insImm(low)); + LIns* cmp = lir->ins2(LIR_ult, diff, lir->insImm(si->count)); + lir->insGuard(LIR_xf, cmp, createGuardRecord(snapshot(DEFAULT_EXIT))); + lir->insStorei(diff, lir->insImmPtr(&si->index), 0); + VMSideExit* exit = snapshot(CASE_EXIT); + exit->switchInfo = si; + LIns* guardIns = lir->insGuard(LIR_xtbl, diff, createGuardRecord(exit)); + fragment->lastIns = guardIns; + compile(&JS_TRACE_MONITOR(cx)); + return JSRS_STOP; +} +#endif + +static JS_ALWAYS_INLINE int32_t +UnboxBooleanOrUndefined(jsval v) +{ + /* Although this says 'special', we really only expect 3 special values: */ + JS_ASSERT(v == JSVAL_TRUE || v == JSVAL_FALSE || v == JSVAL_VOID); + return JSVAL_TO_SPECIAL(v); +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::switchop() +{ + jsval& v = stackval(-1); + LIns* v_ins = get(&v); + + /* No need to guard if the condition is constant. */ + if (v_ins->isconst() || v_ins->isconstq()) + return JSRS_CONTINUE; + if (isNumber(v)) { + jsdouble d = asNumber(v); + guard(true, + addName(lir->ins2(LIR_feq, v_ins, lir->insImmf(d)), + "guard(switch on numeric)"), + BRANCH_EXIT); + } else if (JSVAL_IS_STRING(v)) { + LIns* args[] = { v_ins, INS_CONSTSTR(JSVAL_TO_STRING(v)) }; + guard(true, + addName(lir->ins_eq0(lir->ins_eq0(lir->insCall(&js_EqualStrings_ci, args))), + "guard(switch on string)"), + BRANCH_EXIT); + } else if (JSVAL_IS_SPECIAL(v)) { + guard(true, + addName(lir->ins2(LIR_eq, v_ins, lir->insImm(UnboxBooleanOrUndefined(v))), + "guard(switch on boolean)"), + BRANCH_EXIT); + } else { + ABORT_TRACE("switch on object or null"); + } + return JSRS_CONTINUE; +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::inc(jsval& v, jsint incr, bool pre) +{ + LIns* v_ins = get(&v); + CHECK_STATUS(inc(v, v_ins, incr, pre)); + set(&v, v_ins); + return JSRS_CONTINUE; +} + +/* + * On exit, v_ins is the incremented unboxed value, and the appropriate value + * (pre- or post-increment as described by pre) is stacked. + */ +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::inc(jsval v, LIns*& v_ins, jsint incr, bool pre) +{ + LIns* v_after; + CHECK_STATUS(incHelper(v, v_ins, v_after, incr)); + + const JSCodeSpec& cs = js_CodeSpec[*cx->fp->regs->pc]; + JS_ASSERT(cs.ndefs == 1); + stack(-cs.nuses, pre ? v_after : v_ins); + v_ins = v_after; + return JSRS_CONTINUE; +} + +/* + * Do an increment operation without storing anything to the stack. + */ +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::incHelper(jsval v, LIns* v_ins, LIns*& v_after, jsint incr) +{ + if (!isNumber(v)) + ABORT_TRACE("can only inc numbers"); + v_after = alu(LIR_fadd, asNumber(v), incr, v_ins, lir->insImmf(incr)); + return JSRS_CONTINUE; +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::incProp(jsint incr, bool pre) +{ + jsval& l = stackval(-1); + if (JSVAL_IS_PRIMITIVE(l)) + ABORT_TRACE("incProp on primitive"); + + JSObject* obj = JSVAL_TO_OBJECT(l); + LIns* obj_ins = get(&l); + + uint32 slot; + LIns* v_ins; + CHECK_STATUS(prop(obj, obj_ins, &slot, &v_ins, NULL)); + + if (slot == SPROP_INVALID_SLOT) + ABORT_TRACE("incProp on invalid slot"); + + jsval& v = STOBJ_GET_SLOT(obj, slot); + CHECK_STATUS(inc(v, v_ins, incr, pre)); + + LIns* dslots_ins = NULL; + stobj_set_slot(obj_ins, slot, dslots_ins, box_jsval(v, v_ins)); + return JSRS_CONTINUE; +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::incElem(jsint incr, bool pre) +{ + jsval& r = stackval(-1); + jsval& l = stackval(-2); + jsval* vp; + LIns* v_ins; + LIns* addr_ins; + + if (JSVAL_IS_PRIMITIVE(l) || !JSVAL_IS_INT(r) || + !guardDenseArray(JSVAL_TO_OBJECT(l), get(&l))) { + return JSRS_STOP; + } + + CHECK_STATUS(denseArrayElement(l, r, vp, v_ins, addr_ins)); + if (!addr_ins) // if we read a hole, abort + return JSRS_STOP; + CHECK_STATUS(inc(*vp, v_ins, incr, pre)); + lir->insStorei(box_jsval(*vp, v_ins), addr_ins, 0); + return JSRS_CONTINUE; +} + +static bool +EvalCmp(LOpcode op, double l, double r) +{ + bool cond; + switch (op) { + case LIR_feq: + cond = (l == r); + break; + case LIR_flt: + cond = l < r; + break; + case LIR_fgt: + cond = l > r; + break; + case LIR_fle: + cond = l <= r; + break; + case LIR_fge: + cond = l >= r; + break; + default: + JS_NOT_REACHED("unexpected comparison op"); + return false; + } + return cond; +} + +static bool +EvalCmp(LOpcode op, JSString* l, JSString* r) +{ + if (op == LIR_feq) + return js_EqualStrings(l, r); + return EvalCmp(op, js_CompareStrings(l, r), 0); +} + +JS_REQUIRES_STACK void +TraceRecorder::strictEquality(bool equal, bool cmpCase) +{ + jsval& r = stackval(-1); + jsval& l = stackval(-2); + LIns* l_ins = get(&l); + LIns* r_ins = get(&r); + LIns* x; + bool cond; + + JSTraceType ltag = GetPromotedType(l); + if (ltag != GetPromotedType(r)) { + cond = !equal; + x = lir->insImm(cond); + } else if (ltag == TT_STRING) { + LIns* args[] = { r_ins, l_ins }; + x = lir->ins2i(LIR_eq, lir->insCall(&js_EqualStrings_ci, args), equal); + cond = js_EqualStrings(JSVAL_TO_STRING(l), JSVAL_TO_STRING(r)); + } else { + LOpcode op; + if (ltag == TT_DOUBLE) + op = LIR_feq; + else if (ltag == TT_NULL || ltag == TT_OBJECT || ltag == TT_FUNCTION) + op = LIR_peq; + else + op = LIR_eq; + x = lir->ins2(op, l_ins, r_ins); + if (!equal) + x = lir->ins_eq0(x); + cond = (ltag == TT_DOUBLE) + ? asNumber(l) == asNumber(r) + : l == r; + } + cond = (cond == equal); + + if (cmpCase) { + /* Only guard if the same path may not always be taken. */ + if (!x->isconst()) + guard(cond, x, BRANCH_EXIT); + return; + } + + set(&l, x); +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::equality(bool negate, bool tryBranchAfterCond) +{ + jsval& rval = stackval(-1); + jsval& lval = stackval(-2); + LIns* l_ins = get(&lval); + LIns* r_ins = get(&rval); + + return equalityHelper(lval, rval, l_ins, r_ins, negate, tryBranchAfterCond, lval); +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::equalityHelper(jsval l, jsval r, LIns* l_ins, LIns* r_ins, + bool negate, bool tryBranchAfterCond, + jsval& rval) +{ + LOpcode op = LIR_eq; + bool cond; + LIns* args[] = { NULL, NULL }; + + /* + * The if chain below closely mirrors that found in 11.9.3, in general + * deviating from that ordering of ifs only to account for SpiderMonkey's + * conflation of booleans and undefined and for the possibility of + * confusing objects and null. Note carefully the spec-mandated recursion + * in the final else clause, which terminates because Number == T recurs + * only if T is Object, but that must recur again to convert Object to + * primitive, and ToPrimitive throws if the object cannot be converted to + * a primitive value (which would terminate recursion). + */ + + if (GetPromotedType(l) == GetPromotedType(r)) { + if (JSVAL_TAG(l) == JSVAL_OBJECT || JSVAL_IS_SPECIAL(l)) { + if (JSVAL_TAG(l) == JSVAL_OBJECT && l) { + JSClass *clasp = OBJ_GET_CLASS(cx, JSVAL_TO_OBJECT(l)); + if ((clasp->flags & JSCLASS_IS_EXTENDED) && ((JSExtendedClass*) clasp)->equality) + ABORT_TRACE("Can't trace extended class equality operator"); + } + if (JSVAL_TAG(l) == JSVAL_OBJECT) + op = LIR_peq; + cond = (l == r); + } else if (JSVAL_IS_STRING(l)) { + args[0] = r_ins, args[1] = l_ins; + l_ins = lir->insCall(&js_EqualStrings_ci, args); + r_ins = lir->insImm(1); + cond = js_EqualStrings(JSVAL_TO_STRING(l), JSVAL_TO_STRING(r)); + } else { + JS_ASSERT(isNumber(l) && isNumber(r)); + cond = (asNumber(l) == asNumber(r)); + op = LIR_feq; + } + } else if (JSVAL_IS_NULL(l) && JSVAL_IS_SPECIAL(r)) { + l_ins = lir->insImm(JSVAL_TO_SPECIAL(JSVAL_VOID)); + cond = (r == JSVAL_VOID); + } else if (JSVAL_IS_SPECIAL(l) && JSVAL_IS_NULL(r)) { + r_ins = lir->insImm(JSVAL_TO_SPECIAL(JSVAL_VOID)); + cond = (l == JSVAL_VOID); + } else if (isNumber(l) && JSVAL_IS_STRING(r)) { + args[0] = r_ins, args[1] = cx_ins; + r_ins = lir->insCall(&js_StringToNumber_ci, args); + cond = (asNumber(l) == js_StringToNumber(cx, JSVAL_TO_STRING(r))); + op = LIR_feq; + } else if (JSVAL_IS_STRING(l) && isNumber(r)) { + args[0] = l_ins, args[1] = cx_ins; + l_ins = lir->insCall(&js_StringToNumber_ci, args); + cond = (js_StringToNumber(cx, JSVAL_TO_STRING(l)) == asNumber(r)); + op = LIR_feq; + } else { + if (JSVAL_IS_SPECIAL(l)) { + bool isVoid = JSVAL_IS_VOID(l); + guard(isVoid, + lir->ins2(LIR_eq, l_ins, INS_CONST(JSVAL_TO_SPECIAL(JSVAL_VOID))), + BRANCH_EXIT); + if (!isVoid) { + args[0] = l_ins, args[1] = cx_ins; + l_ins = lir->insCall(&js_BooleanOrUndefinedToNumber_ci, args); + l = (l == JSVAL_VOID) + ? DOUBLE_TO_JSVAL(cx->runtime->jsNaN) + : INT_TO_JSVAL(l == JSVAL_TRUE); + return equalityHelper(l, r, l_ins, r_ins, negate, + tryBranchAfterCond, rval); + } + } else if (JSVAL_IS_SPECIAL(r)) { + bool isVoid = JSVAL_IS_VOID(r); + guard(isVoid, + lir->ins2(LIR_eq, r_ins, INS_CONST(JSVAL_TO_SPECIAL(JSVAL_VOID))), + BRANCH_EXIT); + if (!isVoid) { + args[0] = r_ins, args[1] = cx_ins; + r_ins = lir->insCall(&js_BooleanOrUndefinedToNumber_ci, args); + r = (r == JSVAL_VOID) + ? DOUBLE_TO_JSVAL(cx->runtime->jsNaN) + : INT_TO_JSVAL(r == JSVAL_TRUE); + return equalityHelper(l, r, l_ins, r_ins, negate, + tryBranchAfterCond, rval); + } + } else { + if ((JSVAL_IS_STRING(l) || isNumber(l)) && !JSVAL_IS_PRIMITIVE(r)) { + ABORT_IF_XML(r); + return call_imacro(equality_imacros.any_obj); + } + if (!JSVAL_IS_PRIMITIVE(l) && (JSVAL_IS_STRING(r) || isNumber(r))) { + ABORT_IF_XML(l); + return call_imacro(equality_imacros.obj_any); + } + } + + l_ins = lir->insImm(0); + r_ins = lir->insImm(1); + cond = false; + } + + /* If the operands aren't numbers, compare them as integers. */ + LIns* x = lir->ins2(op, l_ins, r_ins); + if (negate) { + x = lir->ins_eq0(x); + cond = !cond; + } + + jsbytecode* pc = cx->fp->regs->pc; + + /* + * Don't guard if the same path is always taken. If it isn't, we have to + * fuse comparisons and the following branch, because the interpreter does + * that. + */ + if (tryBranchAfterCond) + fuseIf(pc + 1, cond, x); + + /* + * There is no need to write out the result of this comparison if the trace + * ends on this operation. + */ + if (pc[1] == JSOP_IFNE || pc[1] == JSOP_IFEQ) + CHECK_STATUS(checkTraceEnd(pc + 1)); + + /* + * We update the stack after the guard. This is safe since the guard bails + * out at the comparison and the interpreter will therefore re-execute the + * comparison. This way the value of the condition doesn't have to be + * calculated and saved on the stack in most cases. + */ + set(&rval, x); + + return JSRS_CONTINUE; +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::relational(LOpcode op, bool tryBranchAfterCond) +{ + jsval& r = stackval(-1); + jsval& l = stackval(-2); + LIns* x = NULL; + bool cond; + LIns* l_ins = get(&l); + LIns* r_ins = get(&r); + bool fp = false; + jsdouble lnum, rnum; + + /* + * 11.8.5 if either argument is an object with a function-valued valueOf + * property; if both arguments are objects with non-function-valued valueOf + * properties, abort. + */ + if (!JSVAL_IS_PRIMITIVE(l)) { + ABORT_IF_XML(l); + if (!JSVAL_IS_PRIMITIVE(r)) { + ABORT_IF_XML(r); + return call_imacro(binary_imacros.obj_obj); + } + return call_imacro(binary_imacros.obj_any); + } + if (!JSVAL_IS_PRIMITIVE(r)) { + ABORT_IF_XML(r); + return call_imacro(binary_imacros.any_obj); + } + + /* 11.8.5 steps 3, 16-21. */ + if (JSVAL_IS_STRING(l) && JSVAL_IS_STRING(r)) { + LIns* args[] = { r_ins, l_ins }; + l_ins = lir->insCall(&js_CompareStrings_ci, args); + r_ins = lir->insImm(0); + cond = EvalCmp(op, JSVAL_TO_STRING(l), JSVAL_TO_STRING(r)); + goto do_comparison; + } + + /* 11.8.5 steps 4-5. */ + if (!JSVAL_IS_NUMBER(l)) { + LIns* args[] = { l_ins, cx_ins }; + switch (JSVAL_TAG(l)) { + case JSVAL_SPECIAL: + l_ins = lir->insCall(&js_BooleanOrUndefinedToNumber_ci, args); + break; + case JSVAL_STRING: + l_ins = lir->insCall(&js_StringToNumber_ci, args); + break; + case JSVAL_OBJECT: + if (JSVAL_IS_NULL(l)) { + l_ins = lir->insImmf(0.0); + break; + } + // FALL THROUGH + case JSVAL_INT: + case JSVAL_DOUBLE: + default: + JS_NOT_REACHED("JSVAL_IS_NUMBER if int/double, objects should " + "have been handled at start of method"); + ABORT_TRACE("safety belt"); + } + } + if (!JSVAL_IS_NUMBER(r)) { + LIns* args[] = { r_ins, cx_ins }; + switch (JSVAL_TAG(r)) { + case JSVAL_SPECIAL: + r_ins = lir->insCall(&js_BooleanOrUndefinedToNumber_ci, args); + break; + case JSVAL_STRING: + r_ins = lir->insCall(&js_StringToNumber_ci, args); + break; + case JSVAL_OBJECT: + if (JSVAL_IS_NULL(r)) { + r_ins = lir->insImmf(0.0); + break; + } + // FALL THROUGH + case JSVAL_INT: + case JSVAL_DOUBLE: + default: + JS_NOT_REACHED("JSVAL_IS_NUMBER if int/double, objects should " + "have been handled at start of method"); + ABORT_TRACE("safety belt"); + } + } + { + jsval tmp = JSVAL_NULL; + JSAutoTempValueRooter tvr(cx, 1, &tmp); + + tmp = l; + lnum = js_ValueToNumber(cx, &tmp); + tmp = r; + rnum = js_ValueToNumber(cx, &tmp); + } + cond = EvalCmp(op, lnum, rnum); + fp = true; + + /* 11.8.5 steps 6-15. */ + do_comparison: + /* + * If the result is not a number or it's not a quad, we must use an integer + * compare. + */ + if (!fp) { + JS_ASSERT(op >= LIR_feq && op <= LIR_fge); + op = LOpcode(op + (LIR_eq - LIR_feq)); + } + x = lir->ins2(op, l_ins, r_ins); + + jsbytecode* pc = cx->fp->regs->pc; + + /* + * Don't guard if the same path is always taken. If it isn't, we have to + * fuse comparisons and the following branch, because the interpreter does + * that. + */ + if (tryBranchAfterCond) + fuseIf(pc + 1, cond, x); + + /* + * There is no need to write out the result of this comparison if the trace + * ends on this operation. + */ + if (pc[1] == JSOP_IFNE || pc[1] == JSOP_IFEQ) + CHECK_STATUS(checkTraceEnd(pc + 1)); + + /* + * We update the stack after the guard. This is safe since the guard bails + * out at the comparison and the interpreter will therefore re-execute the + * comparison. This way the value of the condition doesn't have to be + * calculated and saved on the stack in most cases. + */ + set(&l, x); + + return JSRS_CONTINUE; +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::unary(LOpcode op) +{ + jsval& v = stackval(-1); + bool intop = !(op & LIR64); + if (isNumber(v)) { + LIns* a = get(&v); + if (intop) + a = f2i(a); + a = lir->ins1(op, a); + if (intop) + a = lir->ins1(LIR_i2f, a); + set(&v, a); + return JSRS_CONTINUE; + } + return JSRS_STOP; +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::binary(LOpcode op) +{ + jsval& r = stackval(-1); + jsval& l = stackval(-2); + + if (!JSVAL_IS_PRIMITIVE(l)) { + ABORT_IF_XML(l); + if (!JSVAL_IS_PRIMITIVE(r)) { + ABORT_IF_XML(r); + return call_imacro(binary_imacros.obj_obj); + } + return call_imacro(binary_imacros.obj_any); + } + if (!JSVAL_IS_PRIMITIVE(r)) { + ABORT_IF_XML(r); + return call_imacro(binary_imacros.any_obj); + } + + bool intop = !(op & LIR64); + LIns* a = get(&l); + LIns* b = get(&r); + + bool leftIsNumber = isNumber(l); + jsdouble lnum = leftIsNumber ? asNumber(l) : 0; + + bool rightIsNumber = isNumber(r); + jsdouble rnum = rightIsNumber ? asNumber(r) : 0; + + if ((op >= LIR_sub && op <= LIR_ush) || // sub, mul, (callh), or, xor, (not,) lsh, rsh, ush + (op >= LIR_fsub && op <= LIR_fmod)) { // fsub, fmul, fdiv, fmod + LIns* args[2]; + if (JSVAL_IS_STRING(l)) { + args[0] = a; + args[1] = cx_ins; + a = lir->insCall(&js_StringToNumber_ci, args); + lnum = js_StringToNumber(cx, JSVAL_TO_STRING(l)); + leftIsNumber = true; + } + if (JSVAL_IS_STRING(r)) { + args[0] = b; + args[1] = cx_ins; + b = lir->insCall(&js_StringToNumber_ci, args); + rnum = js_StringToNumber(cx, JSVAL_TO_STRING(r)); + rightIsNumber = true; + } + } + if (JSVAL_IS_SPECIAL(l)) { + LIns* args[] = { a, cx_ins }; + a = lir->insCall(&js_BooleanOrUndefinedToNumber_ci, args); + lnum = js_BooleanOrUndefinedToNumber(cx, JSVAL_TO_SPECIAL(l)); + leftIsNumber = true; + } + if (JSVAL_IS_SPECIAL(r)) { + LIns* args[] = { b, cx_ins }; + b = lir->insCall(&js_BooleanOrUndefinedToNumber_ci, args); + rnum = js_BooleanOrUndefinedToNumber(cx, JSVAL_TO_SPECIAL(r)); + rightIsNumber = true; + } + if (leftIsNumber && rightIsNumber) { + if (intop) { + LIns *args[] = { a }; + a = lir->insCall(op == LIR_ush ? &js_DoubleToUint32_ci : &js_DoubleToInt32_ci, args); + b = f2i(b); + } + a = alu(op, lnum, rnum, a, b); + if (intop) + a = lir->ins1(op == LIR_ush ? LIR_u2f : LIR_i2f, a); + set(&l, a); + return JSRS_CONTINUE; + } + return JSRS_STOP; +} + +void +TraceRecorder::guardShape(LIns* obj_ins, JSObject* obj, uint32 shape, const char* guardName, + LIns* map_ins, VMSideExit* exit) +{ + LIns* shape_ins = addName(lir->insLoad(LIR_ld, map_ins, offsetof(JSScope, shape)), "shape"); + guard(true, + addName(lir->ins2i(LIR_eq, shape_ins, shape), guardName), + exit); +} + +JS_STATIC_ASSERT(offsetof(JSObjectOps, objectMap) == 0); + +inline LIns* +TraceRecorder::map(LIns *obj_ins) +{ + return addName(lir->insLoad(LIR_ldp, obj_ins, (int) offsetof(JSObject, map)), "map"); +} + +bool +TraceRecorder::map_is_native(JSObjectMap* map, LIns* map_ins, LIns*& ops_ins, size_t op_offset) +{ + JS_ASSERT(op_offset < sizeof(JSObjectOps)); + JS_ASSERT(op_offset % sizeof(void *) == 0); + +#define OP(ops) (*(void **) ((uint8 *) (ops) + op_offset)) + void* ptr = OP(map->ops); + if (ptr != OP(&js_ObjectOps)) + return false; +#undef OP + + ops_ins = addName(lir->insLoad(LIR_ldcp, map_ins, int(offsetof(JSObjectMap, ops))), "ops"); + LIns* n = lir->insLoad(LIR_ldcp, ops_ins, op_offset); + guard(true, + addName(lir->ins2(LIR_peq, n, INS_CONSTPTR(ptr)), "guard(native-map)"), + BRANCH_EXIT); + + return true; +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::guardNativePropertyOp(JSObject* aobj, LIns* map_ins) +{ + /* + * Interpreter calls to PROPERTY_CACHE_TEST guard on native object ops + * which is required to use native objects (those whose maps are scopes), + * or even more narrow conditions required because the cache miss case + * will call a particular object-op (js_GetProperty, js_SetProperty). + * + * We parameterize using offsetof and guard on match against the hook at + * the given offset in js_ObjectOps. TraceRecorder::record_JSOP_SETPROP + * guards the js_SetProperty case. + */ + uint32 format = js_CodeSpec[*cx->fp->regs->pc].format; + uint32 mode = JOF_MODE(format); + + // No need to guard native-ness of global object. + JS_ASSERT(OBJ_IS_NATIVE(globalObj)); + if (aobj != globalObj) { + size_t op_offset = offsetof(JSObjectOps, objectMap); + if (mode == JOF_PROP || mode == JOF_VARPROP) { + op_offset = (format & JOF_SET) + ? offsetof(JSObjectOps, setProperty) + : offsetof(JSObjectOps, getProperty); + } else { + JS_ASSERT(mode == JOF_NAME); + } + + LIns* ops_ins; + if (!map_is_native(aobj->map, map_ins, ops_ins, op_offset)) + ABORT_TRACE("non-native map"); + } + return JSRS_CONTINUE; +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::test_property_cache(JSObject* obj, LIns* obj_ins, JSObject*& obj2, jsuword& pcval) +{ + jsbytecode* pc = cx->fp->regs->pc; + JS_ASSERT(*pc != JSOP_INITPROP && *pc != JSOP_SETNAME && *pc != JSOP_SETPROP); + + // Mimic the interpreter's special case for dense arrays by skipping up one + // hop along the proto chain when accessing a named (not indexed) property, + // typically to find Array.prototype methods. + JSObject* aobj = obj; + if (OBJ_IS_DENSE_ARRAY(cx, obj)) { + guardDenseArray(obj, obj_ins, BRANCH_EXIT); + aobj = OBJ_GET_PROTO(cx, obj); + obj_ins = stobj_get_proto(obj_ins); + } + + if (!OBJ_IS_NATIVE(obj)) + ABORT_TRACE("non-native object"); + + LIns* map_ins = map(obj_ins); + + CHECK_STATUS(guardNativePropertyOp(aobj, map_ins)); + + JSAtom* atom; + JSPropCacheEntry* entry; + PROPERTY_CACHE_TEST(cx, pc, aobj, obj2, entry, atom); + if (!atom) { + // Null atom means that obj2 is locked and must now be unlocked. + JS_UNLOCK_OBJ(cx, obj2); + } else { + // Miss: pre-fill the cache for the interpreter, as well as for our needs. + jsid id = ATOM_TO_JSID(atom); + JSProperty* prop; + if (JOF_OPMODE(*pc) == JOF_NAME) { + JS_ASSERT(aobj == obj); + + JSTraceMonitor &localtm = *traceMonitor; + entry = js_FindPropertyHelper(cx, id, true, &obj, &obj2, &prop); + + /* js_FindPropertyHelper can reenter the interpreter and kill |this|. */ + if (!localtm.recorder) + return JSRS_STOP; + + if (!entry) + ABORT_TRACE_ERROR("error in js_FindPropertyHelper"); + if (entry == JS_NO_PROP_CACHE_FILL) + ABORT_TRACE("cannot cache name"); + } else { + JSTraceMonitor &localtm = *traceMonitor; + JSContext *localcx = cx; + int protoIndex = js_LookupPropertyWithFlags(cx, aobj, id, + cx->resolveFlags, + &obj2, &prop); + + /* js_LookupPropertyWithFlags can reenter the interpreter and kill |this|. */ + if (!localtm.recorder) { + if (prop) + obj2->dropProperty(localcx, prop); + return JSRS_STOP; + } + + if (protoIndex < 0) + ABORT_TRACE_ERROR("error in js_LookupPropertyWithFlags"); + + if (prop) { + if (!OBJ_IS_NATIVE(obj2)) { + obj2->dropProperty(cx, prop); + ABORT_TRACE("property found on non-native object"); + } + entry = js_FillPropertyCache(cx, aobj, 0, protoIndex, obj2, + (JSScopeProperty*) prop, false); + JS_ASSERT(entry); + if (entry == JS_NO_PROP_CACHE_FILL) + entry = NULL; + } + + } + + if (!prop) { + // Propagate obj from js_FindPropertyHelper to record_JSOP_BINDNAME + // via our obj2 out-parameter. If we are recording JSOP_SETNAME and + // the global it's assigning does not yet exist, create it. + obj2 = obj; + + // Use PCVAL_NULL to return "no such property" to our caller. + pcval = PCVAL_NULL; + return JSRS_CONTINUE; + } + + obj2->dropProperty(cx, prop); + if (!entry) + ABORT_TRACE("failed to fill property cache"); + } + +#ifdef JS_THREADSAFE + // There's a potential race in any JS_THREADSAFE embedding that's nuts + // enough to share mutable objects on the scope or proto chain, but we + // don't care about such insane embeddings. Anyway, the (scope, proto) + // entry->vcap coordinates must reach obj2 from aobj at this point. + JS_ASSERT(cx->requestDepth); +#endif + + return guardPropertyCacheHit(obj_ins, map_ins, aobj, obj2, entry, pcval); +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::guardPropertyCacheHit(LIns* obj_ins, + LIns* map_ins, + JSObject* aobj, + JSObject* obj2, + JSPropCacheEntry* entry, + jsuword& pcval) +{ + VMSideExit* exit = snapshot(BRANCH_EXIT); + + uint32 vshape = PCVCAP_SHAPE(entry->vcap); + + // Check for first-level cache hit and guard on kshape if possible. + // Otherwise guard on key object exact match. + if (PCVCAP_TAG(entry->vcap) <= 1) { + if (aobj != globalObj) + guardShape(obj_ins, aobj, entry->kshape, "guard_kshape", map_ins, exit); + + if (entry->adding()) { + if (aobj == globalObj) + ABORT_TRACE("adding a property to the global object"); + + LIns *vshape_ins = addName( + lir->insLoad(LIR_ld, + addName(lir->insLoad(LIR_ldcp, cx_ins, offsetof(JSContext, runtime)), + "runtime"), + offsetof(JSRuntime, protoHazardShape)), + "protoHazardShape"); + guard(true, + addName(lir->ins2i(LIR_eq, vshape_ins, vshape), "guard_protoHazardShape"), + MISMATCH_EXIT); + } + } else { +#ifdef DEBUG + JSOp op = js_GetOpcode(cx, cx->fp->script, cx->fp->regs->pc); + JSAtom *pcatom; + if (op == JSOP_LENGTH) { + pcatom = cx->runtime->atomState.lengthAtom; + } else { + ptrdiff_t pcoff = (JOF_TYPE(js_CodeSpec[op].format) == JOF_SLOTATOM) ? SLOTNO_LEN : 0; + GET_ATOM_FROM_BYTECODE(cx->fp->script, cx->fp->regs->pc, pcoff, pcatom); + } + JS_ASSERT(entry->kpc == (jsbytecode *) pcatom); + JS_ASSERT(entry->kshape == jsuword(aobj)); +#endif + if (aobj != globalObj && !obj_ins->isconstp()) { + guard(true, + addName(lir->ins2(LIR_peq, obj_ins, INS_CONSTOBJ(aobj)), "guard_kobj"), + exit); + } + } + + // For any hit that goes up the scope and/or proto chains, we will need to + // guard on the shape of the object containing the property. + if (PCVCAP_TAG(entry->vcap) >= 1) { + JS_ASSERT(OBJ_SHAPE(obj2) == vshape); + + LIns* obj2_ins; + if (PCVCAP_TAG(entry->vcap) == 1) { + // Duplicate the special case in PROPERTY_CACHE_TEST. + obj2_ins = addName(stobj_get_proto(obj_ins), "proto"); + guard(false, lir->ins_peq0(obj2_ins), exit); + } else { + obj2_ins = INS_CONSTOBJ(obj2); + } + guardShape(obj2_ins, obj2, vshape, "guard_vshape", map(obj2_ins), exit); + } + + pcval = entry->vword; + return JSRS_CONTINUE; +} + +void +TraceRecorder::stobj_set_fslot(LIns *obj_ins, unsigned slot, LIns* v_ins) +{ + lir->insStorei(v_ins, obj_ins, offsetof(JSObject, fslots) + slot * sizeof(jsval)); +} + +void +TraceRecorder::stobj_set_dslot(LIns *obj_ins, unsigned slot, LIns*& dslots_ins, LIns* v_ins) +{ + if (!dslots_ins) + dslots_ins = lir->insLoad(LIR_ldp, obj_ins, offsetof(JSObject, dslots)); + lir->insStorei(v_ins, dslots_ins, slot * sizeof(jsval)); +} + +void +TraceRecorder::stobj_set_slot(LIns* obj_ins, unsigned slot, LIns*& dslots_ins, LIns* v_ins) +{ + if (slot < JS_INITIAL_NSLOTS) { + stobj_set_fslot(obj_ins, slot, v_ins); + } else { + stobj_set_dslot(obj_ins, slot - JS_INITIAL_NSLOTS, dslots_ins, v_ins); + } +} + +LIns* +TraceRecorder::stobj_get_fslot(LIns* obj_ins, unsigned slot) +{ + JS_ASSERT(slot < JS_INITIAL_NSLOTS); + return lir->insLoad(LIR_ldp, obj_ins, offsetof(JSObject, fslots) + slot * sizeof(jsval)); +} + +LIns* +TraceRecorder::stobj_get_dslot(LIns* obj_ins, unsigned index, LIns*& dslots_ins) +{ + if (!dslots_ins) + dslots_ins = lir->insLoad(LIR_ldp, obj_ins, offsetof(JSObject, dslots)); + return lir->insLoad(LIR_ldp, dslots_ins, index * sizeof(jsval)); +} + +LIns* +TraceRecorder::stobj_get_slot(LIns* obj_ins, unsigned slot, LIns*& dslots_ins) +{ + if (slot < JS_INITIAL_NSLOTS) + return stobj_get_fslot(obj_ins, slot); + return stobj_get_dslot(obj_ins, slot - JS_INITIAL_NSLOTS, dslots_ins); +} + +JSRecordingStatus +TraceRecorder::native_get(LIns* obj_ins, LIns* pobj_ins, JSScopeProperty* sprop, + LIns*& dslots_ins, LIns*& v_ins) +{ + if (!SPROP_HAS_STUB_GETTER(sprop)) + return JSRS_STOP; + + if (sprop->slot != SPROP_INVALID_SLOT) + v_ins = stobj_get_slot(pobj_ins, sprop->slot, dslots_ins); + else + v_ins = INS_CONST(JSVAL_TO_SPECIAL(JSVAL_VOID)); + return JSRS_CONTINUE; +} + +JS_REQUIRES_STACK LIns* +TraceRecorder::box_jsval(jsval v, LIns* v_ins) +{ + if (isNumber(v)) { + LIns* args[] = { v_ins, cx_ins }; + v_ins = lir->insCall(&js_BoxDouble_ci, args); + guard(false, lir->ins2(LIR_peq, v_ins, INS_CONSTWORD(JSVAL_ERROR_COOKIE)), + OOM_EXIT); + return v_ins; + } + switch (JSVAL_TAG(v)) { + case JSVAL_SPECIAL: + return lir->ins2(LIR_pior, lir->ins2i(LIR_pilsh, lir->ins_u2p(v_ins), JSVAL_TAGBITS), + INS_CONSTWORD(JSVAL_SPECIAL)); + case JSVAL_OBJECT: + return v_ins; + default: + JS_ASSERT(JSVAL_TAG(v) == JSVAL_STRING); + return lir->ins2(LIR_pior, v_ins, INS_CONSTWORD(JSVAL_STRING)); + } +} + +JS_REQUIRES_STACK LIns* +TraceRecorder::unbox_jsval(jsval v, LIns* v_ins, VMSideExit* exit) +{ + if (isNumber(v)) { + // JSVAL_IS_NUMBER(v) + guard(false, + lir->ins_eq0(lir->ins2(LIR_or, + p2i(lir->ins2(LIR_piand, v_ins, INS_CONSTWORD(JSVAL_INT))), + lir->ins2(LIR_peq, + lir->ins2(LIR_piand, v_ins, + INS_CONSTWORD(JSVAL_TAGMASK)), + INS_CONSTWORD(JSVAL_DOUBLE)))), + exit); + LIns* args[] = { v_ins }; + return lir->insCall(&js_UnboxDouble_ci, args); + } + switch (JSVAL_TAG(v)) { + case JSVAL_SPECIAL: + guard(true, + lir->ins2(LIR_peq, + lir->ins2(LIR_piand, v_ins, INS_CONSTWORD(JSVAL_TAGMASK)), + INS_CONSTWORD(JSVAL_SPECIAL)), + exit); + return p2i(lir->ins2i(LIR_pursh, v_ins, JSVAL_TAGBITS)); + + case JSVAL_OBJECT: + if (JSVAL_IS_NULL(v)) { + // JSVAL_NULL maps to type TT_NULL, so insist that v_ins == 0 here. + guard(true, lir->ins_peq0(v_ins), exit); + } else { + guard(false, lir->ins_peq0(v_ins), exit); + guard(true, + lir->ins2(LIR_peq, + lir->ins2(LIR_piand, v_ins, INS_CONSTWORD(JSVAL_TAGMASK)), + INS_CONSTWORD(JSVAL_OBJECT)), + exit); + + /* + * LIR_ldcp is ok to use here even though Array classword can + * change, because no object's classword can ever change from + * &js_ArrayClass to &js_FunctionClass. + */ + guard(HAS_FUNCTION_CLASS(JSVAL_TO_OBJECT(v)), + lir->ins2(LIR_peq, + lir->ins2(LIR_piand, + lir->insLoad(LIR_ldcp, v_ins, offsetof(JSObject, classword)), + INS_CONSTWORD(~JSSLOT_CLASS_MASK_BITS)), + INS_CONSTPTR(&js_FunctionClass)), + exit); + } + return v_ins; + + default: + JS_ASSERT(JSVAL_TAG(v) == JSVAL_STRING); + guard(true, + lir->ins2(LIR_peq, + lir->ins2(LIR_piand, v_ins, INS_CONSTWORD(JSVAL_TAGMASK)), + INS_CONSTWORD(JSVAL_STRING)), + exit); + return lir->ins2(LIR_piand, v_ins, addName(lir->insImmWord(~JSVAL_TAGMASK), + "~JSVAL_TAGMASK")); + } +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::getThis(LIns*& this_ins) +{ + /* + * js_ComputeThisForFrame updates cx->fp->argv[-1], so sample it into 'original' first. + */ + jsval original = JSVAL_NULL; + if (cx->fp->argv) { + original = cx->fp->argv[-1]; + if (!JSVAL_IS_PRIMITIVE(original) && + guardClass(JSVAL_TO_OBJECT(original), get(&cx->fp->argv[-1]), &js_WithClass, snapshot(MISMATCH_EXIT))) { + ABORT_TRACE("can't trace getThis on With object"); + } + } + + JSObject* thisObj = js_ComputeThisForFrame(cx, cx->fp); + if (!thisObj) + ABORT_TRACE_ERROR("js_ComputeThisForName failed"); + + /* In global code, bake in the global object as 'this' object. */ + if (!cx->fp->callee()) { + JS_ASSERT(callDepth == 0); + this_ins = INS_CONSTOBJ(thisObj); + + /* + * We don't have argv[-1] in global code, so we don't update the + * tracker here. + */ + return JSRS_CONTINUE; + } + + jsval& thisv = cx->fp->argv[-1]; + JS_ASSERT(JSVAL_IS_OBJECT(thisv)); + + /* + * Traces type-specialize between null and objects, so if we currently see + * a null value in argv[-1], this trace will only match if we see null at + * runtime as well. Bake in the global object as 'this' object, updating + * the tracker as well. We can only detect this condition prior to calling + * js_ComputeThisForFrame, since it updates the interpreter's copy of + * argv[-1]. + */ + JSClass* clasp = NULL;; + if (JSVAL_IS_NULL(original) || + (((clasp = STOBJ_GET_CLASS(JSVAL_TO_OBJECT(original))) == &js_CallClass) || + (clasp == &js_BlockClass))) { + if (clasp) + guardClass(JSVAL_TO_OBJECT(original), get(&thisv), clasp, snapshot(BRANCH_EXIT)); + JS_ASSERT(!JSVAL_IS_PRIMITIVE(thisv)); + if (thisObj != globalObj) + ABORT_TRACE("global object was wrapped while recording"); + this_ins = INS_CONSTOBJ(thisObj); + set(&thisv, this_ins); + return JSRS_CONTINUE; + } + + this_ins = get(&thisv); + + JSObject* wrappedGlobal = globalObj->thisObject(cx); + if (!wrappedGlobal) + ABORT_TRACE_ERROR("globalObj->thisObject hook threw in getThis"); + + /* + * The only unwrapped object that needs to be wrapped that we can get here + * is the global object obtained throught the scope chain. + */ + this_ins = lir->ins_choose(lir->ins_peq0(stobj_get_parent(this_ins)), + INS_CONSTOBJ(wrappedGlobal), + this_ins); + return JSRS_CONTINUE; +} + + +LIns* +TraceRecorder::getStringLength(LIns* str_ins) +{ + LIns* len_ins = lir->insLoad(LIR_ldp, str_ins, (int)offsetof(JSString, mLength)); + + LIns* masked_len_ins = lir->ins2(LIR_piand, + len_ins, + INS_CONSTWORD(JSString::LENGTH_MASK)); + + LIns* real_len = + lir->ins_choose(lir->ins_peq0(lir->ins2(LIR_piand, + len_ins, + INS_CONSTWORD(JSString::DEPENDENT))), + masked_len_ins, + lir->ins_choose(lir->ins_peq0(lir->ins2(LIR_piand, + len_ins, + INS_CONSTWORD(JSString::PREFIX))), + lir->ins2(LIR_piand, + len_ins, + INS_CONSTWORD(JSString::DEPENDENT_LENGTH_MASK)), + masked_len_ins)); + return p2i(real_len); +} + +JS_REQUIRES_STACK bool +TraceRecorder::guardClass(JSObject* obj, LIns* obj_ins, JSClass* clasp, VMSideExit* exit) +{ + bool cond = STOBJ_GET_CLASS(obj) == clasp; + + LIns* class_ins = lir->insLoad(LIR_ldp, obj_ins, offsetof(JSObject, classword)); + class_ins = lir->ins2(LIR_piand, class_ins, INS_CONSTWORD(~JSSLOT_CLASS_MASK_BITS)); + + char namebuf[32]; + JS_snprintf(namebuf, sizeof namebuf, "guard(class is %s)", clasp->name); + guard(cond, addName(lir->ins2(LIR_peq, class_ins, INS_CONSTPTR(clasp)), namebuf), exit); + return cond; +} + +JS_REQUIRES_STACK bool +TraceRecorder::guardDenseArray(JSObject* obj, LIns* obj_ins, ExitType exitType) +{ + return guardClass(obj, obj_ins, &js_ArrayClass, snapshot(exitType)); +} + +JS_REQUIRES_STACK bool +TraceRecorder::guardDenseArray(JSObject* obj, LIns* obj_ins, VMSideExit* exit) +{ + return guardClass(obj, obj_ins, &js_ArrayClass, exit); +} + +JS_REQUIRES_STACK bool +TraceRecorder::guardHasPrototype(JSObject* obj, LIns* obj_ins, + JSObject** pobj, LIns** pobj_ins, + VMSideExit* exit) +{ + *pobj = obj->getProto(); + *pobj_ins = stobj_get_proto(obj_ins); + + bool cond = *pobj == NULL; + guard(cond, addName(lir->ins_peq0(*pobj_ins), "guard(proto-not-null)"), exit); + return !cond; +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::guardPrototypeHasNoIndexedProperties(JSObject* obj, LIns* obj_ins, ExitType exitType) +{ + /* + * Guard that no object along the prototype chain has any indexed + * properties which might become visible through holes in the array. + */ + VMSideExit* exit = snapshot(exitType); + + if (js_PrototypeHasIndexedProperties(cx, obj)) + return JSRS_STOP; + + while (guardHasPrototype(obj, obj_ins, &obj, &obj_ins, exit)) + guardShape(obj_ins, obj, OBJ_SHAPE(obj), "guard(shape)", map(obj_ins), exit); + return JSRS_CONTINUE; +} + +JSRecordingStatus +TraceRecorder::guardNotGlobalObject(JSObject* obj, LIns* obj_ins) +{ + if (obj == globalObj) + ABORT_TRACE("reference aliases global object"); + guard(false, lir->ins2(LIR_peq, obj_ins, INS_CONSTOBJ(globalObj)), MISMATCH_EXIT); + return JSRS_CONTINUE; +} + +JS_REQUIRES_STACK void +TraceRecorder::clearFrameSlotsFromCache() +{ + /* + * Clear out all slots of this frame in the nativeFrameTracker. Different + * locations on the VM stack might map to different locations on the native + * stack depending on the number of arguments (i.e.) of the next call, so + * we have to make sure we map those in to the cache with the right + * offsets. + */ + JSStackFrame* fp = cx->fp; + jsval* vp; + jsval* vpstop; + + /* + * Duplicate native stack layout computation: see VisitFrameSlots header comment. + * This doesn't do layout arithmetic, but it must clear out all the slots defined as + * imported by VisitFrameSlots. + */ + if (fp->argv) { + vp = &fp->argv[-2]; + vpstop = &fp->argv[argSlots(fp)]; + while (vp < vpstop) + nativeFrameTracker.set(vp++, (LIns*)0); + nativeFrameTracker.set(&fp->argsobj, (LIns*)0); + } + vp = &fp->slots[0]; + vpstop = &fp->slots[fp->script->nslots]; + while (vp < vpstop) + nativeFrameTracker.set(vp++, (LIns*)0); +} + +/* + * If we have created an |arguments| object for the frame, we must copy the + * argument values into the object as properties in case it is used after + * this frame returns. + */ +JS_REQUIRES_STACK void +TraceRecorder::putArguments() +{ + if (cx->fp->argsobj && cx->fp->argc) { + LIns* argsobj_ins = get(&cx->fp->argsobj); + LIns* args_ins = lir->insAlloc(sizeof(jsval) * cx->fp->argc); + for (uintN i = 0; i < cx->fp->argc; ++i) { + LIns* arg_ins = box_jsval(cx->fp->argv[i], get(&cx->fp->argv[i])); + lir->insStorei(arg_ins, args_ins, i * sizeof(jsval)); + } + LIns* args[] = { args_ins, argsobj_ins, cx_ins }; + lir->insCall(&js_PutArguments_ci, args); + } +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_EnterFrame() +{ + JSStackFrame* fp = cx->fp; + + if (++callDepth >= MAX_CALLDEPTH) + ABORT_TRACE("exceeded maximum call depth"); + + // FIXME: Allow and attempt to inline a single level of recursion until we compile + // recursive calls as independent trees (459301). + if (fp->script == fp->down->script && fp->down->down && fp->down->down->script == fp->script) + ABORT_TRACE("recursive call"); + + debug_only_printf(LC_TMTracer, "EnterFrame %s, callDepth=%d\n", + js_AtomToPrintableString(cx, cx->fp->fun->atom), + callDepth); + debug_only_stmt( + if (js_LogController.lcbits & LC_TMRecorder) { + js_Disassemble(cx, cx->fp->script, JS_TRUE, stdout); + debug_only_print0(LC_TMTracer, "----\n"); + } + ) + LIns* void_ins = INS_VOID(); + + // Duplicate native stack layout computation: see VisitFrameSlots header comment. + // This doesn't do layout arithmetic, but it must initialize in the tracker all the + // slots defined as imported by VisitFrameSlots. + jsval* vp = &fp->argv[fp->argc]; + jsval* vpstop = vp + ptrdiff_t(fp->fun->nargs) - ptrdiff_t(fp->argc); + while (vp < vpstop) { + if (vp >= fp->down->regs->sp) + nativeFrameTracker.set(vp, (LIns*)0); + set(vp++, void_ins, true); + } + + vp = &fp->slots[0]; + vpstop = vp + fp->script->nfixed; + while (vp < vpstop) + set(vp++, void_ins, true); + set(&fp->argsobj, INS_NULL(), true); + return JSRS_CONTINUE; +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_LeaveFrame() +{ + debug_only_stmt( + if (cx->fp->fun) + debug_only_printf(LC_TMTracer, + "LeaveFrame (back to %s), callDepth=%d\n", + js_AtomToPrintableString(cx, cx->fp->fun->atom), + callDepth); + ); + if (callDepth-- <= 0) + ABORT_TRACE("returned out of a loop we started tracing"); + + // LeaveFrame gets called after the interpreter popped the frame and + // stored rval, so cx->fp not cx->fp->down, and -1 not 0. + atoms = FrameAtomBase(cx, cx->fp); + set(&stackval(-1), rval_ins, true); + return JSRS_CONTINUE; +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_PUSH() +{ + stack(0, INS_CONST(JSVAL_TO_SPECIAL(JSVAL_VOID))); + return JSRS_CONTINUE; +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_POPV() +{ + jsval& rval = stackval(-1); + LIns *rval_ins = box_jsval(rval, get(&rval)); + + // Store it in cx->fp->rval. NB: Tricky dependencies. cx->fp is the right + // frame because POPV appears only in global and eval code and we don't + // trace JSOP_EVAL or leaving the frame where tracing started. + LIns *fp_ins = lir->insLoad(LIR_ldp, cx_ins, offsetof(JSContext, fp)); + lir->insStorei(rval_ins, fp_ins, offsetof(JSStackFrame, rval)); + return JSRS_CONTINUE; +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_ENTERWITH() +{ + return JSRS_STOP; +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_LEAVEWITH() +{ + return JSRS_STOP; +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_RETURN() +{ + /* A return from callDepth 0 terminates the current loop. */ + if (callDepth == 0) { + AUDIT(returnLoopExits); + endLoop(); + return JSRS_STOP; + } + + putArguments(); + + /* If we inlined this function call, make the return value available to the caller code. */ + jsval& rval = stackval(-1); + JSStackFrame *fp = cx->fp; + if ((cx->fp->flags & JSFRAME_CONSTRUCTING) && JSVAL_IS_PRIMITIVE(rval)) { + JS_ASSERT(OBJECT_TO_JSVAL(fp->thisp) == fp->argv[-1]); + rval_ins = get(&fp->argv[-1]); + } else { + rval_ins = get(&rval); + } + debug_only_printf(LC_TMTracer, + "returning from %s\n", + js_AtomToPrintableString(cx, cx->fp->fun->atom)); + clearFrameSlotsFromCache(); + + return JSRS_CONTINUE; +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_GOTO() +{ + /* + * If we hit a break or a continue to an outer loop, end the loop and + * generate an always-taken loop exit guard. For other downward gotos + * (like if/else) continue recording. + */ + jssrcnote* sn = js_GetSrcNote(cx->fp->script, cx->fp->regs->pc); + + if (sn && (SN_TYPE(sn) == SRC_BREAK || SN_TYPE(sn) == SRC_CONT2LABEL)) { + AUDIT(breakLoopExits); + endLoop(); + return JSRS_STOP; + } + return JSRS_CONTINUE; +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_IFEQ() +{ + trackCfgMerges(cx->fp->regs->pc); + return ifop(); +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_IFNE() +{ + return ifop(); +} + +LIns* +TraceRecorder::newArguments() +{ + LIns* global_ins = INS_CONSTOBJ(globalObj); + LIns* argc_ins = INS_CONST(cx->fp->argc); + LIns* callee_ins = get(&cx->fp->argv[-2]); + LIns* argv_ins = cx->fp->argc + ? lir->ins2(LIR_piadd, lirbuf->sp, + lir->insImmWord(-treeInfo->nativeStackBase + nativeStackOffset(&cx->fp->argv[0]))) + : INS_CONSTPTR((void *) 2); + js_ArgsPrivateNative *apn = js_ArgsPrivateNative::create(*traceMonitor->dataAlloc, cx->fp->argc); + for (uintN i = 0; i < cx->fp->argc; ++i) { + apn->typemap()[i] = determineSlotType(&cx->fp->argv[i]); + } + + LIns* args[] = { INS_CONSTPTR(apn), argv_ins, callee_ins, argc_ins, global_ins, cx_ins }; + LIns* call_ins = lir->insCall(&js_Arguments_ci, args); + guard(false, lir->ins_peq0(call_ins), OOM_EXIT); + return call_ins; +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_ARGUMENTS() +{ + if (cx->fp->flags & JSFRAME_OVERRIDE_ARGS) + ABORT_TRACE("Can't trace |arguments| if |arguments| is assigned to"); + + LIns* a_ins = get(&cx->fp->argsobj); + LIns* args_ins; + if (a_ins->opcode() == LIR_int) { + // |arguments| is set to 0 by EnterFrame on this trace, so call to create it. + args_ins = newArguments(); + } else { + // Generate LIR to create arguments only if it has not already been created. + + LIns* mem_ins = lir->insAlloc(sizeof(jsval)); + + LIns* br1 = lir->insBranch(LIR_jt, lir->ins_peq0(a_ins), NULL); + lir->insStorei(a_ins, mem_ins, 0); + LIns* br2 = lir->insBranch(LIR_j, NULL, NULL); + + LIns* label1 = lir->ins0(LIR_label); + br1->setTarget(label1); + + LIns* call_ins = newArguments(); + lir->insStorei(call_ins, mem_ins, 0); + + LIns* label2 = lir->ins0(LIR_label); + br2->setTarget(label2); + + args_ins = lir->insLoad(LIR_ldp, mem_ins, 0); + } + + stack(0, args_ins); + set(&cx->fp->argsobj, args_ins); + return JSRS_CONTINUE; +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_DUP() +{ + stack(0, get(&stackval(-1))); + return JSRS_CONTINUE; +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_DUP2() +{ + stack(0, get(&stackval(-2))); + stack(1, get(&stackval(-1))); + return JSRS_CONTINUE; +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_SWAP() +{ + jsval& l = stackval(-2); + jsval& r = stackval(-1); + LIns* l_ins = get(&l); + LIns* r_ins = get(&r); + set(&r, l_ins); + set(&l, r_ins); + return JSRS_CONTINUE; +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_PICK() +{ + jsval* sp = cx->fp->regs->sp; + jsint n = cx->fp->regs->pc[1]; + JS_ASSERT(sp - (n+1) >= StackBase(cx->fp)); + LIns* top = get(sp - (n+1)); + for (jsint i = 0; i < n; ++i) + set(sp - (n+1) + i, get(sp - n + i)); + set(&sp[-1], top); + return JSRS_CONTINUE; +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_SETCONST() +{ + return JSRS_STOP; +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_BITOR() +{ + return binary(LIR_or); +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_BITXOR() +{ + return binary(LIR_xor); +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_BITAND() +{ + return binary(LIR_and); +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_EQ() +{ + return equality(false, true); +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_NE() +{ + return equality(true, true); +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_LT() +{ + return relational(LIR_flt, true); +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_LE() +{ + return relational(LIR_fle, true); +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_GT() +{ + return relational(LIR_fgt, true); +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_GE() +{ + return relational(LIR_fge, true); +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_LSH() +{ + return binary(LIR_lsh); +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_RSH() +{ + return binary(LIR_rsh); +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_URSH() +{ + return binary(LIR_ush); +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_ADD() +{ + jsval& r = stackval(-1); + jsval& l = stackval(-2); + + if (!JSVAL_IS_PRIMITIVE(l)) { + ABORT_IF_XML(l); + if (!JSVAL_IS_PRIMITIVE(r)) { + ABORT_IF_XML(r); + return call_imacro(add_imacros.obj_obj); + } + return call_imacro(add_imacros.obj_any); + } + if (!JSVAL_IS_PRIMITIVE(r)) { + ABORT_IF_XML(r); + return call_imacro(add_imacros.any_obj); + } + + if (JSVAL_IS_STRING(l) || JSVAL_IS_STRING(r)) { + LIns* args[] = { stringify(r), stringify(l), cx_ins }; + LIns* concat = lir->insCall(&js_ConcatStrings_ci, args); + guard(false, lir->ins_peq0(concat), OOM_EXIT); + set(&l, concat); + return JSRS_CONTINUE; + } + + return binary(LIR_fadd); +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_SUB() +{ + return binary(LIR_fsub); +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_MUL() +{ + return binary(LIR_fmul); +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_DIV() +{ + return binary(LIR_fdiv); +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_MOD() +{ + return binary(LIR_fmod); +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_NOT() +{ + jsval& v = stackval(-1); + if (JSVAL_IS_SPECIAL(v)) { + set(&v, lir->ins_eq0(lir->ins2i(LIR_eq, get(&v), 1))); + return JSRS_CONTINUE; + } + if (isNumber(v)) { + LIns* v_ins = get(&v); + set(&v, lir->ins2(LIR_or, lir->ins2(LIR_feq, v_ins, lir->insImmf(0)), + lir->ins_eq0(lir->ins2(LIR_feq, v_ins, v_ins)))); + return JSRS_CONTINUE; + } + if (JSVAL_TAG(v) == JSVAL_OBJECT) { + set(&v, lir->ins_peq0(get(&v))); + return JSRS_CONTINUE; + } + JS_ASSERT(JSVAL_IS_STRING(v)); + set(&v, lir->ins_peq0(lir->ins2(LIR_piand, + lir->insLoad(LIR_ldp, get(&v), (int)offsetof(JSString, mLength)), + INS_CONSTWORD(JSString::LENGTH_MASK)))); + return JSRS_CONTINUE; +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_BITNOT() +{ + return unary(LIR_not); +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_NEG() +{ + jsval& v = stackval(-1); + + if (!JSVAL_IS_PRIMITIVE(v)) { + ABORT_IF_XML(v); + return call_imacro(unary_imacros.sign); + } + + if (isNumber(v)) { + LIns* a = get(&v); + + /* + * If we're a promoted integer, we have to watch out for 0s since -0 is + * a double. Only follow this path if we're not an integer that's 0 and + * we're not a double that's zero. + */ + if (!oracle.isInstructionUndemotable(cx->fp->regs->pc) && + isPromoteInt(a) && + (!JSVAL_IS_INT(v) || JSVAL_TO_INT(v) != 0) && + (!JSVAL_IS_DOUBLE(v) || !JSDOUBLE_IS_NEGZERO(*JSVAL_TO_DOUBLE(v))) && + -asNumber(v) == (int)-asNumber(v)) { + a = lir->ins1(LIR_neg, ::demote(lir, a)); + if (!a->isconst()) { + VMSideExit* exit = snapshot(OVERFLOW_EXIT); + guard(false, lir->ins1(LIR_ov, a), exit); + guard(false, lir->ins2i(LIR_eq, a, 0), exit); + } + a = lir->ins1(LIR_i2f, a); + } else { + a = lir->ins1(LIR_fneg, a); + } + + set(&v, a); + return JSRS_CONTINUE; + } + + if (JSVAL_IS_NULL(v)) { + set(&v, lir->insImmf(-0.0)); + return JSRS_CONTINUE; + } + + JS_ASSERT(JSVAL_TAG(v) == JSVAL_STRING || JSVAL_IS_SPECIAL(v)); + + LIns* args[] = { get(&v), cx_ins }; + set(&v, lir->ins1(LIR_fneg, + lir->insCall(JSVAL_IS_STRING(v) + ? &js_StringToNumber_ci + : &js_BooleanOrUndefinedToNumber_ci, + args))); + return JSRS_CONTINUE; +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_POS() +{ + jsval& v = stackval(-1); + + if (!JSVAL_IS_PRIMITIVE(v)) { + ABORT_IF_XML(v); + return call_imacro(unary_imacros.sign); + } + + if (isNumber(v)) + return JSRS_CONTINUE; + + if (JSVAL_IS_NULL(v)) { + set(&v, lir->insImmf(0)); + return JSRS_CONTINUE; + } + + JS_ASSERT(JSVAL_TAG(v) == JSVAL_STRING || JSVAL_IS_SPECIAL(v)); + + LIns* args[] = { get(&v), cx_ins }; + set(&v, lir->insCall(JSVAL_IS_STRING(v) + ? &js_StringToNumber_ci + : &js_BooleanOrUndefinedToNumber_ci, + args)); + return JSRS_CONTINUE; +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_PRIMTOP() +{ + // Either this opcode does nothing or we couldn't have traced here, because + // we'd have thrown an exception -- so do nothing if we actually hit this. + return JSRS_CONTINUE; +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_OBJTOP() +{ + jsval& v = stackval(-1); + ABORT_IF_XML(v); + return JSRS_CONTINUE; +} + +JSRecordingStatus +TraceRecorder::getClassPrototype(JSObject* ctor, LIns*& proto_ins) +{ + // ctor must be a function created via js_InitClass. +#ifdef DEBUG + JSClass *clasp = FUN_CLASP(GET_FUNCTION_PRIVATE(cx, ctor)); + JS_ASSERT(clasp); + + JSTraceMonitor &localtm = JS_TRACE_MONITOR(cx); +#endif + + jsval pval; + if (!ctor->getProperty(cx, ATOM_TO_JSID(cx->runtime->atomState.classPrototypeAtom), &pval)) + ABORT_TRACE_ERROR("error getting prototype from constructor"); + + // ctor.prototype is a permanent data property, so this lookup cannot have + // deep-aborted. + JS_ASSERT(localtm.recorder); + +#ifdef DEBUG + JSBool ok, found; + uintN attrs; + ok = JS_GetPropertyAttributes(cx, ctor, js_class_prototype_str, &attrs, &found); + JS_ASSERT(ok); + JS_ASSERT(found); + JS_ASSERT((~attrs & (JSPROP_READONLY | JSPROP_PERMANENT)) == 0); +#endif + + // Since ctor was built by js_InitClass, we can assert (rather than check) + // that pval is usable. + JS_ASSERT(!JSVAL_IS_PRIMITIVE(pval)); + JSObject *proto = JSVAL_TO_OBJECT(pval); + JS_ASSERT_IF(clasp != &js_ArrayClass, OBJ_SCOPE(proto)->emptyScope->clasp == clasp); + + proto_ins = INS_CONSTOBJ(proto); + return JSRS_CONTINUE; +} + +JSRecordingStatus +TraceRecorder::getClassPrototype(JSProtoKey key, LIns*& proto_ins) +{ +#ifdef DEBUG + JSTraceMonitor &localtm = JS_TRACE_MONITOR(cx); +#endif + + JSObject* proto; + if (!js_GetClassPrototype(cx, globalObj, INT_TO_JSID(key), &proto)) + ABORT_TRACE_ERROR("error in js_GetClassPrototype"); + + // This should not have reentered. + JS_ASSERT(localtm.recorder); + + // If we might end up passing the proto to JSObject::initSharingEmptyScope, + // we must check here that proto has a matching emptyScope. We skip the + // check for Array.prototype because new arrays, being non-native, are + // never initialized using initSharingEmptyScope. + if (key != JSProto_Array) { + if (!OBJ_IS_NATIVE(proto)) { + //non-native class prototype + return JSRS_STOP; + } + JSEmptyScope *emptyScope = OBJ_SCOPE(proto)->emptyScope; + if (!emptyScope || JSCLASS_CACHED_PROTO_KEY(emptyScope->clasp) != key) { + // class prototype is not the standard one + return JSRS_STOP; + } + } + + proto_ins = INS_CONSTOBJ(proto); + return JSRS_CONTINUE; +} + +#define IGNORE_NATIVE_CALL_COMPLETE_CALLBACK ((JSSpecializedNative*)1) + +JSRecordingStatus +TraceRecorder::newString(JSObject* ctor, uint32 argc, jsval* argv, jsval* rval) +{ + JS_ASSERT(argc == 1); + + if (!JSVAL_IS_PRIMITIVE(argv[0])) { + ABORT_IF_XML(argv[0]); + return call_imacro(new_imacros.String); + } + + LIns* proto_ins; + CHECK_STATUS(getClassPrototype(ctor, proto_ins)); + + LIns* args[] = { stringify(argv[0]), proto_ins, cx_ins }; + LIns* obj_ins = lir->insCall(&js_String_tn_ci, args); + guard(false, lir->ins_peq0(obj_ins), OOM_EXIT); + + set(rval, obj_ins); + pendingSpecializedNative = IGNORE_NATIVE_CALL_COMPLETE_CALLBACK; + return JSRS_CONTINUE; +} + +JSRecordingStatus +TraceRecorder::newArray(JSObject* ctor, uint32 argc, jsval* argv, jsval* rval) +{ + LIns *proto_ins; + CHECK_STATUS(getClassPrototype(ctor, proto_ins)); + + LIns *arr_ins; + if (argc == 0) { + // arr_ins = js_NewEmptyArray(cx, Array.prototype) + LIns *args[] = { proto_ins, cx_ins }; + arr_ins = lir->insCall(&js_NewEmptyArray_ci, args); + guard(false, lir->ins_peq0(arr_ins), OOM_EXIT); + } else if (argc == 1 && JSVAL_IS_NUMBER(argv[0])) { + // arr_ins = js_NewEmptyArray(cx, Array.prototype, length) + LIns *args[] = { f2i(get(argv)), proto_ins, cx_ins }; // FIXME: is this 64-bit safe? + arr_ins = lir->insCall(&js_NewEmptyArrayWithLength_ci, args); + guard(false, lir->ins_peq0(arr_ins), OOM_EXIT); + } else { + // arr_ins = js_NewUninitializedArray(cx, Array.prototype, argc) + LIns *args[] = { INS_CONST(argc), proto_ins, cx_ins }; + arr_ins = lir->insCall(&js_NewUninitializedArray_ci, args); + guard(false, lir->ins_peq0(arr_ins), OOM_EXIT); + + // arr->dslots[i] = box_jsval(vp[i]); for i in 0..argc + LIns *dslots_ins = NULL; + for (uint32 i = 0; i < argc && !outOfMemory(); i++) { + LIns *elt_ins = box_jsval(argv[i], get(&argv[i])); + stobj_set_dslot(arr_ins, i, dslots_ins, elt_ins); + } + + if (argc > 0) + stobj_set_fslot(arr_ins, JSSLOT_ARRAY_COUNT, INS_CONST(argc)); + } + + set(rval, arr_ins); + pendingSpecializedNative = IGNORE_NATIVE_CALL_COMPLETE_CALLBACK; + return JSRS_CONTINUE; +} + +JS_REQUIRES_STACK void +TraceRecorder::propagateFailureToBuiltinStatus(LIns* ok_ins, LIns*& status_ins) +{ + /* + * Check the boolean return value (ok_ins) of a native JSNative, + * JSFastNative, or JSPropertyOp hook for failure. On failure, set the + * JSBUILTIN_ERROR bit of cx->builtinStatus. + * + * If the return value (ok_ins) is true, status' == status. Otherwise + * status' = status | JSBUILTIN_ERROR. We calculate (rval&1)^1, which is 1 + * if rval is JS_FALSE (error), and then shift that by 1, which is the log2 + * of JSBUILTIN_ERROR. + */ + JS_STATIC_ASSERT(((JS_TRUE & 1) ^ 1) << 1 == 0); + JS_STATIC_ASSERT(((JS_FALSE & 1) ^ 1) << 1 == JSBUILTIN_ERROR); + status_ins = lir->ins2(LIR_or, + status_ins, + lir->ins2i(LIR_lsh, + lir->ins2i(LIR_xor, + lir->ins2i(LIR_and, ok_ins, 1), + 1), + 1)); + lir->insStorei(status_ins, lirbuf->state, (int) offsetof(InterpState, builtinStatus)); +} + +JS_REQUIRES_STACK void +TraceRecorder::emitNativePropertyOp(JSScope* scope, JSScopeProperty* sprop, LIns* obj_ins, + bool setflag, LIns* boxed_ins) +{ + JS_ASSERT(!(sprop->attrs & (setflag ? JSPROP_SETTER : JSPROP_GETTER))); + JS_ASSERT(setflag ? !SPROP_HAS_STUB_SETTER(sprop) : !SPROP_HAS_STUB_GETTER(sprop)); + + enterDeepBailCall(); + + // It is unsafe to pass the address of an object slot as the out parameter, + // because the getter or setter could end up resizing the object's dslots. + // Instead, use a word of stack and root it in nativeVp. + LIns* vp_ins = lir->insAlloc(sizeof(jsval)); + lir->insStorei(vp_ins, lirbuf->state, offsetof(InterpState, nativeVp)); + lir->insStorei(INS_CONST(1), lirbuf->state, offsetof(InterpState, nativeVpLen)); + if (setflag) + lir->insStorei(boxed_ins, vp_ins, 0); + + CallInfo* ci = new (*traceMonitor->dataAlloc) CallInfo(); + ci->_address = uintptr_t(setflag ? sprop->setter : sprop->getter); + ci->_argtypes = ARGSIZE_I << (0*ARGSIZE_SHIFT) | + ARGSIZE_P << (1*ARGSIZE_SHIFT) | + ARGSIZE_P << (2*ARGSIZE_SHIFT) | + ARGSIZE_P << (3*ARGSIZE_SHIFT) | + ARGSIZE_P << (4*ARGSIZE_SHIFT); + ci->_cse = ci->_fold = 0; + ci->_abi = ABI_CDECL; +#ifdef DEBUG + ci->_name = "JSPropertyOp"; +#endif + LIns* args[] = { vp_ins, INS_CONSTVAL(SPROP_USERID(sprop)), obj_ins, cx_ins }; + LIns* ok_ins = lir->insCall(ci, args); + + // Cleanup. Immediately clear nativeVp before we might deep bail. + lir->insStorei(INS_NULL(), lirbuf->state, offsetof(InterpState, nativeVp)); + leaveDeepBailCall(); + + // Guard that the call succeeded and builtinStatus is still 0. + // If the native op succeeds but we deep-bail here, the result value is + // lost! Therefore this can only be used for setters of shared properties. + // In that case we ignore the result value anyway. + LIns* status_ins = lir->insLoad(LIR_ld, + lirbuf->state, + (int) offsetof(InterpState, builtinStatus)); + propagateFailureToBuiltinStatus(ok_ins, status_ins); + guard(true, lir->ins_eq0(status_ins), STATUS_EXIT); + + // Re-load the value--but this is currently unused, so commented out. + //boxed_ins = lir->insLoad(LIR_ldp, vp_ins, 0); +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::emitNativeCall(JSSpecializedNative* sn, uintN argc, LIns* args[], bool rooted) +{ + bool constructing = sn->flags & JSTN_CONSTRUCTOR; + + if (JSTN_ERRTYPE(sn) == FAIL_STATUS) { + // This needs to capture the pre-call state of the stack. So do not set + // pendingSpecializedNative before taking this snapshot. + JS_ASSERT(!pendingSpecializedNative); + + // Take snapshot for js_DeepBail and store it in cx->bailExit. + // If we are calling a slow native, add information to the side exit + // for SynthesizeSlowNativeFrame. + VMSideExit* exit = enterDeepBailCall(); + JSObject* funobj = JSVAL_TO_OBJECT(stackval(0 - (2 + argc))); + if (FUN_SLOW_NATIVE(GET_FUNCTION_PRIVATE(cx, funobj))) { + exit->setNativeCallee(funobj, constructing); + treeInfo->gcthings.addUnique(OBJECT_TO_JSVAL(funobj)); + } + } + + LIns* res_ins = lir->insCall(sn->builtin, args); + + // Immediately unroot the vp as soon we return since we might deep bail next. + if (rooted) + lir->insStorei(INS_NULL(), lirbuf->state, offsetof(InterpState, nativeVp)); + + rval_ins = res_ins; + switch (JSTN_ERRTYPE(sn)) { + case FAIL_NULL: + guard(false, lir->ins_peq0(res_ins), OOM_EXIT); + break; + case FAIL_NEG: + res_ins = lir->ins1(LIR_i2f, res_ins); + guard(false, lir->ins2(LIR_flt, res_ins, lir->insImmf(0)), OOM_EXIT); + break; + case FAIL_VOID: + guard(false, lir->ins2i(LIR_eq, res_ins, JSVAL_TO_SPECIAL(JSVAL_VOID)), OOM_EXIT); + break; + case FAIL_COOKIE: + guard(false, lir->ins2(LIR_peq, res_ins, INS_CONSTWORD(JSVAL_ERROR_COOKIE)), OOM_EXIT); + break; + default:; + } + + set(&stackval(0 - (2 + argc)), res_ins); + + /* + * The return value will be processed by NativeCallComplete since + * we have to know the actual return value type for calls that return + * jsval (like Array_p_pop). + */ + pendingSpecializedNative = sn; + + return JSRS_CONTINUE; +} + +/* + * Check whether we have a specialized implementation for this native + * invocation. + */ +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::callSpecializedNative(JSNativeTraceInfo *trcinfo, uintN argc, + bool constructing) +{ + JSStackFrame* fp = cx->fp; + jsbytecode *pc = fp->regs->pc; + + jsval& fval = stackval(0 - (2 + argc)); + jsval& tval = stackval(0 - (1 + argc)); + + LIns* this_ins = get(&tval); + + LIns* args[nanojit::MAXARGS]; + JSSpecializedNative *sn = trcinfo->specializations; + JS_ASSERT(sn); + do { + if (((sn->flags & JSTN_CONSTRUCTOR) != 0) != constructing) + continue; + + uintN knownargc = strlen(sn->argtypes); + if (argc != knownargc) + continue; + + intN prefixc = strlen(sn->prefix); + JS_ASSERT(prefixc <= 3); + LIns** argp = &args[argc + prefixc - 1]; + char argtype; + +#if defined DEBUG + memset(args, 0xCD, sizeof(args)); +#endif + + uintN i; + for (i = prefixc; i--; ) { + argtype = sn->prefix[i]; + if (argtype == 'C') { + *argp = cx_ins; + } else if (argtype == 'T') { /* this, as an object */ + if (JSVAL_IS_PRIMITIVE(tval)) + goto next_specialization; + *argp = this_ins; + } else if (argtype == 'S') { /* this, as a string */ + if (!JSVAL_IS_STRING(tval)) + goto next_specialization; + *argp = this_ins; + } else if (argtype == 'f') { + *argp = INS_CONSTOBJ(JSVAL_TO_OBJECT(fval)); + } else if (argtype == 'p') { + CHECK_STATUS(getClassPrototype(JSVAL_TO_OBJECT(fval), *argp)); + } else if (argtype == 'R') { + *argp = INS_CONSTPTR(cx->runtime); + } else if (argtype == 'P') { + // FIXME: Set pc to imacpc when recording JSOP_CALL inside the + // JSOP_GETELEM imacro (bug 476559). + if (*pc == JSOP_CALL && fp->imacpc && *fp->imacpc == JSOP_GETELEM) + *argp = INS_CONSTPTR(fp->imacpc); + else + *argp = INS_CONSTPTR(pc); + } else if (argtype == 'D') { /* this, as a number */ + if (!isNumber(tval)) + goto next_specialization; + *argp = this_ins; + } else { + JS_NOT_REACHED("unknown prefix arg type"); + } + argp--; + } + + for (i = knownargc; i--; ) { + jsval& arg = stackval(0 - (i + 1)); + *argp = get(&arg); + + argtype = sn->argtypes[i]; + if (argtype == 'd' || argtype == 'i') { + if (!isNumber(arg)) + goto next_specialization; + if (argtype == 'i') + *argp = f2i(*argp); + } else if (argtype == 'o') { + if (JSVAL_IS_PRIMITIVE(arg)) + goto next_specialization; + } else if (argtype == 's') { + if (!JSVAL_IS_STRING(arg)) + goto next_specialization; + } else if (argtype == 'r') { + if (!VALUE_IS_REGEXP(cx, arg)) + goto next_specialization; + } else if (argtype == 'f') { + if (!VALUE_IS_FUNCTION(cx, arg)) + goto next_specialization; + } else if (argtype == 'v') { + *argp = box_jsval(arg, *argp); + } else { + goto next_specialization; + } + argp--; + } +#if defined DEBUG + JS_ASSERT(args[0] != (LIns *)0xcdcdcdcd); +#endif + return emitNativeCall(sn, argc, args, false); + +next_specialization:; + } while ((sn++)->flags & JSTN_MORE); + + return JSRS_STOP; +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::callNative(uintN argc, JSOp mode) +{ + LIns* args[5]; + + JS_ASSERT(mode == JSOP_CALL || mode == JSOP_NEW || mode == JSOP_APPLY); + + jsval* vp = &stackval(0 - (2 + argc)); + JSObject* funobj = JSVAL_TO_OBJECT(vp[0]); + JSFunction* fun = GET_FUNCTION_PRIVATE(cx, funobj); + JSFastNative native = (JSFastNative)fun->u.n.native; + + switch (argc) { + case 1: + if (isNumber(vp[2]) && + (native == js_math_ceil || native == js_math_floor || native == js_math_round)) { + LIns* a = get(&vp[2]); + if (isPromote(a)) { + set(&vp[0], a); + pendingSpecializedNative = IGNORE_NATIVE_CALL_COMPLETE_CALLBACK; + return JSRS_CONTINUE; + } + } + break; + case 2: + if (isNumber(vp[2]) && isNumber(vp[3]) && + (native == js_math_min || native == js_math_max)) { + LIns* a = get(&vp[2]); + LIns* b = get(&vp[3]); + if (isPromote(a) && isPromote(b)) { + a = ::demote(lir, a); + b = ::demote(lir, b); + set(&vp[0], + lir->ins1(LIR_i2f, + lir->ins_choose(lir->ins2((native == js_math_min) + ? LIR_lt + : LIR_gt, a, b), + a, b))); + pendingSpecializedNative = IGNORE_NATIVE_CALL_COMPLETE_CALLBACK; + return JSRS_CONTINUE; + } + } + break; + } + + if (fun->flags & JSFUN_TRCINFO) { + JSNativeTraceInfo *trcinfo = FUN_TRCINFO(fun); + JS_ASSERT(trcinfo && (JSFastNative)fun->u.n.native == trcinfo->native); + + /* Try to call a type specialized version of the native. */ + if (trcinfo->specializations) { + JSRecordingStatus status = callSpecializedNative(trcinfo, argc, mode == JSOP_NEW); + if (status != JSRS_STOP) + return status; + } + } + + if (native == js_fun_apply || native == js_fun_call) + ABORT_TRACE("trying to call native apply or call"); + + // Allocate the vp vector and emit code to root it. + uintN vplen = 2 + JS_MAX(argc, FUN_MINARGS(fun)) + fun->u.n.extra; + if (!(fun->flags & JSFUN_FAST_NATIVE)) + vplen++; // slow native return value slot + LIns* invokevp_ins = lir->insAlloc(vplen * sizeof(jsval)); + + // vp[0] is the callee. + lir->insStorei(INS_CONSTVAL(OBJECT_TO_JSVAL(funobj)), invokevp_ins, 0); + + // Calculate |this|. + LIns* this_ins; + if (mode == JSOP_NEW) { + JSClass* clasp = fun->u.n.clasp; + JS_ASSERT(clasp != &js_SlowArrayClass); + if (!clasp) + clasp = &js_ObjectClass; + JS_ASSERT(((jsuword) clasp & 3) == 0); + + // Abort on |new Function|. js_NewInstance would allocate a regular- + // sized JSObject, not a Function-sized one. (The Function ctor would + // deep-bail anyway but let's not go there.) + if (clasp == &js_FunctionClass) + ABORT_TRACE("new Function"); + + if (clasp->getObjectOps) + ABORT_TRACE("new with non-native ops"); + + args[0] = INS_CONSTOBJ(funobj); + args[1] = INS_CONSTPTR(clasp); + args[2] = cx_ins; + newobj_ins = lir->insCall(&js_NewInstance_ci, args); + guard(false, lir->ins_peq0(newobj_ins), OOM_EXIT); + this_ins = newobj_ins; /* boxing an object is a no-op */ + } else if (JSFUN_BOUND_METHOD_TEST(fun->flags)) { + /* |funobj| was rooted above already. */ + this_ins = INS_CONSTWORD(OBJECT_TO_JSVAL(OBJ_GET_PARENT(cx, funobj))); + } else { + this_ins = get(&vp[1]); + + /* + * For fast natives, 'null' or primitives are fine as as 'this' value. + * For slow natives we have to ensure the object is substituted for the + * appropriate global object or boxed object value. JSOP_NEW allocates its + * own object so it's guaranteed to have a valid 'this' value. + */ + if (!(fun->flags & JSFUN_FAST_NATIVE)) { + if (JSVAL_IS_NULL(vp[1])) { + JSObject* thisObj = js_ComputeThis(cx, JS_FALSE, vp + 2); + if (!thisObj) + ABORT_TRACE_ERROR("error in js_ComputeGlobalThis"); + this_ins = INS_CONSTOBJ(thisObj); + } else if (!JSVAL_IS_OBJECT(vp[1])) { + ABORT_TRACE("slow native(primitive, args)"); + } else { + if (guardClass(JSVAL_TO_OBJECT(vp[1]), this_ins, &js_WithClass, snapshot(MISMATCH_EXIT))) + ABORT_TRACE("can't trace slow native invocation on With object"); + + this_ins = lir->ins_choose(lir->ins_peq0(stobj_get_parent(this_ins)), + INS_CONSTOBJ(globalObj), + this_ins); + } + } + this_ins = box_jsval(vp[1], this_ins); + } + set(&vp[1], this_ins); + lir->insStorei(this_ins, invokevp_ins, 1 * sizeof(jsval)); + + // Populate argv. + for (uintN n = 2; n < 2 + argc; n++) { + LIns* i = box_jsval(vp[n], get(&vp[n])); + lir->insStorei(i, invokevp_ins, n * sizeof(jsval)); + + // For a very long argument list we might run out of LIR space, so + // check inside the loop. + if (outOfMemory()) + ABORT_TRACE("out of memory in argument list"); + } + + // Populate extra slots, including the return value slot for a slow native. + if (2 + argc < vplen) { + LIns* undef_ins = INS_CONSTWORD(JSVAL_VOID); + for (uintN n = 2 + argc; n < vplen; n++) { + lir->insStorei(undef_ins, invokevp_ins, n * sizeof(jsval)); + + if (outOfMemory()) + ABORT_TRACE("out of memory in extra slots"); + } + } + + // Set up arguments for the JSNative or JSFastNative. + uint32 types; + if (fun->flags & JSFUN_FAST_NATIVE) { + if (mode == JSOP_NEW) + ABORT_TRACE("untraceable fast native constructor"); + native_rval_ins = invokevp_ins; + args[0] = invokevp_ins; + args[1] = lir->insImm(argc); + args[2] = cx_ins; + types = ARGSIZE_I << (0*ARGSIZE_SHIFT) | + ARGSIZE_P << (1*ARGSIZE_SHIFT) | + ARGSIZE_I << (2*ARGSIZE_SHIFT) | + ARGSIZE_P << (3*ARGSIZE_SHIFT); + } else { + int32_t offset = (vplen - 1) * sizeof(jsval); + native_rval_ins = lir->ins2(LIR_piadd, invokevp_ins, INS_CONSTWORD(offset)); + args[0] = native_rval_ins; + args[1] = lir->ins2(LIR_piadd, invokevp_ins, INS_CONSTWORD(2 * sizeof(jsval))); + args[2] = lir->insImm(argc); + args[3] = this_ins; + args[4] = cx_ins; + types = ARGSIZE_I << (0*ARGSIZE_SHIFT) | + ARGSIZE_P << (1*ARGSIZE_SHIFT) | + ARGSIZE_P << (2*ARGSIZE_SHIFT) | + ARGSIZE_I << (3*ARGSIZE_SHIFT) | + ARGSIZE_P << (4*ARGSIZE_SHIFT) | + ARGSIZE_P << (5*ARGSIZE_SHIFT); + } + + // Generate CallInfo and a JSSpecializedNative structure on the fly. + // Do not use JSTN_UNBOX_AFTER for mode JSOP_NEW because + // record_NativeCallComplete unboxes the result specially. + + CallInfo* ci = new (*traceMonitor->dataAlloc) CallInfo(); + ci->_address = uintptr_t(fun->u.n.native); + ci->_cse = ci->_fold = 0; + ci->_abi = ABI_CDECL; + ci->_argtypes = types; +#ifdef DEBUG + ci->_name = JS_GetFunctionName(fun); + #endif + + // Generate a JSSpecializedNative structure on the fly. + generatedSpecializedNative.builtin = ci; + generatedSpecializedNative.flags = FAIL_STATUS | ((mode == JSOP_NEW) + ? JSTN_CONSTRUCTOR + : JSTN_UNBOX_AFTER); + generatedSpecializedNative.prefix = NULL; + generatedSpecializedNative.argtypes = NULL; + + // We only have to ensure that the values we wrote into the stack buffer + // are rooted if we actually make it to the call, so only set nativeVp and + // nativeVpLen immediately before emitting the call code. This way we avoid + // leaving trace with a bogus nativeVp because we fall off trace while unboxing + // values into the stack buffer. + lir->insStorei(INS_CONST(vplen), lirbuf->state, offsetof(InterpState, nativeVpLen)); + lir->insStorei(invokevp_ins, lirbuf->state, offsetof(InterpState, nativeVp)); + + // argc is the original argc here. It is used to calculate where to place + // the return value. + return emitNativeCall(&generatedSpecializedNative, argc, args, true); +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::functionCall(uintN argc, JSOp mode) +{ + jsval& fval = stackval(0 - (2 + argc)); + JS_ASSERT(&fval >= StackBase(cx->fp)); + + if (!VALUE_IS_FUNCTION(cx, fval)) + ABORT_TRACE("callee is not a function"); + + jsval& tval = stackval(0 - (1 + argc)); + + /* + * If callee is not constant, it's a shapeless call and we have to guard + * explicitly that we will get this callee again at runtime. + */ + if (!get(&fval)->isconstp()) + CHECK_STATUS(guardCallee(fval)); + + /* + * Require that the callee be a function object, to avoid guarding on its + * class here. We know if the callee and this were pushed by JSOP_CALLNAME + * or JSOP_CALLPROP that callee is a *particular* function, since these hit + * the property cache and guard on the object (this) in which the callee + * was found. So it's sufficient to test here that the particular function + * is interpreted, not guard on that condition. + * + * Bytecode sequences that push shapeless callees must guard on the callee + * class being Function and the function being interpreted. + */ + JSFunction* fun = GET_FUNCTION_PRIVATE(cx, JSVAL_TO_OBJECT(fval)); + + if (FUN_INTERPRETED(fun)) { + if (mode == JSOP_NEW) { + LIns* args[] = { get(&fval), INS_CONSTPTR(&js_ObjectClass), cx_ins }; + LIns* tv_ins = lir->insCall(&js_NewInstance_ci, args); + guard(false, lir->ins_peq0(tv_ins), OOM_EXIT); + set(&tval, tv_ins); + } + return interpretedFunctionCall(fval, fun, argc, mode == JSOP_NEW); + } + + if (FUN_SLOW_NATIVE(fun)) { + JSNative native = fun->u.n.native; + jsval* argv = &tval + 1; + if (native == js_Array) + return newArray(JSVAL_TO_OBJECT(fval), argc, argv, &fval); + if (native == js_String && argc == 1) { + if (mode == JSOP_NEW) + return newString(JSVAL_TO_OBJECT(fval), 1, argv, &fval); + if (!JSVAL_IS_PRIMITIVE(argv[0])) { + ABORT_IF_XML(argv[0]); + return call_imacro(call_imacros.String); + } + set(&fval, stringify(argv[0])); + pendingSpecializedNative = IGNORE_NATIVE_CALL_COMPLETE_CALLBACK; + return JSRS_CONTINUE; + } + } + + return callNative(argc, mode); +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_NEW() +{ + uintN argc = GET_ARGC(cx->fp->regs->pc); + cx->fp->assertValidStackDepth(argc + 2); + return functionCall(argc, JSOP_NEW); +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_DELNAME() +{ + return JSRS_STOP; +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_DELPROP() +{ + return JSRS_STOP; +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_DELELEM() +{ + return JSRS_STOP; +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_TYPEOF() +{ + jsval& r = stackval(-1); + LIns* type; + if (JSVAL_IS_STRING(r)) { + type = INS_ATOM(cx->runtime->atomState.typeAtoms[JSTYPE_STRING]); + } else if (isNumber(r)) { + type = INS_ATOM(cx->runtime->atomState.typeAtoms[JSTYPE_NUMBER]); + } else if (VALUE_IS_FUNCTION(cx, r)) { + type = INS_ATOM(cx->runtime->atomState.typeAtoms[JSTYPE_FUNCTION]); + } else { + LIns* args[] = { get(&r), cx_ins }; + if (JSVAL_IS_SPECIAL(r)) { + // We specialize identically for boolean and undefined. We must not have a hole here. + // Pass the unboxed type here, since TypeOfBoolean knows how to handle it. + JS_ASSERT(r == JSVAL_TRUE || r == JSVAL_FALSE || r == JSVAL_VOID); + type = lir->insCall(&js_TypeOfBoolean_ci, args); + } else { + JS_ASSERT(JSVAL_TAG(r) == JSVAL_OBJECT); + type = lir->insCall(&js_TypeOfObject_ci, args); + } + } + set(&r, type); + return JSRS_CONTINUE; +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_VOID() +{ + stack(-1, INS_CONST(JSVAL_TO_SPECIAL(JSVAL_VOID))); + return JSRS_CONTINUE; +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_INCNAME() +{ + return incName(1); +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_INCPROP() +{ + return incProp(1); +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_INCELEM() +{ + return incElem(1); +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_DECNAME() +{ + return incName(-1); +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_DECPROP() +{ + return incProp(-1); +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_DECELEM() +{ + return incElem(-1); +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::incName(jsint incr, bool pre) +{ + jsval* vp; + LIns* v_ins; + LIns* v_after; + NameResult nr; + + CHECK_STATUS(name(vp, v_ins, nr)); + jsval v = nr.tracked ? *vp : nr.v; + CHECK_STATUS(incHelper(v, v_ins, v_after, incr)); + LIns* v_result = pre ? v_after : v_ins; + if (nr.tracked) { + set(vp, v_after); + stack(0, v_result); + return JSRS_CONTINUE; + } + + if (OBJ_GET_CLASS(cx, nr.obj) != &js_CallClass) + ABORT_TRACE("incName on unsupported object class"); + + CHECK_STATUS(setCallProp(nr.obj, nr.obj_ins, nr.sprop, v_after, v)); + stack(0, v_result); + return JSRS_CONTINUE; +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_NAMEINC() +{ + return incName(1, false); +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_PROPINC() +{ + return incProp(1, false); +} + +// XXX consolidate with record_JSOP_GETELEM code... +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_ELEMINC() +{ + return incElem(1, false); +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_NAMEDEC() +{ + return incName(-1, false); +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_PROPDEC() +{ + return incProp(-1, false); +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_ELEMDEC() +{ + return incElem(-1, false); +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_GETPROP() +{ + return getProp(stackval(-1)); +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_SETPROP() +{ + jsval& l = stackval(-2); + if (JSVAL_IS_PRIMITIVE(l)) + ABORT_TRACE("primitive this for SETPROP"); + + JSObject* obj = JSVAL_TO_OBJECT(l); + if (obj->map->ops->setProperty != js_SetProperty) + ABORT_TRACE("non-native JSObjectOps::setProperty"); + return JSRS_CONTINUE; +} + +/* Emit a specialized, inlined copy of js_NativeSet. */ +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::nativeSet(JSObject* obj, LIns* obj_ins, JSScopeProperty* sprop, + jsval v, LIns* v_ins) +{ + JSScope* scope = OBJ_SCOPE(obj); + uint32 slot = sprop->slot; + + /* + * We do not trace assignment to properties that have both a nonstub setter + * and a slot, for several reasons. + * + * First, that would require sampling rt->propertyRemovals before and after + * (see js_NativeSet), and even more code to handle the case where the two + * samples differ. A mere guard is not enough, because you can't just bail + * off trace in the middle of a property assignment without storing the + * value and making the stack right. + * + * If obj is the global object, there are two additional problems. We would + * have to emit still more code to store the result in the object (not the + * native global frame) if the setter returned successfully after + * deep-bailing. And we would have to cope if the run-time type of the + * setter's return value differed from the record-time type of v, in which + * case unboxing would fail and, having called a native setter, we could + * not just retry the instruction in the interpreter. + */ + JS_ASSERT(SPROP_HAS_STUB_SETTER(sprop) || slot == SPROP_INVALID_SLOT); + + // Box the value to be stored, if necessary. + LIns* boxed_ins = NULL; + if (!SPROP_HAS_STUB_SETTER(sprop) || (slot != SPROP_INVALID_SLOT && obj != globalObj)) + boxed_ins = box_jsval(v, v_ins); + + // Call the setter, if any. + if (!SPROP_HAS_STUB_SETTER(sprop)) + emitNativePropertyOp(scope, sprop, obj_ins, true, boxed_ins); + + // Store the value, if this property has a slot. + if (slot != SPROP_INVALID_SLOT) { + JS_ASSERT(SPROP_HAS_VALID_SLOT(sprop, scope)); + JS_ASSERT(!(sprop->attrs & JSPROP_SHARED)); + if (obj == globalObj) { + if (!lazilyImportGlobalSlot(slot)) + ABORT_TRACE("lazy import of global slot failed"); + set(&STOBJ_GET_SLOT(obj, slot), v_ins); + } else { + LIns* dslots_ins = NULL; + stobj_set_slot(obj_ins, slot, dslots_ins, boxed_ins); + } + } + + return JSRS_CONTINUE; +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::setProp(jsval &l, JSPropCacheEntry* entry, JSScopeProperty* sprop, + jsval &v, LIns*& v_ins) +{ + if (entry == JS_NO_PROP_CACHE_FILL) + ABORT_TRACE("can't trace uncacheable property set"); + JS_ASSERT_IF(PCVCAP_TAG(entry->vcap) >= 1, sprop->attrs & JSPROP_SHARED); + if (!SPROP_HAS_STUB_SETTER(sprop) && sprop->slot != SPROP_INVALID_SLOT) + ABORT_TRACE("can't trace set of property with setter and slot"); + if (sprop->attrs & JSPROP_SETTER) + ABORT_TRACE("can't trace JavaScript function setter"); + + // These two cases are errors and can't be traced. + if (sprop->attrs & JSPROP_GETTER) + ABORT_TRACE("can't assign to property with script getter but no setter"); + if (sprop->attrs & JSPROP_READONLY) + ABORT_TRACE("can't assign to readonly property"); + + JS_ASSERT(!JSVAL_IS_PRIMITIVE(l)); + JSObject* obj = JSVAL_TO_OBJECT(l); + LIns* obj_ins = get(&l); + JSScope* scope = OBJ_SCOPE(obj); + + JS_ASSERT_IF(entry->vcap == PCVCAP_MAKE(entry->kshape, 0, 0), scope->has(sprop)); + + // Fast path for CallClass. This is about 20% faster than the general case. + if (OBJ_GET_CLASS(cx, obj) == &js_CallClass) { + v_ins = get(&v); + return setCallProp(obj, obj_ins, sprop, v_ins, v); + } + + /* + * Setting a function-valued property might need to rebrand the object; we + * don't trace that case. There's no need to guard on that, though, because + * separating functions into the trace-time type TT_FUNCTION will save the + * day! + */ + if (scope->branded() && VALUE_IS_FUNCTION(cx, v)) + ABORT_TRACE("can't trace function-valued property set in branded scope"); + + // Find obj2. If entry->adding(), the TAG bits are all 0. + JSObject* obj2 = obj; + for (jsuword i = PCVCAP_TAG(entry->vcap) >> PCVCAP_PROTOBITS; i; i--) + obj2 = OBJ_GET_PARENT(cx, obj2); + for (jsuword j = PCVCAP_TAG(entry->vcap) & PCVCAP_PROTOMASK; j; j--) + obj2 = OBJ_GET_PROTO(cx, obj2); + scope = OBJ_SCOPE(obj2); + JS_ASSERT_IF(entry->adding(), obj2 == obj); + + // Guard before anything else. + LIns* map_ins = map(obj_ins); + CHECK_STATUS(guardNativePropertyOp(obj, map_ins)); + jsuword pcval; + CHECK_STATUS(guardPropertyCacheHit(obj_ins, map_ins, obj, obj2, entry, pcval)); + JS_ASSERT(scope->object == obj2); + JS_ASSERT(scope->has(sprop)); + JS_ASSERT_IF(obj2 != obj, sprop->attrs & JSPROP_SHARED); + + // Add a property to the object if necessary. + if (entry->adding()) { + JS_ASSERT(!(sprop->attrs & JSPROP_SHARED)); + if (obj == globalObj) + ABORT_TRACE("adding a property to the global object"); + + LIns* args[] = { INS_CONSTSPROP(sprop), obj_ins, cx_ins }; + LIns* ok_ins = lir->insCall(&js_AddProperty_ci, args); + guard(false, lir->ins_eq0(ok_ins), OOM_EXIT); + } + + v_ins = get(&v); + return nativeSet(obj, obj_ins, sprop, v, v_ins); +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::setUpwardTrackedVar(jsval* stackVp, jsval v, LIns* v_ins) +{ + JSTraceType stackT = determineSlotType(stackVp); + JSTraceType otherT = getCoercedType(v); + + bool promote = true; + + if (stackT != otherT) { + if (stackT == TT_DOUBLE && otherT == TT_INT32 && isPromoteInt(v_ins)) + promote = false; + else + ABORT_TRACE("can't trace this upvar mutation"); + } + + set(stackVp, v_ins, promote); + + return JSRS_CONTINUE; +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::setCallProp(JSObject *callobj, LIns *callobj_ins, JSScopeProperty *sprop, + LIns *v_ins, jsval v) +{ + // Set variables in on-trace-stack call objects by updating the tracker. + JSStackFrame *fp = frameIfInRange(callobj); + if (fp) { + jsint slot = JSVAL_TO_INT(SPROP_USERID(sprop)); + if (sprop->setter == SetCallArg) { + jsval *vp2 = &fp->argv[slot]; + CHECK_STATUS(setUpwardTrackedVar(vp2, v, v_ins)); + return JSRS_CONTINUE; + } + if (sprop->setter == SetCallVar) { + jsval *vp2 = &fp->slots[slot]; + CHECK_STATUS(setUpwardTrackedVar(vp2, v, v_ins)); + return JSRS_CONTINUE; + } + ABORT_TRACE("can't trace special CallClass setter"); + } + + // Set variables in off-trace-stack call objects by calling standard builtins. + const CallInfo* ci = NULL; + if (sprop->setter == SetCallArg) + ci = &js_SetCallArg_ci; + else if (sprop->setter == SetCallVar) + ci = &js_SetCallVar_ci; + else + ABORT_TRACE("can't trace special CallClass setter"); + + // Even though the frame is out of range, later we might be called as an + // inner trace such that the target variable is defined in the outer trace + // entry frame. In that case, we must store to the native stack area for + // that frame. + + LIns *fp_ins = lir->insLoad(LIR_ldp, cx_ins, offsetof(JSContext, fp)); + LIns *fpcallobj_ins = lir->insLoad(LIR_ldp, fp_ins, offsetof(JSStackFrame, callobj)); + LIns *br1 = lir->insBranch(LIR_jf, lir->ins2(LIR_peq, fpcallobj_ins, callobj_ins), NULL); + + // Case 1: storing to native stack area. + + // Compute native stack slot and address offset we are storing to. + unsigned slot = uint16(sprop->shortid); + LIns *slot_ins; + if (sprop->setter == SetCallArg) + slot_ins = ArgClosureTraits::adj_slot_lir(lir, fp_ins, slot); + else + slot_ins = VarClosureTraits::adj_slot_lir(lir, fp_ins, slot); + LIns *offset_ins = lir->ins2(LIR_mul, slot_ins, INS_CONST(sizeof(double))); + + // Guard that we are not changing the type of the slot we are storing to. + LIns *callstackBase_ins = lir->insLoad(LIR_ldp, lirbuf->state, + offsetof(InterpState, callstackBase)); + LIns *frameInfo_ins = lir->insLoad(LIR_ldp, callstackBase_ins, 0); + LIns *typemap_ins = lir->ins2(LIR_addp, frameInfo_ins, INS_CONSTWORD(sizeof(FrameInfo))); + LIns *type_ins = lir->insLoad(LIR_ldcb, + lir->ins2(LIR_addp, typemap_ins, lir->ins_u2p(slot_ins)), 0); + JSTraceType type = getCoercedType(v); + if (type == TT_INT32 && !isPromoteInt(v_ins)) + type = TT_DOUBLE; + guard(true, + addName(lir->ins2(LIR_eq, type_ins, lir->insImm(type)), + "guard(type-stable set upvar)"), + BRANCH_EXIT); + + // Store to the native stack slot. + LIns *stackBase_ins = lir->insLoad(LIR_ldp, lirbuf->state, + offsetof(InterpState, stackBase)); + LIns *storeValue_ins = isPromoteInt(v_ins) ? demote(lir, v_ins) : v_ins; + lir->insStorei(storeValue_ins, + lir->ins2(LIR_addp, stackBase_ins, lir->ins_u2p(offset_ins)), 0); + LIns *br2 = lir->insBranch(LIR_j, NULL, NULL); + + // Case 2: calling builtin. + LIns *label1 = lir->ins0(LIR_label); + br1->setTarget(label1); + LIns* args[] = { + box_jsval(v, v_ins), + INS_CONST(SPROP_USERID(sprop)), + callobj_ins, + cx_ins + }; + LIns* call_ins = lir->insCall(ci, args); + guard(false, addName(lir->ins_eq0(call_ins), "guard(set upvar)"), STATUS_EXIT); + + LIns *label2 = lir->ins0(LIR_label); + br2->setTarget(label2); + + return JSRS_CONTINUE; +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_SetPropHit(JSPropCacheEntry* entry, JSScopeProperty* sprop) +{ + jsval& r = stackval(-1); + jsval& l = stackval(-2); + LIns* v_ins; + CHECK_STATUS(setProp(l, entry, sprop, r, v_ins)); + + jsbytecode* pc = cx->fp->regs->pc; + if (*pc != JSOP_INITPROP && pc[JSOP_SETPROP_LENGTH] != JSOP_POP) + set(&l, v_ins); + + return JSRS_CONTINUE; +} + +JS_REQUIRES_STACK VMSideExit* +TraceRecorder::enterDeepBailCall() +{ + // Take snapshot for js_DeepBail and store it in cx->bailExit. + VMSideExit* exit = snapshot(DEEP_BAIL_EXIT); + lir->insStorei(INS_CONSTPTR(exit), cx_ins, offsetof(JSContext, bailExit)); + + // Tell nanojit not to discard or defer stack writes before this call. + GuardRecord* guardRec = createGuardRecord(exit); + lir->insGuard(LIR_xbarrier, NULL, guardRec); + return exit; +} + +JS_REQUIRES_STACK void +TraceRecorder::leaveDeepBailCall() +{ + // Keep cx->bailExit null when it's invalid. + lir->insStorei(INS_NULL(), cx_ins, offsetof(JSContext, bailExit)); +} + +JS_REQUIRES_STACK void +TraceRecorder::finishGetProp(LIns* obj_ins, LIns* vp_ins, LIns* ok_ins, jsval* outp) +{ + // Store the boxed result (and this-object, if JOF_CALLOP) before the + // guard. The deep-bail case requires this. If the property get fails, + // these slots will be ignored anyway. + LIns* result_ins = lir->insLoad(LIR_ldp, vp_ins, 0); + set(outp, result_ins, true); + if (js_CodeSpec[*cx->fp->regs->pc].format & JOF_CALLOP) + set(outp + 1, obj_ins, true); + + // We need to guard on ok_ins, but this requires a snapshot of the state + // after this op. monitorRecording will do it for us. + pendingGuardCondition = ok_ins; + + // Note there is a boxed result sitting on the stack. The caller must leave + // it there for the time being, since the return type is not yet + // known. monitorRecording will emit the code to unbox it. + pendingUnboxSlot = outp; +} + +static inline bool +RootedStringToId(JSContext* cx, JSString** namep, jsid* idp) +{ + JSString* name = *namep; + if (name->isAtomized()) { + *idp = ATOM_TO_JSID((JSAtom*) STRING_TO_JSVAL(name)); + return true; + } + + JSAtom* atom = js_AtomizeString(cx, name, 0); + if (!atom) + return false; + *namep = ATOM_TO_STRING(atom); /* write back to GC root */ + *idp = ATOM_TO_JSID(atom); + return true; +} + +static JSBool FASTCALL +GetPropertyByName(JSContext* cx, JSObject* obj, JSString** namep, jsval* vp) +{ + js_LeaveTraceIfGlobalObject(cx, obj); + + jsid id; + if (!RootedStringToId(cx, namep, &id) || !obj->getProperty(cx, id, vp)) { + js_SetBuiltinError(cx); + return JS_FALSE; + } + return cx->interpState->builtinStatus == 0; +} +JS_DEFINE_CALLINFO_4(static, BOOL_FAIL, GetPropertyByName, CONTEXT, OBJECT, STRINGPTR, JSVALPTR, + 0, 0) + +// Convert the value in a slot to a string and store the resulting string back +// in the slot (typically in order to root it). +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::primitiveToStringInPlace(jsval* vp) +{ + jsval v = *vp; + JS_ASSERT(JSVAL_IS_PRIMITIVE(v)); + + if (!JSVAL_IS_STRING(v)) { + // v is not a string. Turn it into one. js_ValueToString is safe + // because v is not an object. + JSString *str = js_ValueToString(cx, v); + if (!str) + ABORT_TRACE_ERROR("failed to stringify element id"); + v = STRING_TO_JSVAL(str); + set(vp, stringify(*vp)); + + // Write the string back to the stack to save the interpreter some work + // and to ensure snapshots get the correct type for this slot. + *vp = v; + } + return JSRS_CONTINUE; +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::getPropertyByName(LIns* obj_ins, jsval* idvalp, jsval* outp) +{ + CHECK_STATUS(primitiveToStringInPlace(idvalp)); + enterDeepBailCall(); + + // Call GetPropertyByName. The vp parameter points to stack because this is + // what the interpreter currently does. obj and id are rooted on the + // interpreter stack, but the slot at vp is not a root. + LIns* vp_ins = addName(lir->insAlloc(sizeof(jsval)), "vp"); + LIns* idvalp_ins = addName(addr(idvalp), "idvalp"); + LIns* args[] = {vp_ins, idvalp_ins, obj_ins, cx_ins}; + LIns* ok_ins = lir->insCall(&GetPropertyByName_ci, args); + + // GetPropertyByName can assign to *idvalp, so the tracker has an incorrect + // entry for that address. Correct it. (If the value in the address is + // never used again, the usual case, Nanojit will kill this load.) + tracker.set(idvalp, lir->insLoad(LIR_ldp, idvalp_ins, 0)); + + finishGetProp(obj_ins, vp_ins, ok_ins, outp); + leaveDeepBailCall(); + return JSRS_CONTINUE; +} + +static JSBool FASTCALL +GetPropertyByIndex(JSContext* cx, JSObject* obj, int32 index, jsval* vp) +{ + js_LeaveTraceIfGlobalObject(cx, obj); + + JSAutoTempIdRooter idr(cx); + if (!js_Int32ToId(cx, index, idr.addr()) || !obj->getProperty(cx, idr.id(), vp)) { + js_SetBuiltinError(cx); + return JS_FALSE; + } + return cx->interpState->builtinStatus == 0; +} +JS_DEFINE_CALLINFO_4(static, BOOL_FAIL, GetPropertyByIndex, CONTEXT, OBJECT, INT32, JSVALPTR, 0, 0) + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::getPropertyByIndex(LIns* obj_ins, LIns* index_ins, jsval* outp) +{ + index_ins = makeNumberInt32(index_ins); + + // See note in getPropertyByName about vp. + enterDeepBailCall(); + LIns* vp_ins = addName(lir->insAlloc(sizeof(jsval)), "vp"); + LIns* args[] = {vp_ins, index_ins, obj_ins, cx_ins}; + LIns* ok_ins = lir->insCall(&GetPropertyByIndex_ci, args); + finishGetProp(obj_ins, vp_ins, ok_ins, outp); + leaveDeepBailCall(); + return JSRS_CONTINUE; +} + +static JSBool FASTCALL +GetPropertyById(JSContext* cx, JSObject* obj, jsid id, jsval* vp) +{ + js_LeaveTraceIfGlobalObject(cx, obj); + if (!obj->getProperty(cx, id, vp)) { + js_SetBuiltinError(cx); + return JS_FALSE; + } + return cx->interpState->builtinStatus == 0; +} +JS_DEFINE_CALLINFO_4(static, BOOL_FAIL, GetPropertyById, + CONTEXT, OBJECT, JSVAL, JSVALPTR, 0, 0) + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::getPropertyById(LIns* obj_ins, jsval* outp) +{ + // Find the atom. + JSAtom* atom; + jsbytecode* pc = cx->fp->regs->pc; + const JSCodeSpec& cs = js_CodeSpec[*pc]; + if (*pc == JSOP_LENGTH) { + atom = cx->runtime->atomState.lengthAtom; + } else if (JOF_TYPE(cs.format) == JOF_ATOM) { + atom = atoms[GET_INDEX(pc)]; + } else { + JS_ASSERT(JOF_TYPE(cs.format) == JOF_SLOTATOM); + atom = atoms[GET_INDEX(pc + SLOTNO_LEN)]; + } + + // Call GetPropertyById. See note in getPropertyByName about vp. + enterDeepBailCall(); + jsid id = ATOM_TO_JSID(atom); + LIns* vp_ins = addName(lir->insAlloc(sizeof(jsval)), "vp"); + LIns* args[] = {vp_ins, INS_CONSTWORD(id), obj_ins, cx_ins}; + LIns* ok_ins = lir->insCall(&GetPropertyById_ci, args); + finishGetProp(obj_ins, vp_ins, ok_ins, outp); + leaveDeepBailCall(); + return JSRS_CONTINUE; +} + +/* Manually inlined, specialized copy of js_NativeGet. */ +static JSBool FASTCALL +GetPropertyWithNativeGetter(JSContext* cx, JSObject* obj, JSScopeProperty* sprop, jsval* vp) +{ + js_LeaveTraceIfGlobalObject(cx, obj); + +#ifdef DEBUG + JSProperty* prop; + JSObject* pobj; + JS_ASSERT(obj->lookupProperty(cx, sprop->id, &pobj, &prop)); + JS_ASSERT(prop == (JSProperty*) sprop); + pobj->dropProperty(cx, prop); +#endif + + // js_GetSprop contains a special case for With objects. We can elide it + // here because With objects are, we claim, never on the operand stack. + JS_ASSERT(STOBJ_GET_CLASS(obj) != &js_WithClass); + + *vp = JSVAL_VOID; + if (!sprop->getter(cx, obj, SPROP_USERID(sprop), vp)) { + js_SetBuiltinError(cx); + return JS_FALSE; + } + return cx->interpState->builtinStatus == 0; +} +JS_DEFINE_CALLINFO_4(static, BOOL_FAIL, GetPropertyWithNativeGetter, + CONTEXT, OBJECT, SCOPEPROP, JSVALPTR, 0, 0) + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::getPropertyWithNativeGetter(LIns* obj_ins, JSScopeProperty* sprop, jsval* outp) +{ + JS_ASSERT(!(sprop->attrs & JSPROP_GETTER)); + JS_ASSERT(sprop->slot == SPROP_INVALID_SLOT); + JS_ASSERT(!SPROP_HAS_STUB_GETTER(sprop)); + + // Call GetPropertyWithNativeGetter. See note in getPropertyByName about vp. + // FIXME - We should call the getter directly. Using a builtin function for + // now because it buys some extra asserts. See bug 508310. + enterDeepBailCall(); + LIns* vp_ins = addName(lir->insAlloc(sizeof(jsval)), "vp"); + LIns* args[] = {vp_ins, INS_CONSTPTR(sprop), obj_ins, cx_ins}; + LIns* ok_ins = lir->insCall(&GetPropertyWithNativeGetter_ci, args); + finishGetProp(obj_ins, vp_ins, ok_ins, outp); + leaveDeepBailCall(); + return JSRS_CONTINUE; +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_GETELEM() +{ + bool call = *cx->fp->regs->pc == JSOP_CALLELEM; + + jsval& idx = stackval(-1); + jsval& lval = stackval(-2); + + LIns* obj_ins = get(&lval); + LIns* idx_ins = get(&idx); + + // Special case for array-like access of strings. + if (JSVAL_IS_STRING(lval) && isInt32(idx)) { + if (call) + ABORT_TRACE("JSOP_CALLELEM on a string"); + int i = asInt32(idx); + if (size_t(i) >= JSVAL_TO_STRING(lval)->length()) + ABORT_TRACE("Invalid string index in JSOP_GETELEM"); + idx_ins = makeNumberInt32(idx_ins); + LIns* args[] = { idx_ins, obj_ins, cx_ins }; + LIns* unitstr_ins = lir->insCall(&js_String_getelem_ci, args); + guard(false, lir->ins_peq0(unitstr_ins), MISMATCH_EXIT); + set(&lval, unitstr_ins); + return JSRS_CONTINUE; + } + + if (JSVAL_IS_PRIMITIVE(lval)) + ABORT_TRACE("JSOP_GETLEM on a primitive"); + ABORT_IF_XML(lval); + + JSObject* obj = JSVAL_TO_OBJECT(lval); + if (obj == globalObj) + ABORT_TRACE("JSOP_GETELEM on global"); + LIns* v_ins; + + /* Property access using a string name or something we have to stringify. */ + if (!JSVAL_IS_INT(idx)) { + if (!JSVAL_IS_PRIMITIVE(idx)) + ABORT_TRACE("object used as index"); + + return getPropertyByName(obj_ins, &idx, &lval); + } + + if (STOBJ_GET_CLASS(obj) == &js_ArgumentsClass) { + unsigned depth; + JSStackFrame *afp = guardArguments(obj, obj_ins, &depth); + if (afp) { + uintN int_idx = JSVAL_TO_INT(idx); + jsval* vp = &afp->argv[int_idx]; + if (idx_ins->isconstf()) { + if (int_idx >= 0 && int_idx < afp->argc) + v_ins = get(vp); + else + v_ins = INS_VOID(); + } else { + // If the index is not a constant expression, we generate LIR to load the value from + // the native stack area. The guard on js_ArgumentClass above ensures the up-to-date + // value has been written back to the native stack area. + idx_ins = makeNumberInt32(idx_ins); + + if (int_idx < 0 || int_idx >= afp->argc) + ABORT_TRACE("cannot trace arguments with out of range index"); + + guard(true, + addName(lir->ins2(LIR_ge, idx_ins, INS_CONST(0)), + "guard(index >= 0)"), + MISMATCH_EXIT); + guard(true, + addName(lir->ins2(LIR_lt, idx_ins, INS_CONST(afp->argc)), + "guard(index < argc)"), + MISMATCH_EXIT); + + JSTraceType type = getCoercedType(*vp); + + // Guard that the argument has the same type on trace as during recording. + LIns* typemap_ins; + if (depth == 0) { + // In this case, we are in the same frame where the arguments object was created. + // The entry type map is not necessarily up-to-date, so we capture a new type map + // for this point in the code. + unsigned stackSlots = NativeStackSlots(cx, 0 /* callDepth */); + if (stackSlots * sizeof(JSTraceType) > LirBuffer::MAX_SKIP_PAYLOAD_SZB) + ABORT_TRACE("|arguments| requires saving too much stack"); + JSTraceType* typemap = new (*traceMonitor->dataAlloc) JSTraceType[stackSlots]; + DetermineTypesVisitor detVisitor(*this, typemap); + VisitStackSlots(detVisitor, cx, 0); + typemap_ins = INS_CONSTPTR(typemap + 2 /* callee, this */); + } else { + // In this case, we are in a deeper frame from where the arguments object was + // created. The type map at the point of the call out from the creation frame + // is accurate. + // Note: this relies on the assumption that we abort on setting an element of + // an arguments object in any deeper frame. + LIns* fip_ins = lir->insLoad(LIR_ldp, lirbuf->rp, (callDepth-depth)*sizeof(FrameInfo*)); + typemap_ins = lir->ins2(LIR_add, fip_ins, INS_CONST(sizeof(FrameInfo) + 2/*callee,this*/ * sizeof(JSTraceType))); + } + + LIns* typep_ins = lir->ins2(LIR_piadd, typemap_ins, + lir->ins_u2p(lir->ins2(LIR_mul, + idx_ins, + INS_CONST(sizeof(JSTraceType))))); + LIns* type_ins = lir->insLoad(LIR_ldcb, typep_ins, 0); + guard(true, + addName(lir->ins2(LIR_eq, type_ins, lir->insImm(type)), + "guard(type-stable upvar)"), + BRANCH_EXIT); + + // Read the value out of the native stack area. + guard(true, lir->ins2(LIR_ult, idx_ins, INS_CONST(afp->argc)), + snapshot(BRANCH_EXIT)); + size_t stackOffset = -treeInfo->nativeStackBase + nativeStackOffset(&afp->argv[0]); + LIns* args_addr_ins = lir->ins2(LIR_piadd, lirbuf->sp, INS_CONSTWORD(stackOffset)); + LIns* argi_addr_ins = lir->ins2(LIR_piadd, + args_addr_ins, + lir->ins_u2p(lir->ins2(LIR_mul, + idx_ins, + INS_CONST(sizeof(double))))); + v_ins = stackLoad(argi_addr_ins, type); + } + JS_ASSERT(v_ins); + set(&lval, v_ins); + if (call) + set(&idx, obj_ins); + return JSRS_CONTINUE; + } + ABORT_TRACE("can't reach arguments object's frame"); + } + if (js_IsDenseArray(obj)) { + // Fast path for dense arrays accessed with a integer index. + jsval* vp; + LIns* addr_ins; + + guardDenseArray(obj, obj_ins, BRANCH_EXIT); + CHECK_STATUS(denseArrayElement(lval, idx, vp, v_ins, addr_ins)); + set(&lval, v_ins); + if (call) + set(&idx, obj_ins); + return JSRS_CONTINUE; + } + + return getPropertyByIndex(obj_ins, idx_ins, &lval); +} + +/* Functions used by JSOP_SETELEM */ + +static JSBool FASTCALL +SetPropertyByName(JSContext* cx, JSObject* obj, JSString** namep, jsval* vp) +{ + js_LeaveTraceIfGlobalObject(cx, obj); + + jsid id; + if (!RootedStringToId(cx, namep, &id) || !obj->setProperty(cx, id, vp)) { + js_SetBuiltinError(cx); + return JS_FALSE; + } + return cx->interpState->builtinStatus == 0; +} +JS_DEFINE_CALLINFO_4(static, BOOL_FAIL, SetPropertyByName, CONTEXT, OBJECT, STRINGPTR, JSVALPTR, + 0, 0) + +static JSBool FASTCALL +InitPropertyByName(JSContext* cx, JSObject* obj, JSString** namep, jsval val) +{ + js_LeaveTraceIfGlobalObject(cx, obj); + + jsid id; + if (!RootedStringToId(cx, namep, &id) || + !obj->defineProperty(cx, id, val, NULL, NULL, JSPROP_ENUMERATE)) { + js_SetBuiltinError(cx); + return JS_FALSE; + } + return cx->interpState->builtinStatus == 0; +} +JS_DEFINE_CALLINFO_4(static, BOOL_FAIL, InitPropertyByName, CONTEXT, OBJECT, STRINGPTR, JSVAL, + 0, 0) + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::initOrSetPropertyByName(LIns* obj_ins, jsval* idvalp, jsval* rvalp, bool init) +{ + CHECK_STATUS(primitiveToStringInPlace(idvalp)); + + LIns* rval_ins = box_jsval(*rvalp, get(rvalp)); + + enterDeepBailCall(); + + LIns* ok_ins; + LIns* idvalp_ins = addName(addr(idvalp), "idvalp"); + if (init) { + LIns* args[] = {rval_ins, idvalp_ins, obj_ins, cx_ins}; + ok_ins = lir->insCall(&InitPropertyByName_ci, args); + } else { + // See note in getPropertyByName about vp. + LIns* vp_ins = addName(lir->insAlloc(sizeof(jsval)), "vp"); + lir->insStorei(rval_ins, vp_ins, 0); + LIns* args[] = {vp_ins, idvalp_ins, obj_ins, cx_ins}; + ok_ins = lir->insCall(&SetPropertyByName_ci, args); + } + pendingGuardCondition = ok_ins; + + leaveDeepBailCall(); + return JSRS_CONTINUE; +} + +static JSBool FASTCALL +SetPropertyByIndex(JSContext* cx, JSObject* obj, int32 index, jsval* vp) +{ + js_LeaveTraceIfGlobalObject(cx, obj); + + JSAutoTempIdRooter idr(cx); + if (!js_Int32ToId(cx, index, idr.addr()) || !obj->setProperty(cx, idr.id(), vp)) { + js_SetBuiltinError(cx); + return JS_FALSE; + } + return cx->interpState->builtinStatus == 0; +} +JS_DEFINE_CALLINFO_4(static, BOOL_FAIL, SetPropertyByIndex, CONTEXT, OBJECT, INT32, JSVALPTR, 0, 0) + +static JSBool FASTCALL +InitPropertyByIndex(JSContext* cx, JSObject* obj, int32 index, jsval val) +{ + js_LeaveTraceIfGlobalObject(cx, obj); + + JSAutoTempIdRooter idr(cx); + if (!js_Int32ToId(cx, index, idr.addr()) || + !obj->defineProperty(cx, idr.id(), val, NULL, NULL, JSPROP_ENUMERATE)) { + js_SetBuiltinError(cx); + return JS_FALSE; + } + return cx->interpState->builtinStatus == 0; +} +JS_DEFINE_CALLINFO_4(static, BOOL_FAIL, InitPropertyByIndex, CONTEXT, OBJECT, INT32, JSVAL, 0, 0) + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::initOrSetPropertyByIndex(LIns* obj_ins, LIns* index_ins, jsval* rvalp, bool init) +{ + index_ins = makeNumberInt32(index_ins); + + LIns* rval_ins = box_jsval(*rvalp, get(rvalp)); + + enterDeepBailCall(); + + LIns* ok_ins; + if (init) { + LIns* args[] = {rval_ins, index_ins, obj_ins, cx_ins}; + ok_ins = lir->insCall(&InitPropertyByIndex_ci, args); + } else { + // See note in getPropertyByName about vp. + LIns* vp_ins = addName(lir->insAlloc(sizeof(jsval)), "vp"); + lir->insStorei(rval_ins, vp_ins, 0); + LIns* args[] = {vp_ins, index_ins, obj_ins, cx_ins}; + ok_ins = lir->insCall(&SetPropertyByIndex_ci, args); + } + pendingGuardCondition = ok_ins; + + leaveDeepBailCall(); + return JSRS_CONTINUE; +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_SETELEM() +{ + jsval& v = stackval(-1); + jsval& idx = stackval(-2); + jsval& lval = stackval(-3); + + if (JSVAL_IS_PRIMITIVE(lval)) + ABORT_TRACE("left JSOP_SETELEM operand is not an object"); + ABORT_IF_XML(lval); + + JSObject* obj = JSVAL_TO_OBJECT(lval); + LIns* obj_ins = get(&lval); + LIns* idx_ins = get(&idx); + LIns* v_ins = get(&v); + + if (JS_InstanceOf(cx, obj, &js_ArgumentsClass, NULL)) + ABORT_TRACE("can't trace setting elements of the |arguments| object"); + + if (obj == globalObj) + ABORT_TRACE("can't trace setting elements on the global object"); + + if (!JSVAL_IS_INT(idx)) { + if (!JSVAL_IS_PRIMITIVE(idx)) + ABORT_TRACE("non-primitive index"); + CHECK_STATUS(initOrSetPropertyByName(obj_ins, &idx, &v, + *cx->fp->regs->pc == JSOP_INITELEM)); + } else if (JSVAL_TO_INT(idx) < 0 || !OBJ_IS_DENSE_ARRAY(cx, obj)) { + CHECK_STATUS(initOrSetPropertyByIndex(obj_ins, idx_ins, &v, + *cx->fp->regs->pc == JSOP_INITELEM)); + } else { + // Fast path: assigning to element of dense array. + + // Make sure the array is actually dense. + if (!guardDenseArray(obj, obj_ins, BRANCH_EXIT)) + return JSRS_STOP; + + // The index was on the stack and is therefore a LIR float. Force it to + // be an integer. + idx_ins = makeNumberInt32(idx_ins); + + // Box the value so we can use one builtin instead of having to add one + // builtin for every storage type. Special case for integers though, + // since they are so common. + LIns* res_ins; + LIns* args[] = { NULL, idx_ins, obj_ins, cx_ins }; + if (isNumber(v)) { + if (isPromoteInt(v_ins)) { + args[0] = ::demote(lir, v_ins); + res_ins = lir->insCall(&js_Array_dense_setelem_int_ci, args); + } else { + args[0] = v_ins; + res_ins = lir->insCall(&js_Array_dense_setelem_double_ci, args); + } + } else { + LIns* args[] = { box_jsval(v, v_ins), idx_ins, obj_ins, cx_ins }; + res_ins = lir->insCall(&js_Array_dense_setelem_ci, args); + } + guard(false, lir->ins_eq0(res_ins), MISMATCH_EXIT); + } + + jsbytecode* pc = cx->fp->regs->pc; + if (*pc == JSOP_SETELEM && pc[JSOP_SETELEM_LENGTH] != JSOP_POP) + set(&lval, v_ins); + + return JSRS_CONTINUE; +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_CALLNAME() +{ + JSObject* obj = cx->fp->scopeChain; + if (obj != globalObj) { + jsval* vp; + LIns* ins; + NameResult nr; + CHECK_STATUS(scopeChainProp(obj, vp, ins, nr)); + stack(0, ins); + stack(1, INS_CONSTOBJ(globalObj)); + return JSRS_CONTINUE; + } + + LIns* obj_ins = scopeChain(); + JSObject* obj2; + jsuword pcval; + + CHECK_STATUS(test_property_cache(obj, obj_ins, obj2, pcval)); + + if (PCVAL_IS_NULL(pcval) || !PCVAL_IS_OBJECT(pcval)) + ABORT_TRACE("callee is not an object"); + + JS_ASSERT(HAS_FUNCTION_CLASS(PCVAL_TO_OBJECT(pcval))); + + stack(0, INS_CONSTOBJ(PCVAL_TO_OBJECT(pcval))); + stack(1, obj_ins); + return JSRS_CONTINUE; +} + +JS_DEFINE_CALLINFO_5(extern, UINT32, GetUpvarArgOnTrace, CONTEXT, UINT32, INT32, UINT32, + DOUBLEPTR, 0, 0) +JS_DEFINE_CALLINFO_5(extern, UINT32, GetUpvarVarOnTrace, CONTEXT, UINT32, INT32, UINT32, + DOUBLEPTR, 0, 0) +JS_DEFINE_CALLINFO_5(extern, UINT32, GetUpvarStackOnTrace, CONTEXT, UINT32, INT32, UINT32, + DOUBLEPTR, 0, 0) + +/* + * Record LIR to get the given upvar. Return the LIR instruction for the upvar + * value. NULL is returned only on a can't-happen condition with an invalid + * typemap. The value of the upvar is returned as v. + */ +JS_REQUIRES_STACK LIns* +TraceRecorder::upvar(JSScript* script, JSUpvarArray* uva, uintN index, jsval& v) +{ + /* + * Try to find the upvar in the current trace's tracker. For &vr to be + * the address of the jsval found in js_GetUpvar, we must initialize + * vr directly with the result, so it is a reference to the same location. + * It does not work to assign the result to v, because v is an already + * existing reference that points to something else. + */ + uint32 cookie = uva->vector[index]; + jsval& vr = js_GetUpvar(cx, script->staticLevel, cookie); + v = vr; + + if (known(&vr)) + return get(&vr); + + /* + * The upvar is not in the current trace, so get the upvar value exactly as + * the interpreter does and unbox. + */ + uint32 level = script->staticLevel - UPVAR_FRAME_SKIP(cookie); + uint32 cookieSlot = UPVAR_FRAME_SLOT(cookie); + JSStackFrame* fp = cx->display[level]; + const CallInfo* ci; + int32 slot; + if (!fp->fun) { + ci = &GetUpvarStackOnTrace_ci; + slot = cookieSlot; + } else if (cookieSlot < fp->fun->nargs) { + ci = &GetUpvarArgOnTrace_ci; + slot = cookieSlot; + } else if (cookieSlot == CALLEE_UPVAR_SLOT) { + ci = &GetUpvarArgOnTrace_ci; + slot = -2; + } else { + ci = &GetUpvarVarOnTrace_ci; + slot = cookieSlot - fp->fun->nargs; + } + + LIns* outp = lir->insAlloc(sizeof(double)); + LIns* args[] = { + outp, + INS_CONST(callDepth), + INS_CONST(slot), + INS_CONST(level), + cx_ins + }; + LIns* call_ins = lir->insCall(ci, args); + JSTraceType type = getCoercedType(v); + guard(true, + addName(lir->ins2(LIR_eq, call_ins, lir->insImm(type)), + "guard(type-stable upvar)"), + BRANCH_EXIT); + return stackLoad(outp, type); +} + +/* + * Generate LIR to load a value from the native stack. This method ensures that + * the correct LIR load operator is used. + */ +LIns* TraceRecorder::stackLoad(LIns* base, uint8 type) +{ + LOpcode loadOp; + switch (type) { + case TT_DOUBLE: + loadOp = LIR_ldq; + break; + case TT_OBJECT: + case TT_STRING: + case TT_FUNCTION: + case TT_NULL: + loadOp = LIR_ldp; + break; + case TT_INT32: + case TT_PSEUDOBOOLEAN: + loadOp = LIR_ld; + break; + case TT_JSVAL: + default: + JS_NOT_REACHED("found jsval type in an upvar type map entry"); + return NULL; + } + + LIns* result = lir->insLoad(loadOp, base, 0); + if (type == TT_INT32) + result = lir->ins1(LIR_i2f, result); + return result; +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_GETUPVAR() +{ + uintN index = GET_UINT16(cx->fp->regs->pc); + JSScript *script = cx->fp->script; + JSUpvarArray* uva = script->upvars(); + JS_ASSERT(index < uva->length); + + jsval v; + LIns* upvar_ins = upvar(script, uva, index, v); + if (!upvar_ins) + return JSRS_STOP; + stack(0, upvar_ins); + return JSRS_CONTINUE; +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_CALLUPVAR() +{ + CHECK_STATUS(record_JSOP_GETUPVAR()); + stack(1, INS_NULL()); + return JSRS_CONTINUE; +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_GETDSLOT() +{ + JSObject* callee = JSVAL_TO_OBJECT(cx->fp->argv[-2]); + LIns* callee_ins = get(&cx->fp->argv[-2]); + + unsigned index = GET_UINT16(cx->fp->regs->pc); + LIns* dslots_ins = NULL; + LIns* v_ins = stobj_get_dslot(callee_ins, index, dslots_ins); + + stack(0, unbox_jsval(callee->dslots[index], v_ins, snapshot(BRANCH_EXIT))); + return JSRS_CONTINUE; +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_CALLDSLOT() +{ + CHECK_STATUS(record_JSOP_GETDSLOT()); + stack(1, INS_NULL()); + return JSRS_CONTINUE; +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::guardCallee(jsval& callee) +{ + JS_ASSERT(VALUE_IS_FUNCTION(cx, callee)); + + VMSideExit* branchExit = snapshot(BRANCH_EXIT); + JSObject* callee_obj = JSVAL_TO_OBJECT(callee); + LIns* callee_ins = get(&callee); + + treeInfo->gcthings.addUnique(callee); + guard(true, + lir->ins2(LIR_peq, + stobj_get_private(callee_ins), + INS_CONSTPTR(callee_obj->getPrivate())), + branchExit); + guard(true, + lir->ins2(LIR_peq, + stobj_get_parent(callee_ins), + INS_CONSTOBJ(OBJ_GET_PARENT(cx, callee_obj))), + branchExit); + return JSRS_CONTINUE; +} + +/* + * Prepare the given |arguments| object to be accessed on trace. If the return + * value is non-NULL, then the given |arguments| object refers to a frame on + * the current trace and is guaranteed to refer to the same frame on trace for + * all later executions. + */ +JS_REQUIRES_STACK JSStackFrame * +TraceRecorder::guardArguments(JSObject *obj, LIns* obj_ins, unsigned *depthp) +{ + JS_ASSERT(STOBJ_GET_CLASS(obj) == &js_ArgumentsClass); + + JSStackFrame *afp = frameIfInRange(obj, depthp); + if (!afp) + return NULL; + + VMSideExit *exit = snapshot(MISMATCH_EXIT); + guardClass(obj, obj_ins, &js_ArgumentsClass, exit); + + LIns* args_ins = get(&afp->argsobj); + LIns* cmp = lir->ins2(LIR_peq, args_ins, obj_ins); + lir->insGuard(LIR_xf, cmp, createGuardRecord(exit)); + return afp; +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::interpretedFunctionCall(jsval& fval, JSFunction* fun, uintN argc, bool constructing) +{ + if (JS_GetGlobalForObject(cx, JSVAL_TO_OBJECT(fval)) != globalObj) + ABORT_TRACE("JSOP_CALL or JSOP_NEW crosses global scopes"); + + JSStackFrame* fp = cx->fp; + + // TODO: track the copying via the tracker... + if (argc < fun->nargs && + jsuword(fp->regs->sp + (fun->nargs - argc)) > cx->stackPool.current->limit) { + ABORT_TRACE("can't trace calls with too few args requiring argv move"); + } + + // Generate a type map for the outgoing frame and stash it in the LIR + unsigned stackSlots = NativeStackSlots(cx, 0 /* callDepth */); + if (sizeof(FrameInfo) + stackSlots * sizeof(JSTraceType) > LirBuffer::MAX_SKIP_PAYLOAD_SZB) + ABORT_TRACE("interpreted function call requires saving too much stack"); + FrameInfo* fi = (FrameInfo*) + traceMonitor->dataAlloc->alloc(sizeof(FrameInfo) + + stackSlots * sizeof(JSTraceType)); + JSTraceType* typemap = reinterpret_cast(fi + 1); + + DetermineTypesVisitor detVisitor(*this, typemap); + VisitStackSlots(detVisitor, cx, 0); + + JS_ASSERT(argc < FrameInfo::CONSTRUCTING_FLAG); + + treeInfo->gcthings.addUnique(fval); + fi->block = fp->blockChain; + if (fp->blockChain) + treeInfo->gcthings.addUnique(OBJECT_TO_JSVAL(fp->blockChain)); + fi->pc = fp->regs->pc; + fi->imacpc = fp->imacpc; + fi->spdist = fp->regs->sp - fp->slots; + fi->set_argc(argc, constructing); + fi->callerHeight = NativeStackSlots(cx, 0) - (2 + argc); + fi->callerArgc = fp->argc; + + unsigned callDepth = getCallDepth(); + if (callDepth >= treeInfo->maxCallDepth) + treeInfo->maxCallDepth = callDepth + 1; + + lir->insStorei(INS_CONSTPTR(fi), lirbuf->rp, callDepth * sizeof(FrameInfo*)); + + atoms = fun->u.i.script->atomMap.vector; + return JSRS_CONTINUE; +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_CALL() +{ + uintN argc = GET_ARGC(cx->fp->regs->pc); + cx->fp->assertValidStackDepth(argc + 2); + return functionCall(argc, + (cx->fp->imacpc && *cx->fp->imacpc == JSOP_APPLY) + ? JSOP_APPLY + : JSOP_CALL); +} + +static jsbytecode* apply_imacro_table[] = { + apply_imacros.apply0, + apply_imacros.apply1, + apply_imacros.apply2, + apply_imacros.apply3, + apply_imacros.apply4, + apply_imacros.apply5, + apply_imacros.apply6, + apply_imacros.apply7, + apply_imacros.apply8 +}; + +static jsbytecode* call_imacro_table[] = { + apply_imacros.call0, + apply_imacros.call1, + apply_imacros.call2, + apply_imacros.call3, + apply_imacros.call4, + apply_imacros.call5, + apply_imacros.call6, + apply_imacros.call7, + apply_imacros.call8 +}; + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_APPLY() +{ + JSStackFrame* fp = cx->fp; + jsbytecode *pc = fp->regs->pc; + uintN argc = GET_ARGC(pc); + cx->fp->assertValidStackDepth(argc + 2); + + jsval* vp = fp->regs->sp - (argc + 2); + jsuint length = 0; + JSObject* aobj = NULL; + LIns* aobj_ins = NULL; + + JS_ASSERT(!fp->imacpc); + + if (!VALUE_IS_FUNCTION(cx, vp[0])) + return record_JSOP_CALL(); + ABORT_IF_XML(vp[0]); + + JSObject* obj = JSVAL_TO_OBJECT(vp[0]); + JSFunction* fun = GET_FUNCTION_PRIVATE(cx, obj); + if (FUN_INTERPRETED(fun)) + return record_JSOP_CALL(); + + bool apply = (JSFastNative)fun->u.n.native == js_fun_apply; + if (!apply && (JSFastNative)fun->u.n.native != js_fun_call) + return record_JSOP_CALL(); + + /* + * We don't trace apply and call with a primitive 'this', which is the + * first positional parameter. + */ + if (argc > 0 && !JSVAL_IS_OBJECT(vp[2])) + return record_JSOP_CALL(); + + /* + * Guard on the identity of this, which is the function we are applying. + */ + if (!VALUE_IS_FUNCTION(cx, vp[1])) + ABORT_TRACE("callee is not a function"); + CHECK_STATUS(guardCallee(vp[1])); + + if (apply && argc >= 2) { + if (argc != 2) + ABORT_TRACE("apply with excess arguments"); + if (JSVAL_IS_PRIMITIVE(vp[3])) + ABORT_TRACE("arguments parameter of apply is primitive"); + aobj = JSVAL_TO_OBJECT(vp[3]); + aobj_ins = get(&vp[3]); + + /* + * We trace dense arrays and arguments objects. The code we generate + * for apply uses imacros to handle a specific number of arguments. + */ + if (OBJ_IS_DENSE_ARRAY(cx, aobj)) { + guardDenseArray(aobj, aobj_ins); + length = jsuint(aobj->fslots[JSSLOT_ARRAY_LENGTH]); + guard(true, + lir->ins2i(LIR_eq, + p2i(stobj_get_fslot(aobj_ins, JSSLOT_ARRAY_LENGTH)), + length), + BRANCH_EXIT); + } else if (OBJ_GET_CLASS(cx, aobj) == &js_ArgumentsClass) { + unsigned depth; + JSStackFrame *afp = guardArguments(aobj, aobj_ins, &depth); + if (!afp) + ABORT_TRACE("can't reach arguments object's frame"); + length = afp->argc; + } else { + ABORT_TRACE("arguments parameter of apply is not a dense array or argments object"); + } + + if (length >= JS_ARRAY_LENGTH(apply_imacro_table)) + ABORT_TRACE("too many arguments to apply"); + + return call_imacro(apply_imacro_table[length]); + } + + if (argc >= JS_ARRAY_LENGTH(call_imacro_table)) + ABORT_TRACE("too many arguments to call"); + + return call_imacro(call_imacro_table[argc]); +} + +static JSBool FASTCALL +CatchStopIteration_tn(JSContext* cx, JSBool ok, jsval* vp) +{ + if (!ok && cx->throwing && js_ValueIsStopIteration(cx->exception)) { + cx->throwing = JS_FALSE; + cx->exception = JSVAL_VOID; + *vp = JSVAL_HOLE; + return JS_TRUE; + } + return ok; +} + +JS_DEFINE_TRCINFO_1(CatchStopIteration_tn, + (3, (static, BOOL, CatchStopIteration_tn, CONTEXT, BOOL, JSVALPTR, 0, 0))) + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_NativeCallComplete() +{ + if (pendingSpecializedNative == IGNORE_NATIVE_CALL_COMPLETE_CALLBACK) + return JSRS_CONTINUE; + + jsbytecode* pc = cx->fp->regs->pc; + + JS_ASSERT(pendingSpecializedNative); + JS_ASSERT(*pc == JSOP_CALL || *pc == JSOP_APPLY || *pc == JSOP_NEW || *pc == JSOP_SETPROP); + + jsval& v = stackval(-1); + LIns* v_ins = get(&v); + + /* + * At this point the generated code has already called the native function + * and we can no longer fail back to the original pc location (JSOP_CALL) + * because that would cause the interpreter to re-execute the native + * function, which might have side effects. + * + * Instead, the snapshot() call below sees that we are currently parked on + * a traceable native's JSOP_CALL instruction, and it will advance the pc + * to restore by the length of the current opcode. If the native's return + * type is jsval, snapshot() will also indicate in the type map that the + * element on top of the stack is a boxed value which doesn't need to be + * boxed if the type guard generated by unbox_jsval() fails. + */ + + if (JSTN_ERRTYPE(pendingSpecializedNative) == FAIL_STATUS) { + /* Keep cx->bailExit null when it's invalid. */ + lir->insStorei(INS_NULL(), cx_ins, (int) offsetof(JSContext, bailExit)); + + LIns* status = lir->insLoad(LIR_ld, lirbuf->state, (int) offsetof(InterpState, builtinStatus)); + if (pendingSpecializedNative == &generatedSpecializedNative) { + LIns* ok_ins = v_ins; + + /* + * Custom implementations of Iterator.next() throw a StopIteration exception. + * Catch and clear it and set the return value to JSVAL_HOLE in this case. + */ + if (uintptr_t(pc - nextiter_imacros.custom_iter_next) < + sizeof(nextiter_imacros.custom_iter_next)) { + LIns* args[] = { native_rval_ins, ok_ins, cx_ins }; /* reverse order */ + ok_ins = lir->insCall(&CatchStopIteration_tn_ci, args); + } + + /* + * If we run a generic traceable native, the return value is in the argument + * vector for native function calls. The actual return value of the native is a JSBool + * indicating the error status. + */ + v_ins = lir->insLoad(LIR_ldp, native_rval_ins, 0); + if (*pc == JSOP_NEW) { + LIns* x = lir->ins_peq0(lir->ins2(LIR_piand, v_ins, INS_CONSTWORD(JSVAL_TAGMASK))); + x = lir->ins_choose(x, v_ins, INS_CONSTWORD(0)); + v_ins = lir->ins_choose(lir->ins_peq0(x), newobj_ins, x); + } + set(&v, v_ins); + + propagateFailureToBuiltinStatus(ok_ins, status); + } + guard(true, lir->ins_eq0(status), STATUS_EXIT); + } + + JSRecordingStatus ok = JSRS_CONTINUE; + if (pendingSpecializedNative->flags & JSTN_UNBOX_AFTER) { + /* + * If we side exit on the unboxing code due to a type change, make sure that the boxed + * value is actually currently associated with that location, and that we are talking + * about the top of the stack here, which is where we expected boxed values. + */ + JS_ASSERT(&v == &cx->fp->regs->sp[-1] && get(&v) == v_ins); + set(&v, unbox_jsval(v, v_ins, snapshot(BRANCH_EXIT))); + } else if (JSTN_ERRTYPE(pendingSpecializedNative) == FAIL_NEG) { + /* Already added i2f in functionCall. */ + JS_ASSERT(JSVAL_IS_NUMBER(v)); + } else { + /* Convert the result to double if the builtin returns int32. */ + if (JSVAL_IS_NUMBER(v) && + (pendingSpecializedNative->builtin->_argtypes & ARGSIZE_MASK_ANY) == ARGSIZE_I) { + set(&v, lir->ins1(LIR_i2f, v_ins)); + } + } + + // We'll null pendingSpecializedNative in monitorRecording, on the next op + // cycle. There must be a next op since the stack is non-empty. + return ok; +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::name(jsval*& vp, LIns*& ins, NameResult& nr) +{ + JSObject* obj = cx->fp->scopeChain; + if (obj != globalObj) + return scopeChainProp(obj, vp, ins, nr); + + /* Can't use prop here, because we don't want unboxing from global slots. */ + LIns* obj_ins = scopeChain(); + uint32 slot; + + JSObject* obj2; + jsuword pcval; + + /* + * Property cache ensures that we are dealing with an existing property, + * and guards the shape for us. + */ + CHECK_STATUS(test_property_cache(obj, obj_ins, obj2, pcval)); + + /* Abort if property doesn't exist (interpreter will report an error.) */ + if (PCVAL_IS_NULL(pcval)) + ABORT_TRACE("named property not found"); + + /* Insist on obj being the directly addressed object. */ + if (obj2 != obj) + ABORT_TRACE("name() hit prototype chain"); + + /* Don't trace getter or setter calls, our caller wants a direct slot. */ + if (PCVAL_IS_SPROP(pcval)) { + JSScopeProperty* sprop = PCVAL_TO_SPROP(pcval); + if (!isValidSlot(OBJ_SCOPE(obj), sprop)) + ABORT_TRACE("name() not accessing a valid slot"); + slot = sprop->slot; + } else { + if (!PCVAL_IS_SLOT(pcval)) + ABORT_TRACE("PCE is not a slot"); + slot = PCVAL_TO_SLOT(pcval); + } + + if (!lazilyImportGlobalSlot(slot)) + ABORT_TRACE("lazy import of global slot failed"); + + vp = &STOBJ_GET_SLOT(obj, slot); + ins = get(vp); + nr.tracked = true; + return JSRS_CONTINUE; +} + +/* + * Get a property. The current opcode has JOF_ATOM. + * + * There are two modes. The caller must pass nonnull pointers for either outp + * or both slotp and v_insp. In the latter case, we require a plain old + * property with a slot; if the property turns out to be anything else, abort + * tracing (rather than emit a call to a native getter or GetAnyProperty). + */ +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::prop(JSObject* obj, LIns* obj_ins, uint32 *slotp, LIns** v_insp, jsval *outp) +{ + JS_ASSERT((slotp && v_insp && !outp) || (!slotp && !v_insp && outp)); + + /* + * Can't specialize to assert obj != global, must guard to avoid aliasing + * stale homes of stacked global variables. + */ + CHECK_STATUS(guardNotGlobalObject(obj, obj_ins)); + + /* + * Property cache ensures that we are dealing with an existing property, + * and guards the shape for us. + */ + JSObject* obj2; + jsuword pcval; + CHECK_STATUS(test_property_cache(obj, obj_ins, obj2, pcval)); + + /* Check for non-existent property reference, which results in undefined. */ + const JSCodeSpec& cs = js_CodeSpec[*cx->fp->regs->pc]; + if (PCVAL_IS_NULL(pcval)) { + if (slotp) + ABORT_TRACE("property not found"); + + /* + * We could specialize to guard on just JSClass.getProperty, but a mere + * class guard is simpler and slightly faster. + */ + if (OBJ_GET_CLASS(cx, obj)->getProperty != JS_PropertyStub) { + ABORT_TRACE("can't trace through access to undefined property if " + "JSClass.getProperty hook isn't stubbed"); + } + guardClass(obj, obj_ins, OBJ_GET_CLASS(cx, obj), snapshot(MISMATCH_EXIT)); + + /* + * This trace will be valid as long as neither the object nor any object + * on its prototype chain changes shape. + * + * FIXME: This loop can become a single shape guard once bug 497789 has + * been fixed. + */ + VMSideExit* exit = snapshot(BRANCH_EXIT); + do { + LIns* map_ins = map(obj_ins); + LIns* ops_ins; + if (map_is_native(obj->map, map_ins, ops_ins)) { + guardShape(obj_ins, obj, OBJ_SHAPE(obj), "guard(shape)", map_ins, exit); + } else if (!guardDenseArray(obj, obj_ins, exit)) { + ABORT_TRACE("non-native object involved in undefined property access"); + } + } while (guardHasPrototype(obj, obj_ins, &obj, &obj_ins, exit)); + + set(outp, INS_CONST(JSVAL_TO_SPECIAL(JSVAL_VOID)), true); + return JSRS_CONTINUE; + } + + uint32 setflags = (cs.format & (JOF_INCDEC | JOF_FOR)); + JS_ASSERT(!(cs.format & JOF_SET)); + + uint32 slot; + if (PCVAL_IS_SPROP(pcval)) { + JSScopeProperty* sprop = PCVAL_TO_SPROP(pcval); + + if (setflags && !SPROP_HAS_STUB_SETTER(sprop)) + ABORT_TRACE("non-stub setter"); + if (setflags && (sprop->attrs & JSPROP_READONLY)) + ABORT_TRACE("writing to a readonly property"); + if (!SPROP_HAS_STUB_GETTER(sprop)) { + if (slotp) + ABORT_TRACE("can't trace non-stub getter for this opcode"); + if (sprop->attrs & JSPROP_GETTER) + ABORT_TRACE("script getter"); + if (sprop->slot == SPROP_INVALID_SLOT) + return getPropertyWithNativeGetter(obj_ins, sprop, outp); + return getPropertyById(obj_ins, outp); + } + if (!SPROP_HAS_VALID_SLOT(sprop, OBJ_SCOPE(obj2))) + ABORT_TRACE("no valid slot"); + slot = sprop->slot; + } else { + if (!PCVAL_IS_SLOT(pcval)) + ABORT_TRACE("PCE is not a slot"); + slot = PCVAL_TO_SLOT(pcval); + } + + /* We have a slot. */ + if (obj2 != obj) { + if (setflags) + ABORT_TRACE("JOF_INCDEC|JOF_FOR opcode hit prototype chain"); + + /* + * We're getting a proto-property. Walk up the prototype chain emitting + * proto slot loads, updating obj as we go, leaving obj set to obj2 with + * obj_ins the last proto-load. + */ + while (obj != obj2) { + obj_ins = stobj_get_proto(obj_ins); + obj = STOBJ_GET_PROTO(obj); + } + } + + LIns* dslots_ins = NULL; + LIns* v_ins = unbox_jsval(STOBJ_GET_SLOT(obj, slot), + stobj_get_slot(obj_ins, slot, dslots_ins), + snapshot(BRANCH_EXIT)); + + if (slotp) { + *slotp = slot; + *v_insp = v_ins; + } + if (outp) + set(outp, v_ins, true); + return JSRS_CONTINUE; +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::denseArrayElement(jsval& oval, jsval& ival, jsval*& vp, LIns*& v_ins, + LIns*& addr_ins) +{ + JS_ASSERT(JSVAL_IS_OBJECT(oval) && JSVAL_IS_INT(ival)); + + JSObject* obj = JSVAL_TO_OBJECT(oval); + LIns* obj_ins = get(&oval); + jsint idx = JSVAL_TO_INT(ival); + LIns* idx_ins = makeNumberInt32(get(&ival)); + LIns* pidx_ins = lir->ins_u2p(idx_ins); + + VMSideExit* exit = snapshot(BRANCH_EXIT); + + /* check that the index is within bounds */ + LIns* dslots_ins = lir->insLoad(LIR_ldp, obj_ins, offsetof(JSObject, dslots)); + jsuint capacity = js_DenseArrayCapacity(obj); + bool within = (jsuint(idx) < jsuint(obj->fslots[JSSLOT_ARRAY_LENGTH]) && jsuint(idx) < capacity); + if (!within) { + /* If idx < 0, stay on trace (and read value as undefined, since this is a dense array). */ + LIns* br1 = NULL; + if (MAX_DSLOTS_LENGTH > MAX_DSLOTS_LENGTH32 && !idx_ins->isconst()) { + /* Only 64-bit machines support large enough arrays for this. */ + JS_ASSERT(sizeof(jsval) == 8); + br1 = lir->insBranch(LIR_jt, + lir->ins2i(LIR_lt, idx_ins, 0), + NULL); + } + + /* If not idx < length, stay on trace (and read value as undefined). */ + LIns* br2 = lir->insBranch(LIR_jf, + lir->ins2(LIR_pult, + pidx_ins, + stobj_get_fslot(obj_ins, JSSLOT_ARRAY_LENGTH)), + NULL); + + /* If dslots is NULL, stay on trace (and read value as undefined). */ + LIns* br3 = lir->insBranch(LIR_jt, lir->ins_peq0(dslots_ins), NULL); + + /* If not idx < capacity, stay on trace (and read value as undefined). */ + LIns* br4 = lir->insBranch(LIR_jf, + lir->ins2(LIR_pult, + pidx_ins, + lir->insLoad(LIR_ldp, + dslots_ins, + -(int)sizeof(jsval))), + NULL); + lir->insGuard(LIR_x, NULL, createGuardRecord(exit)); + LIns* label = lir->ins0(LIR_label); + if (br1) + br1->setTarget(label); + br2->setTarget(label); + br3->setTarget(label); + br4->setTarget(label); + + CHECK_STATUS(guardPrototypeHasNoIndexedProperties(obj, obj_ins, MISMATCH_EXIT)); + + // Return undefined and indicate that we didn't actually read this (addr_ins). + v_ins = lir->insImm(JSVAL_TO_SPECIAL(JSVAL_VOID)); + addr_ins = NULL; + return JSRS_CONTINUE; + } + + /* Guard against negative index */ + if (MAX_DSLOTS_LENGTH > MAX_DSLOTS_LENGTH32 && !idx_ins->isconst()) { + /* Only 64-bit machines support large enough arrays for this. */ + JS_ASSERT(sizeof(jsval) == 8); + guard(false, + lir->ins2i(LIR_lt, idx_ins, 0), + exit); + } + + /* Guard array length */ + guard(true, + lir->ins2(LIR_pult, pidx_ins, stobj_get_fslot(obj_ins, JSSLOT_ARRAY_LENGTH)), + exit); + + /* dslots must not be NULL */ + guard(false, + lir->ins_peq0(dslots_ins), + exit); + + /* Guard array capacity */ + guard(true, + lir->ins2(LIR_pult, + pidx_ins, + lir->insLoad(LIR_ldp, dslots_ins, 0 - (int)sizeof(jsval))), + exit); + + /* Load the value and guard on its type to unbox it. */ + vp = &obj->dslots[jsuint(idx)]; + addr_ins = lir->ins2(LIR_piadd, dslots_ins, + lir->ins2i(LIR_pilsh, pidx_ins, (sizeof(jsval) == 4) ? 2 : 3)); + v_ins = unbox_jsval(*vp, lir->insLoad(LIR_ldp, addr_ins, 0), exit); + + if (JSVAL_IS_SPECIAL(*vp)) { + /* + * If we read a hole from the array, convert it to undefined and guard + * that there are no indexed properties along the prototype chain. + */ + LIns* br = lir->insBranch(LIR_jf, + lir->ins2i(LIR_eq, v_ins, JSVAL_TO_SPECIAL(JSVAL_HOLE)), + NULL); + CHECK_STATUS(guardPrototypeHasNoIndexedProperties(obj, obj_ins, MISMATCH_EXIT)); + br->setTarget(lir->ins0(LIR_label)); + + /* Don't let the hole value escape. Turn it into an undefined. */ + v_ins = lir->ins2i(LIR_and, v_ins, ~(JSVAL_HOLE_FLAG >> JSVAL_TAGBITS)); + } + return JSRS_CONTINUE; +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::getProp(JSObject* obj, LIns* obj_ins) +{ + const JSCodeSpec& cs = js_CodeSpec[*cx->fp->regs->pc]; + JS_ASSERT(cs.ndefs == 1); + return prop(obj, obj_ins, NULL, NULL, &stackval(-cs.nuses)); +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::getProp(jsval& v) +{ + if (JSVAL_IS_PRIMITIVE(v)) + ABORT_TRACE("primitive lhs"); + + return getProp(JSVAL_TO_OBJECT(v), get(&v)); +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_NAME() +{ + jsval* vp; + LIns* v_ins; + NameResult nr; + CHECK_STATUS(name(vp, v_ins, nr)); + stack(0, v_ins); + return JSRS_CONTINUE; +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_DOUBLE() +{ + jsval v = jsval(atoms[GET_INDEX(cx->fp->regs->pc)]); + stack(0, lir->insImmf(*JSVAL_TO_DOUBLE(v))); + return JSRS_CONTINUE; +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_STRING() +{ + JSAtom* atom = atoms[GET_INDEX(cx->fp->regs->pc)]; + JS_ASSERT(ATOM_IS_STRING(atom)); + stack(0, INS_ATOM(atom)); + return JSRS_CONTINUE; +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_ZERO() +{ + stack(0, lir->insImmf(0)); + return JSRS_CONTINUE; +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_ONE() +{ + stack(0, lir->insImmf(1)); + return JSRS_CONTINUE; +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_NULL() +{ + stack(0, INS_NULL()); + return JSRS_CONTINUE; +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_THIS() +{ + LIns* this_ins; + CHECK_STATUS(getThis(this_ins)); + stack(0, this_ins); + return JSRS_CONTINUE; +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_FALSE() +{ + stack(0, lir->insImm(0)); + return JSRS_CONTINUE; +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_TRUE() +{ + stack(0, lir->insImm(1)); + return JSRS_CONTINUE; +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_OR() +{ + return ifop(); +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_AND() +{ + return ifop(); +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_TABLESWITCH() +{ +#ifdef NANOJIT_IA32 + /* Handle tableswitches specially -- prepare a jump table if needed. */ + return tableswitch(); +#else + return switchop(); +#endif +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_LOOKUPSWITCH() +{ + return switchop(); +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_STRICTEQ() +{ + strictEquality(true, false); + return JSRS_CONTINUE; +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_STRICTNE() +{ + strictEquality(false, false); + return JSRS_CONTINUE; +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_OBJECT() +{ + JSStackFrame* fp = cx->fp; + JSScript* script = fp->script; + unsigned index = atoms - script->atomMap.vector + GET_INDEX(fp->regs->pc); + + JSObject* obj; + obj = script->getObject(index); + stack(0, INS_CONSTOBJ(obj)); + return JSRS_CONTINUE; +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_POP() +{ + return JSRS_CONTINUE; +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_TRAP() +{ + return JSRS_STOP; +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_GETARG() +{ + stack(0, arg(GET_ARGNO(cx->fp->regs->pc))); + return JSRS_CONTINUE; +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_SETARG() +{ + arg(GET_ARGNO(cx->fp->regs->pc), stack(-1)); + return JSRS_CONTINUE; +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_GETLOCAL() +{ + stack(0, var(GET_SLOTNO(cx->fp->regs->pc))); + return JSRS_CONTINUE; +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_SETLOCAL() +{ + var(GET_SLOTNO(cx->fp->regs->pc), stack(-1)); + return JSRS_CONTINUE; +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_UINT16() +{ + stack(0, lir->insImmf(GET_UINT16(cx->fp->regs->pc))); + return JSRS_CONTINUE; +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_NEWINIT() +{ + JSProtoKey key = JSProtoKey(GET_INT8(cx->fp->regs->pc)); + LIns *proto_ins; + CHECK_STATUS(getClassPrototype(key, proto_ins)); + + LIns* args[] = { proto_ins, cx_ins }; + const CallInfo *ci = (key == JSProto_Array) ? &js_NewEmptyArray_ci : &js_Object_tn_ci; + LIns* v_ins = lir->insCall(ci, args); + guard(false, lir->ins_peq0(v_ins), OOM_EXIT); + stack(0, v_ins); + return JSRS_CONTINUE; +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_ENDINIT() +{ +#ifdef DEBUG + jsval& v = stackval(-1); + JS_ASSERT(!JSVAL_IS_PRIMITIVE(v)); +#endif + return JSRS_CONTINUE; +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_INITPROP() +{ + // All the action is in record_SetPropHit. + return JSRS_CONTINUE; +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_INITELEM() +{ + return record_JSOP_SETELEM(); +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_DEFSHARP() +{ + return JSRS_STOP; +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_USESHARP() +{ + return JSRS_STOP; +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_INCARG() +{ + return inc(argval(GET_ARGNO(cx->fp->regs->pc)), 1); +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_INCLOCAL() +{ + return inc(varval(GET_SLOTNO(cx->fp->regs->pc)), 1); +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_DECARG() +{ + return inc(argval(GET_ARGNO(cx->fp->regs->pc)), -1); +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_DECLOCAL() +{ + return inc(varval(GET_SLOTNO(cx->fp->regs->pc)), -1); +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_ARGINC() +{ + return inc(argval(GET_ARGNO(cx->fp->regs->pc)), 1, false); +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_LOCALINC() +{ + return inc(varval(GET_SLOTNO(cx->fp->regs->pc)), 1, false); +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_ARGDEC() +{ + return inc(argval(GET_ARGNO(cx->fp->regs->pc)), -1, false); +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_LOCALDEC() +{ + return inc(varval(GET_SLOTNO(cx->fp->regs->pc)), -1, false); +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_IMACOP() +{ + JS_ASSERT(cx->fp->imacpc); + return JSRS_CONTINUE; +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_ITER() +{ + jsval& v = stackval(-1); + if (JSVAL_IS_PRIMITIVE(v)) + ABORT_TRACE("for-in on a primitive value"); + ABORT_IF_XML(v); + + jsuint flags = cx->fp->regs->pc[1]; + + if (hasIteratorMethod(JSVAL_TO_OBJECT(v))) { + if (flags == JSITER_ENUMERATE) + return call_imacro(iter_imacros.for_in); + if (flags == (JSITER_ENUMERATE | JSITER_FOREACH)) + return call_imacro(iter_imacros.for_each); + } else { + if (flags == JSITER_ENUMERATE) + return call_imacro(iter_imacros.for_in_native); + if (flags == (JSITER_ENUMERATE | JSITER_FOREACH)) + return call_imacro(iter_imacros.for_each_native); + } + ABORT_TRACE("unimplemented JSITER_* flags"); +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_NEXTITER() +{ + jsval& iterobj_val = stackval(-2); + if (JSVAL_IS_PRIMITIVE(iterobj_val)) + ABORT_TRACE("for-in on a primitive value"); + ABORT_IF_XML(iterobj_val); + JSObject* iterobj = JSVAL_TO_OBJECT(iterobj_val); + JSClass* clasp = STOBJ_GET_CLASS(iterobj); + LIns* iterobj_ins = get(&iterobj_val); + guardClass(iterobj, iterobj_ins, clasp, snapshot(BRANCH_EXIT)); + if (clasp == &js_IteratorClass || clasp == &js_GeneratorClass) + return call_imacro(nextiter_imacros.native_iter_next); + return call_imacro(nextiter_imacros.custom_iter_next); +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_ENDITER() +{ + LIns* args[] = { stack(-2), cx_ins }; + LIns* ok_ins = lir->insCall(&js_CloseIterator_ci, args); + guard(false, lir->ins_eq0(ok_ins), MISMATCH_EXIT); + return JSRS_CONTINUE; +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_FORNAME() +{ + jsval* vp; + LIns* x_ins; + NameResult nr; + CHECK_STATUS(name(vp, x_ins, nr)); + if (!nr.tracked) + ABORT_TRACE("forname on non-tracked value not supported"); + set(vp, stack(-1)); + return JSRS_CONTINUE; +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_FORPROP() +{ + return JSRS_STOP; +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_FORELEM() +{ + return record_JSOP_DUP(); +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_FORARG() +{ + return record_JSOP_SETARG(); +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_FORLOCAL() +{ + return record_JSOP_SETLOCAL(); +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_POPN() +{ + return JSRS_CONTINUE; +} + +/* + * Generate LIR to reach |obj2| from |obj| by traversing the scope chain. The + * generated code also ensures that any call objects found have not changed shape. + * + * obj starting object + * obj_ins LIR instruction representing obj + * targetObj end object for traversal + * targetIns [out] LIR instruction representing obj2 + */ +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::traverseScopeChain(JSObject *obj, LIns *obj_ins, JSObject *targetObj, + LIns *&targetIns) +{ + VMSideExit* exit = NULL; + + /* + * Scope chains are often left "incomplete", and reified lazily when + * necessary, since doing so is expensive. When creating null and flat + * closures on trace (the only kinds supported), the global object is + * hardcoded as the parent, since reifying the scope chain on trace + * would be extremely difficult. This is because block objects need frame + * pointers, which do not exist on trace, and thus would require magic + * similar to arguments objects or reification of stack frames. Luckily, + * for null and flat closures, these blocks are unnecessary. + * + * The problem, as exposed by bug 523793, is that this means creating a + * fixed traversal on trace can be inconsistent with the shorter scope + * chain used when executing a trace. To address this, perform an initial + * sweep of the scope chain to make sure that if there is a heavyweight + * function with a call object, and there is also a block object, the + * trace is safely aborted. + * + * If there is no call object, we must have arrived at the global object, + * and can bypass the scope chain traversal completely. + */ + bool foundCallObj = false; + bool foundBlockObj = false; + JSObject* searchObj = obj; + + for (;;) { + if (searchObj != globalObj) { + JSClass* cls = STOBJ_GET_CLASS(searchObj); + if (cls == &js_BlockClass) { + foundBlockObj = true; + } else if (cls == &js_CallClass) { + // If the function that owns this call object is not heavyweight, then + // we can't be sure it will always be there, which means the scope chain + // does not have a definite length, so abort. + if (JSFUN_HEAVYWEIGHT_TEST(js_GetCallObjectFunction(searchObj)->flags)) + foundCallObj = true; + else + ABORT_TRACE("found call object for non-heavyweight function on scope chain"); + } + } + + if (searchObj == targetObj) + break; + + searchObj = STOBJ_GET_PARENT(searchObj); + if (!searchObj) + ABORT_TRACE("cannot traverse this scope chain on trace"); + } + + if (!foundCallObj) { + JS_ASSERT(targetObj == globalObj); + targetIns = INS_CONSTPTR(globalObj); + return JSRS_CONTINUE; + } + + if (foundBlockObj) + ABORT_TRACE("cannot traverse this scope chain on trace"); + + /* There was a call object, or should be a call object now. */ + for (;;) { + if (obj != globalObj) { + if (!js_IsCacheableNonGlobalScope(obj)) + ABORT_TRACE("scope chain lookup crosses non-cacheable object"); + + // We must guard on the shape of all call objects for heavyweight functions + // that we traverse on the scope chain: if the shape changes, a variable with + // the same name may have been inserted in the scope chain. + if (STOBJ_GET_CLASS(obj) == &js_CallClass && + JSFUN_HEAVYWEIGHT_TEST(js_GetCallObjectFunction(obj)->flags)) { + LIns* map_ins = map(obj_ins); + LIns* shape_ins = addName(lir->insLoad(LIR_ld, map_ins, offsetof(JSScope, shape)), + "obj_shape"); + if (!exit) + exit = snapshot(BRANCH_EXIT); + guard(true, + addName(lir->ins2i(LIR_eq, shape_ins, OBJ_SHAPE(obj)), "guard_shape"), + exit); + } + } + + JS_ASSERT(STOBJ_GET_CLASS(obj) != &js_BlockClass); + + if (obj == targetObj) + break; + + obj = STOBJ_GET_PARENT(obj); + obj_ins = stobj_get_parent(obj_ins); + } + + targetIns = obj_ins; + return JSRS_CONTINUE; +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_BINDNAME() +{ + JSStackFrame *fp = cx->fp; + JSObject *obj; + + if (!fp->fun) { + obj = fp->scopeChain; + + // In global code, fp->scopeChain can only contain blocks whose values + // are still on the stack. We never use BINDNAME to refer to these. + while (OBJ_GET_CLASS(cx, obj) == &js_BlockClass) { + // The block's values are still on the stack. + JS_ASSERT(obj->getPrivate() == fp); + obj = OBJ_GET_PARENT(cx, obj); + // Blocks always have parents. + JS_ASSERT(obj); + } + + if (obj != globalObj) + ABORT_TRACE("BINDNAME in global code resolved to non-global object"); + + /* + * The trace is specialized to this global object. Furthermore, we know it + * is the sole 'global' object on the scope chain: we set globalObj to the + * scope chain element with no parent, and we reached it starting from the + * function closure or the current scopeChain, so there is nothing inner to + * it. Therefore this must be the right base object. + */ + stack(0, INS_CONSTOBJ(obj)); + return JSRS_CONTINUE; + } + + // We can't trace BINDNAME in functions that contain direct calls to eval, + // as they might add bindings which previously-traced references would have + // to see. + if (JSFUN_HEAVYWEIGHT_TEST(fp->fun->flags)) + ABORT_TRACE("BINDNAME in heavyweight function."); + + // We don't have the scope chain on trace, so instead we get a start object + // that is on the scope chain and doesn't skip the target object (the one + // that contains the property). + jsval *callee = &cx->fp->argv[-2]; + obj = STOBJ_GET_PARENT(JSVAL_TO_OBJECT(*callee)); + if (obj == globalObj) { + stack(0, INS_CONSTOBJ(obj)); + return JSRS_CONTINUE; + } + LIns *obj_ins = stobj_get_parent(get(callee)); + + // Find the target object. + JSAtom *atom = atoms[GET_INDEX(cx->fp->regs->pc)]; + jsid id = ATOM_TO_JSID(atom); + JSContext *localCx = cx; + JSObject *obj2 = js_FindIdentifierBase(cx, fp->scopeChain, id); + if (!obj2) + ABORT_TRACE_ERROR("js_FindIdentifierBase failed"); + if (!TRACE_RECORDER(localCx)) + return JSRS_STOP; + if (obj2 != globalObj && STOBJ_GET_CLASS(obj2) != &js_CallClass) + ABORT_TRACE("BINDNAME on non-global, non-call object"); + + // Generate LIR to get to the target object from the start object. + LIns *obj2_ins; + CHECK_STATUS(traverseScopeChain(obj, obj_ins, obj2, obj2_ins)); + + // If |obj2| is the global object, we can refer to it directly instead of walking up + // the scope chain. There may still be guards on intervening call objects. + stack(0, obj2 == globalObj ? INS_CONSTOBJ(obj2) : obj2_ins); + return JSRS_CONTINUE; +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_SETNAME() +{ + jsval& l = stackval(-2); + JS_ASSERT(!JSVAL_IS_PRIMITIVE(l)); + + /* + * Trace only cases that are global code, in lightweight functions + * scoped by the global object only, or in call objects. + */ + JSObject* obj = JSVAL_TO_OBJECT(l); + if (OBJ_GET_CLASS(cx, obj) == &js_CallClass) + return JSRS_CONTINUE; + if (obj != cx->fp->scopeChain || obj != globalObj) + ABORT_TRACE("JSOP_SETNAME left operand is not the global object"); + + // The rest of the work is in record_SetPropHit. + return JSRS_CONTINUE; +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_THROW() +{ + return JSRS_STOP; +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_IN() +{ + jsval& rval = stackval(-1); + jsval& lval = stackval(-2); + + if (JSVAL_IS_PRIMITIVE(rval)) + ABORT_TRACE("JSOP_IN on non-object right operand"); + JSObject* obj = JSVAL_TO_OBJECT(rval); + LIns* obj_ins = get(&rval); + + jsid id; + LIns* x; + if (JSVAL_IS_INT(lval)) { + id = INT_JSVAL_TO_JSID(lval); + LIns* args[] = { makeNumberInt32(get(&lval)), obj_ins, cx_ins }; + x = lir->insCall(&js_HasNamedPropertyInt32_ci, args); + } else if (JSVAL_IS_STRING(lval)) { + if (!js_ValueToStringId(cx, lval, &id)) + ABORT_TRACE_ERROR("left operand of JSOP_IN didn't convert to a string-id"); + LIns* args[] = { get(&lval), obj_ins, cx_ins }; + x = lir->insCall(&js_HasNamedProperty_ci, args); + } else { + ABORT_TRACE("string or integer expected"); + } + + guard(false, lir->ins2i(LIR_eq, x, JSVAL_TO_SPECIAL(JSVAL_VOID)), OOM_EXIT); + x = lir->ins2i(LIR_eq, x, 1); + + JSTraceMonitor &localtm = *traceMonitor; + JSContext *localcx = cx; + + JSObject* obj2; + JSProperty* prop; + bool ok = obj->lookupProperty(cx, id, &obj2, &prop); + + /* lookupProperty can reenter the interpreter and kill |this|. */ + if (!localtm.recorder) { + if (prop) + obj2->dropProperty(localcx, prop); + return JSRS_STOP; + } + + if (!ok) + ABORT_TRACE_ERROR("obj->lookupProperty failed in JSOP_IN"); + bool cond = prop != NULL; + if (prop) + obj2->dropProperty(cx, prop); + + /* + * The interpreter fuses comparisons and the following branch, so we have + * to do that here as well. + */ + fuseIf(cx->fp->regs->pc + 1, cond, x); + + /* + * We update the stack after the guard. This is safe since the guard bails + * out at the comparison and the interpreter will therefore re-execute the + * comparison. This way the value of the condition doesn't have to be + * calculated and saved on the stack in most cases. + */ + set(&lval, x); + return JSRS_CONTINUE; +} + +static JSBool FASTCALL +HasInstance(JSContext* cx, JSObject* ctor, jsval val) +{ + JSBool result = JS_FALSE; + if (!ctor->map->ops->hasInstance(cx, ctor, val, &result)) + js_SetBuiltinError(cx); + return result; +} +JS_DEFINE_CALLINFO_3(static, BOOL_FAIL, HasInstance, CONTEXT, OBJECT, JSVAL, 0, 0) + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_INSTANCEOF() +{ + // If the rhs isn't an object, we are headed for a TypeError. + jsval& ctor = stackval(-1); + if (JSVAL_IS_PRIMITIVE(ctor)) + ABORT_TRACE("non-object on rhs of instanceof"); + + jsval& val = stackval(-2); + LIns* val_ins = box_jsval(val, get(&val)); + + enterDeepBailCall(); + LIns* args[] = {val_ins, get(&ctor), cx_ins}; + stack(-2, lir->insCall(&HasInstance_ci, args)); + LIns* status_ins = lir->insLoad(LIR_ld, + lirbuf->state, + (int) offsetof(InterpState, builtinStatus)); + pendingGuardCondition = lir->ins_eq0(status_ins); + leaveDeepBailCall(); + + return JSRS_CONTINUE; +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_DEBUGGER() +{ + return JSRS_STOP; +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_GOSUB() +{ + return JSRS_STOP; +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_RETSUB() +{ + return JSRS_STOP; +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_EXCEPTION() +{ + return JSRS_STOP; +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_LINENO() +{ + return JSRS_CONTINUE; +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_CONDSWITCH() +{ + return JSRS_CONTINUE; +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_CASE() +{ + strictEquality(true, true); + return JSRS_CONTINUE; +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_DEFAULT() +{ + return JSRS_CONTINUE; +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_EVAL() +{ + return JSRS_STOP; +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_ENUMELEM() +{ + return JSRS_STOP; +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_GETTER() +{ + return JSRS_STOP; +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_SETTER() +{ + return JSRS_STOP; +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_DEFFUN() +{ + return JSRS_STOP; +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_DEFFUN_FC() +{ + return JSRS_STOP; +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_DEFCONST() +{ + return JSRS_STOP; +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_DEFVAR() +{ + return JSRS_STOP; +} + +jsatomid +TraceRecorder::getFullIndex(ptrdiff_t pcoff) +{ + jsatomid index = GET_INDEX(cx->fp->regs->pc + pcoff); + index += atoms - cx->fp->script->atomMap.vector; + return index; +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_LAMBDA() +{ + JSFunction* fun; + fun = cx->fp->script->getFunction(getFullIndex()); + + if (FUN_NULL_CLOSURE(fun) && OBJ_GET_PARENT(cx, FUN_OBJECT(fun)) == globalObj) { + LIns *proto_ins; + CHECK_STATUS(getClassPrototype(JSProto_Function, proto_ins)); + + LIns* args[] = { INS_CONSTOBJ(globalObj), proto_ins, INS_CONSTFUN(fun), cx_ins }; + LIns* x = lir->insCall(&js_NewNullClosure_ci, args); + stack(0, x); + return JSRS_CONTINUE; + } + return JSRS_STOP; +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_LAMBDA_FC() +{ + JSFunction* fun; + fun = cx->fp->script->getFunction(getFullIndex()); + + LIns* scopeChain_ins = get(&cx->fp->argv[-2]); + JS_ASSERT(scopeChain_ins); + + LIns* args[] = { + scopeChain_ins, + INS_CONSTFUN(fun), + cx_ins + }; + LIns* call_ins = lir->insCall(&js_AllocFlatClosure_ci, args); + guard(false, + addName(lir->ins2(LIR_peq, call_ins, INS_NULL()), + "guard(js_AllocFlatClosure)"), + OOM_EXIT); + + if (fun->u.i.nupvars) { + JSUpvarArray *uva = fun->u.i.script->upvars(); + for (uint32 i = 0, n = uva->length; i < n; i++) { + jsval v; + LIns* upvar_ins = upvar(fun->u.i.script, uva, i, v); + if (!upvar_ins) + return JSRS_STOP; + LIns* dslots_ins = NULL; + stobj_set_dslot(call_ins, i, dslots_ins, box_jsval(v, upvar_ins)); + } + } + + stack(0, call_ins); + return JSRS_CONTINUE; +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_CALLEE() +{ + stack(0, get(&cx->fp->argv[-2])); + return JSRS_CONTINUE; +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_SETLOCALPOP() +{ + var(GET_SLOTNO(cx->fp->regs->pc), stack(-1)); + return JSRS_CONTINUE; +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_IFPRIMTOP() +{ + // Traces are type-specialized, including null vs. object, so we need do + // nothing here. The upstream unbox_jsval called after valueOf or toString + // from an imacro (e.g.) will fork the trace for us, allowing us to just + // follow along mindlessly :-). + return JSRS_CONTINUE; +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_SETCALL() +{ + return JSRS_STOP; +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_TRY() +{ + return JSRS_CONTINUE; +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_FINALLY() +{ + return JSRS_CONTINUE; +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_NOP() +{ + return JSRS_CONTINUE; +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_ARGSUB() +{ + JSStackFrame* fp = cx->fp; + if (!(fp->fun->flags & JSFUN_HEAVYWEIGHT)) { + uintN slot = GET_ARGNO(fp->regs->pc); + if (slot >= fp->argc) + ABORT_TRACE("can't trace out-of-range arguments"); + stack(0, get(&cx->fp->argv[slot])); + return JSRS_CONTINUE; + } + ABORT_TRACE("can't trace JSOP_ARGSUB hard case"); +} + +JS_REQUIRES_STACK LIns* +TraceRecorder::guardArgsLengthNotAssigned(LIns* argsobj_ins) +{ + // The following implements js_IsOverriddenArgsLength on trace. + // The '2' bit is set if length was overridden. + LIns *len_ins = stobj_get_fslot(argsobj_ins, JSSLOT_ARGS_LENGTH); + LIns *ovr_ins = lir->ins2(LIR_piand, len_ins, INS_CONSTWORD(2)); + guard(true, lir->ins_peq0(ovr_ins), snapshot(BRANCH_EXIT)); + return len_ins; +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_ARGCNT() +{ + if (cx->fp->fun->flags & JSFUN_HEAVYWEIGHT) + ABORT_TRACE("can't trace heavyweight JSOP_ARGCNT"); + + // argc is fixed on trace, so ideally we would simply generate LIR for + // constant argc. But the user can mutate arguments.length in the + // interpreter, so we have to check for that in the trace entry frame. + // We also have to check that arguments.length has not been mutated + // at record time, because if so we will generate incorrect constant + // LIR, which will assert in alu(). + if (cx->fp->argsobj && js_IsOverriddenArgsLength(JSVAL_TO_OBJECT(cx->fp->argsobj))) + ABORT_TRACE("can't trace JSOP_ARGCNT if arguments.length has been modified"); + LIns *a_ins = get(&cx->fp->argsobj); + if (callDepth == 0) { + LIns *br = lir->insBranch(LIR_jt, lir->ins_peq0(a_ins), NULL); + guardArgsLengthNotAssigned(a_ins); + LIns *label = lir->ins0(LIR_label); + br->setTarget(label); + } + stack(0, lir->insImmf(cx->fp->argc)); + return JSRS_CONTINUE; +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_DefLocalFunSetSlot(uint32 slot, JSObject* obj) +{ + JSFunction* fun = GET_FUNCTION_PRIVATE(cx, obj); + + if (FUN_NULL_CLOSURE(fun) && OBJ_GET_PARENT(cx, FUN_OBJECT(fun)) == globalObj) { + LIns *proto_ins; + CHECK_STATUS(getClassPrototype(JSProto_Function, proto_ins)); + + LIns* args[] = { INS_CONSTOBJ(globalObj), proto_ins, INS_CONSTFUN(fun), cx_ins }; + LIns* x = lir->insCall(&js_NewNullClosure_ci, args); + var(slot, x); + return JSRS_CONTINUE; + } + + return JSRS_STOP; +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_DEFLOCALFUN() +{ + return JSRS_CONTINUE; +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_DEFLOCALFUN_FC() +{ + return JSRS_CONTINUE; +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_GOTOX() +{ + return record_JSOP_GOTO(); +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_IFEQX() +{ + return record_JSOP_IFEQ(); +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_IFNEX() +{ + return record_JSOP_IFNE(); +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_ORX() +{ + return record_JSOP_OR(); +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_ANDX() +{ + return record_JSOP_AND(); +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_GOSUBX() +{ + return record_JSOP_GOSUB(); +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_CASEX() +{ + strictEquality(true, true); + return JSRS_CONTINUE; +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_DEFAULTX() +{ + return JSRS_CONTINUE; +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_TABLESWITCHX() +{ + return record_JSOP_TABLESWITCH(); +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_LOOKUPSWITCHX() +{ + return switchop(); +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_BACKPATCH() +{ + return JSRS_CONTINUE; +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_BACKPATCH_POP() +{ + return JSRS_CONTINUE; +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_THROWING() +{ + return JSRS_STOP; +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_SETRVAL() +{ + // If we implement this, we need to update JSOP_STOP. + return JSRS_STOP; +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_RETRVAL() +{ + return JSRS_STOP; +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_GETGVAR() +{ + jsval slotval = cx->fp->slots[GET_SLOTNO(cx->fp->regs->pc)]; + if (JSVAL_IS_NULL(slotval)) + return JSRS_CONTINUE; // We will see JSOP_NAME from the interpreter's jump, so no-op here. + + uint32 slot = JSVAL_TO_INT(slotval); + + if (!lazilyImportGlobalSlot(slot)) + ABORT_TRACE("lazy import of global slot failed"); + + stack(0, get(&STOBJ_GET_SLOT(globalObj, slot))); + return JSRS_CONTINUE; +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_SETGVAR() +{ + jsval slotval = cx->fp->slots[GET_SLOTNO(cx->fp->regs->pc)]; + if (JSVAL_IS_NULL(slotval)) + return JSRS_CONTINUE; // We will see JSOP_NAME from the interpreter's jump, so no-op here. + + uint32 slot = JSVAL_TO_INT(slotval); + + if (!lazilyImportGlobalSlot(slot)) + ABORT_TRACE("lazy import of global slot failed"); + + set(&STOBJ_GET_SLOT(globalObj, slot), stack(-1)); + return JSRS_CONTINUE; +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_INCGVAR() +{ + jsval slotval = cx->fp->slots[GET_SLOTNO(cx->fp->regs->pc)]; + if (JSVAL_IS_NULL(slotval)) + // We will see JSOP_INCNAME from the interpreter's jump, so no-op here. + return JSRS_CONTINUE; + + uint32 slot = JSVAL_TO_INT(slotval); + + if (!lazilyImportGlobalSlot(slot)) + ABORT_TRACE("lazy import of global slot failed"); + + return inc(STOBJ_GET_SLOT(globalObj, slot), 1); +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_DECGVAR() +{ + jsval slotval = cx->fp->slots[GET_SLOTNO(cx->fp->regs->pc)]; + if (JSVAL_IS_NULL(slotval)) + // We will see JSOP_INCNAME from the interpreter's jump, so no-op here. + return JSRS_CONTINUE; + + uint32 slot = JSVAL_TO_INT(slotval); + + if (!lazilyImportGlobalSlot(slot)) + ABORT_TRACE("lazy import of global slot failed"); + + return inc(STOBJ_GET_SLOT(globalObj, slot), -1); +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_GVARINC() +{ + jsval slotval = cx->fp->slots[GET_SLOTNO(cx->fp->regs->pc)]; + if (JSVAL_IS_NULL(slotval)) + // We will see JSOP_INCNAME from the interpreter's jump, so no-op here. + return JSRS_CONTINUE; + + uint32 slot = JSVAL_TO_INT(slotval); + + if (!lazilyImportGlobalSlot(slot)) + ABORT_TRACE("lazy import of global slot failed"); + + return inc(STOBJ_GET_SLOT(globalObj, slot), 1, false); +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_GVARDEC() +{ + jsval slotval = cx->fp->slots[GET_SLOTNO(cx->fp->regs->pc)]; + if (JSVAL_IS_NULL(slotval)) + // We will see JSOP_INCNAME from the interpreter's jump, so no-op here. + return JSRS_CONTINUE; + + uint32 slot = JSVAL_TO_INT(slotval); + + if (!lazilyImportGlobalSlot(slot)) + ABORT_TRACE("lazy import of global slot failed"); + + return inc(STOBJ_GET_SLOT(globalObj, slot), -1, false); +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_REGEXP() +{ + return JSRS_STOP; +} + +// begin JS_HAS_XML_SUPPORT + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_DEFXMLNS() +{ + return JSRS_STOP; +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_ANYNAME() +{ + return JSRS_STOP; +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_QNAMEPART() +{ + return record_JSOP_STRING(); +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_QNAMECONST() +{ + return JSRS_STOP; +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_QNAME() +{ + return JSRS_STOP; +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_TOATTRNAME() +{ + return JSRS_STOP; +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_TOATTRVAL() +{ + return JSRS_STOP; +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_ADDATTRNAME() +{ + return JSRS_STOP; +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_ADDATTRVAL() +{ + return JSRS_STOP; +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_BINDXMLNAME() +{ + return JSRS_STOP; +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_SETXMLNAME() +{ + return JSRS_STOP; +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_XMLNAME() +{ + return JSRS_STOP; +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_DESCENDANTS() +{ + return JSRS_STOP; +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_FILTER() +{ + return JSRS_STOP; +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_ENDFILTER() +{ + return JSRS_STOP; +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_TOXML() +{ + return JSRS_STOP; +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_TOXMLLIST() +{ + return JSRS_STOP; +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_XMLTAGEXPR() +{ + return JSRS_STOP; +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_XMLELTEXPR() +{ + return JSRS_STOP; +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_XMLOBJECT() +{ + return JSRS_STOP; +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_XMLCDATA() +{ + return JSRS_STOP; +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_XMLCOMMENT() +{ + return JSRS_STOP; +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_XMLPI() +{ + return JSRS_STOP; +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_GETFUNNS() +{ + return JSRS_STOP; +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_STARTXML() +{ + return JSRS_STOP; +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_STARTXMLEXPR() +{ + return JSRS_STOP; +} + +// end JS_HAS_XML_SUPPORT + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_CALLPROP() +{ + jsval& l = stackval(-1); + JSObject* obj; + LIns* obj_ins; + LIns* this_ins; + if (!JSVAL_IS_PRIMITIVE(l)) { + obj = JSVAL_TO_OBJECT(l); + obj_ins = get(&l); + this_ins = obj_ins; // |this| for subsequent call + } else { + jsint i; + debug_only_stmt(const char* protoname = NULL;) + if (JSVAL_IS_STRING(l)) { + i = JSProto_String; + debug_only_stmt(protoname = "String.prototype";) + } else if (JSVAL_IS_NUMBER(l)) { + i = JSProto_Number; + debug_only_stmt(protoname = "Number.prototype";) + } else if (JSVAL_IS_SPECIAL(l)) { + if (l == JSVAL_VOID) + ABORT_TRACE("callprop on void"); + guard(false, lir->ins2i(LIR_eq, get(&l), JSVAL_TO_SPECIAL(JSVAL_VOID)), MISMATCH_EXIT); + i = JSProto_Boolean; + debug_only_stmt(protoname = "Boolean.prototype";) + } else { + JS_ASSERT(JSVAL_IS_NULL(l) || JSVAL_IS_VOID(l)); + ABORT_TRACE("callprop on null or void"); + } + + if (!js_GetClassPrototype(cx, NULL, INT_TO_JSID(i), &obj)) + ABORT_TRACE_ERROR("GetClassPrototype failed!"); + + obj_ins = INS_CONSTOBJ(obj); + debug_only_stmt(obj_ins = addName(obj_ins, protoname);) + this_ins = get(&l); // use primitive as |this| + } + + JSObject* obj2; + jsuword pcval; + CHECK_STATUS(test_property_cache(obj, obj_ins, obj2, pcval)); + + if (PCVAL_IS_NULL(pcval) || !PCVAL_IS_OBJECT(pcval)) + ABORT_TRACE("callee is not an object"); + JS_ASSERT(HAS_FUNCTION_CLASS(PCVAL_TO_OBJECT(pcval))); + + if (JSVAL_IS_PRIMITIVE(l)) { + JSFunction* fun = GET_FUNCTION_PRIVATE(cx, PCVAL_TO_OBJECT(pcval)); + if (!PRIMITIVE_THIS_TEST(fun, l)) + ABORT_TRACE("callee does not accept primitive |this|"); + } + + stack(0, this_ins); + stack(-1, INS_CONSTOBJ(PCVAL_TO_OBJECT(pcval))); + return JSRS_CONTINUE; +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_DELDESC() +{ + return JSRS_STOP; +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_UINT24() +{ + stack(0, lir->insImmf(GET_UINT24(cx->fp->regs->pc))); + return JSRS_CONTINUE; +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_INDEXBASE() +{ + atoms += GET_INDEXBASE(cx->fp->regs->pc); + return JSRS_CONTINUE; +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_RESETBASE() +{ + atoms = cx->fp->script->atomMap.vector; + return JSRS_CONTINUE; +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_RESETBASE0() +{ + atoms = cx->fp->script->atomMap.vector; + return JSRS_CONTINUE; +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_CALLELEM() +{ + return record_JSOP_GETELEM(); +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_STOP() +{ + JSStackFrame *fp = cx->fp; + + if (fp->imacpc) { + /* + * End of imacro, so return true to the interpreter immediately. The + * interpreter's JSOP_STOP case will return from the imacro, back to + * the pc after the calling op, still in the same JSStackFrame. + */ + atoms = fp->script->atomMap.vector; + return JSRS_CONTINUE; + } + + putArguments(); + + /* + * We know falling off the end of a constructor returns the new object that + * was passed in via fp->argv[-1], while falling off the end of a function + * returns undefined. + * + * NB: we do not support script rval (eval, API users who want the result + * of the last expression-statement, debugger API calls). + */ + if (fp->flags & JSFRAME_CONSTRUCTING) { + JS_ASSERT(OBJECT_TO_JSVAL(fp->thisp) == fp->argv[-1]); + rval_ins = get(&fp->argv[-1]); + } else { + rval_ins = INS_CONST(JSVAL_TO_SPECIAL(JSVAL_VOID)); + } + clearFrameSlotsFromCache(); + return JSRS_CONTINUE; +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_GETXPROP() +{ + jsval& l = stackval(-1); + if (JSVAL_IS_PRIMITIVE(l)) + ABORT_TRACE("primitive-this for GETXPROP?"); + + jsval* vp; + LIns* v_ins; + NameResult nr; + CHECK_STATUS(name(vp, v_ins, nr)); + stack(-1, v_ins); + return JSRS_CONTINUE; +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_CALLXMLNAME() +{ + return JSRS_STOP; +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_TYPEOFEXPR() +{ + return record_JSOP_TYPEOF(); +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_ENTERBLOCK() +{ + JSObject* obj; + obj = cx->fp->script->getObject(getFullIndex(0)); + + LIns* void_ins = INS_CONST(JSVAL_TO_SPECIAL(JSVAL_VOID)); + for (int i = 0, n = OBJ_BLOCK_COUNT(cx, obj); i < n; i++) + stack(i, void_ins); + return JSRS_CONTINUE; +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_LEAVEBLOCK() +{ + /* We mustn't exit the lexical block we began recording in. */ + if (cx->fp->blockChain != lexicalBlock) + return JSRS_CONTINUE; + else + return JSRS_STOP; +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_GENERATOR() +{ + return JSRS_STOP; +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_YIELD() +{ + return JSRS_STOP; +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_ARRAYPUSH() +{ + uint32_t slot = GET_UINT16(cx->fp->regs->pc); + JS_ASSERT(cx->fp->script->nfixed <= slot); + JS_ASSERT(cx->fp->slots + slot < cx->fp->regs->sp - 1); + jsval &arrayval = cx->fp->slots[slot]; + JS_ASSERT(JSVAL_IS_OBJECT(arrayval)); + JS_ASSERT(OBJ_IS_DENSE_ARRAY(cx, JSVAL_TO_OBJECT(arrayval))); + LIns *array_ins = get(&arrayval); + jsval &elt = stackval(-1); + LIns *elt_ins = box_jsval(elt, get(&elt)); + + LIns *args[] = { elt_ins, array_ins, cx_ins }; + LIns *ok_ins = lir->insCall(&js_ArrayCompPush_ci, args); + guard(false, lir->ins_eq0(ok_ins), OOM_EXIT); + return JSRS_CONTINUE; +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_ENUMCONSTELEM() +{ + return JSRS_STOP; +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_LEAVEBLOCKEXPR() +{ + LIns* v_ins = stack(-1); + int n = -1 - GET_UINT16(cx->fp->regs->pc); + stack(n, v_ins); + return JSRS_CONTINUE; +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_GETTHISPROP() +{ + LIns* this_ins; + + CHECK_STATUS(getThis(this_ins)); + + /* + * It's safe to just use cx->fp->thisp here because getThis() returns + * JSRS_STOP if thisp is not available. + */ + CHECK_STATUS(getProp(cx->fp->thisp, this_ins)); + return JSRS_CONTINUE; +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_GETARGPROP() +{ + return getProp(argval(GET_ARGNO(cx->fp->regs->pc))); +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_GETLOCALPROP() +{ + return getProp(varval(GET_SLOTNO(cx->fp->regs->pc))); +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_INDEXBASE1() +{ + atoms += 1 << 16; + return JSRS_CONTINUE; +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_INDEXBASE2() +{ + atoms += 2 << 16; + return JSRS_CONTINUE; +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_INDEXBASE3() +{ + atoms += 3 << 16; + return JSRS_CONTINUE; +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_CALLGVAR() +{ + jsval slotval = cx->fp->slots[GET_SLOTNO(cx->fp->regs->pc)]; + if (JSVAL_IS_NULL(slotval)) + // We will see JSOP_CALLNAME from the interpreter's jump, so no-op here. + return JSRS_CONTINUE; + + uint32 slot = JSVAL_TO_INT(slotval); + + if (!lazilyImportGlobalSlot(slot)) + ABORT_TRACE("lazy import of global slot failed"); + + jsval& v = STOBJ_GET_SLOT(globalObj, slot); + stack(0, get(&v)); + stack(1, INS_NULL()); + return JSRS_CONTINUE; +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_CALLLOCAL() +{ + uintN slot = GET_SLOTNO(cx->fp->regs->pc); + stack(0, var(slot)); + stack(1, INS_NULL()); + return JSRS_CONTINUE; +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_CALLARG() +{ + uintN slot = GET_ARGNO(cx->fp->regs->pc); + stack(0, arg(slot)); + stack(1, INS_NULL()); + return JSRS_CONTINUE; +} + +/* Functions for use with JSOP_CALLBUILTIN. */ + +static JSBool +ObjectToIterator(JSContext *cx, uintN argc, jsval *vp) +{ + jsval *argv = JS_ARGV(cx, vp); + JS_ASSERT(JSVAL_IS_INT(argv[0])); + JS_SET_RVAL(cx, vp, JS_THIS(cx, vp)); + return js_ValueToIterator(cx, JSVAL_TO_INT(argv[0]), &JS_RVAL(cx, vp)); +} + +static JSObject* FASTCALL +ObjectToIterator_tn(JSContext* cx, jsbytecode* pc, JSObject *obj, int32 flags) +{ + jsval v = OBJECT_TO_JSVAL(obj); + JSBool ok = js_ValueToIterator(cx, flags, &v); + + if (!ok) { + js_SetBuiltinError(cx); + return NULL; + } + return JSVAL_TO_OBJECT(v); +} + +static JSBool +CallIteratorNext(JSContext *cx, uintN argc, jsval *vp) +{ + return js_CallIteratorNext(cx, JS_THIS_OBJECT(cx, vp), &JS_RVAL(cx, vp)); +} + +static jsval FASTCALL +CallIteratorNext_tn(JSContext* cx, jsbytecode* pc, JSObject* iterobj) +{ + JSAutoTempValueRooter tvr(cx); + JSBool ok = js_CallIteratorNext(cx, iterobj, tvr.addr()); + + if (!ok) { + js_SetBuiltinError(cx); + return JSVAL_ERROR_COOKIE; + } + return tvr.value(); +} + +JS_DEFINE_TRCINFO_1(ObjectToIterator, + (4, (static, OBJECT_FAIL, ObjectToIterator_tn, CONTEXT, PC, THIS, INT32, 0, 0))) +JS_DEFINE_TRCINFO_1(CallIteratorNext, + (3, (static, JSVAL_FAIL, CallIteratorNext_tn, CONTEXT, PC, THIS, 0, 0))) + +static const struct BuiltinFunctionInfo { + JSNativeTraceInfo *ti; + int nargs; +} builtinFunctionInfo[JSBUILTIN_LIMIT] = { + {&ObjectToIterator_trcinfo, 1}, + {&CallIteratorNext_trcinfo, 0}, +}; + +JSObject * +js_GetBuiltinFunction(JSContext *cx, uintN index) +{ + JSRuntime *rt = cx->runtime; + JSObject *funobj = rt->builtinFunctions[index]; + + if (!funobj) { + /* Use NULL parent and atom. Builtin functions never escape to scripts. */ + JS_ASSERT(index < JS_ARRAY_LENGTH(builtinFunctionInfo)); + const BuiltinFunctionInfo *bfi = &builtinFunctionInfo[index]; + JSFunction *fun = js_NewFunction(cx, + NULL, + JS_DATA_TO_FUNC_PTR(JSNative, bfi->ti), + bfi->nargs, + JSFUN_FAST_NATIVE | JSFUN_TRCINFO, + NULL, + NULL); + if (fun) { + funobj = FUN_OBJECT(fun); + STOBJ_CLEAR_PROTO(funobj); + STOBJ_CLEAR_PARENT(funobj); + + JS_LOCK_GC(rt); + if (!rt->builtinFunctions[index]) /* retest now that the lock is held */ + rt->builtinFunctions[index] = funobj; + else + funobj = rt->builtinFunctions[index]; + JS_UNLOCK_GC(rt); + } + } + return funobj; +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_CALLBUILTIN() +{ + JSObject *obj = js_GetBuiltinFunction(cx, GET_INDEX(cx->fp->regs->pc)); + if (!obj) + ABORT_TRACE_ERROR("error in js_GetBuiltinFunction"); + + stack(0, get(&stackval(-1))); + stack(-1, INS_CONSTOBJ(obj)); + return JSRS_CONTINUE; +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_INT8() +{ + stack(0, lir->insImmf(GET_INT8(cx->fp->regs->pc))); + return JSRS_CONTINUE; +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_INT32() +{ + stack(0, lir->insImmf(GET_INT32(cx->fp->regs->pc))); + return JSRS_CONTINUE; +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_LENGTH() +{ + jsval& l = stackval(-1); + if (JSVAL_IS_PRIMITIVE(l)) { + if (!JSVAL_IS_STRING(l)) + ABORT_TRACE("non-string primitive JSOP_LENGTH unsupported"); + set(&l, lir->ins1(LIR_i2f, getStringLength(get(&l)))); + return JSRS_CONTINUE; + } + + JSObject* obj = JSVAL_TO_OBJECT(l); + LIns* obj_ins = get(&l); + + if (STOBJ_GET_CLASS(obj) == &js_ArgumentsClass) { + unsigned depth; + JSStackFrame *afp = guardArguments(obj, obj_ins, &depth); + if (!afp) + ABORT_TRACE("can't reach arguments object's frame"); + + // We must both check at record time and guard at run time that + // arguments.length has not been reassigned, redefined or deleted. + if (js_IsOverriddenArgsLength(obj)) + ABORT_TRACE("can't trace JSOP_ARGCNT if arguments.length has been modified"); + LIns* slot_ins = guardArgsLengthNotAssigned(obj_ins); + + // slot_ins is the value from the slot; right-shift by 2 bits to get + // the length (see GetArgsLength in jsfun.cpp). + LIns* v_ins = lir->ins1(LIR_i2f, lir->ins2i(LIR_rsh, slot_ins, 2)); + set(&l, v_ins); + return JSRS_CONTINUE; + } + + LIns* v_ins; + if (OBJ_IS_ARRAY(cx, obj)) { + if (OBJ_IS_DENSE_ARRAY(cx, obj)) { + if (!guardDenseArray(obj, obj_ins, BRANCH_EXIT)) { + JS_NOT_REACHED("OBJ_IS_DENSE_ARRAY but not?!?"); + return JSRS_STOP; + } + } else { + if (!guardClass(obj, obj_ins, &js_SlowArrayClass, snapshot(BRANCH_EXIT))) + ABORT_TRACE("can't trace length property access on non-array"); + } + v_ins = lir->ins1(LIR_i2f, p2i(stobj_get_fslot(obj_ins, JSSLOT_ARRAY_LENGTH))); + } else { + if (!OBJ_IS_NATIVE(obj)) + ABORT_TRACE("can't trace length property access on non-array, non-native object"); + return getProp(obj, obj_ins); + } + set(&l, v_ins); + return JSRS_CONTINUE; +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_NEWARRAY() +{ + LIns *proto_ins; + CHECK_STATUS(getClassPrototype(JSProto_Array, proto_ins)); + + uint32 len = GET_UINT16(cx->fp->regs->pc); + cx->fp->assertValidStackDepth(len); + + LIns* args[] = { lir->insImm(len), proto_ins, cx_ins }; + LIns* v_ins = lir->insCall(&js_NewUninitializedArray_ci, args); + guard(false, lir->ins_peq0(v_ins), OOM_EXIT); + + LIns* dslots_ins = NULL; + uint32 count = 0; + for (uint32 i = 0; i < len; i++) { + jsval& v = stackval(int(i) - int(len)); + if (v != JSVAL_HOLE) + count++; + LIns* elt_ins = box_jsval(v, get(&v)); + stobj_set_dslot(v_ins, i, dslots_ins, elt_ins); + } + + if (count > 0) + stobj_set_fslot(v_ins, JSSLOT_ARRAY_COUNT, INS_CONST(count)); + + stack(-int(len), v_ins); + return JSRS_CONTINUE; +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_HOLE() +{ + stack(0, INS_CONST(JSVAL_TO_SPECIAL(JSVAL_HOLE))); + return JSRS_CONTINUE; +} + +JSRecordingStatus +TraceRecorder::record_JSOP_TRACE() +{ + return JSRS_CONTINUE; +} + +static const uint32 sMaxConcatNSize = 32; + +/* + * Copy the result of defvalue.string back into concatn's arguments, clean the + * stack, and return a pointer to the argument that was just overwritten. + */ +JS_REQUIRES_STACK jsval * +js_ConcatPostImacroStackCleanup(uint32 argc, JSFrameRegs ®s, + TraceRecorder *recorder) +{ + JS_ASSERT(*regs.pc == JSOP_IMACOP); + + /* Pop the argument offset and imacro return value. */ + jsint offset = JSVAL_TO_INT(*--regs.sp); + jsval *imacroResult = --regs.sp; + + /* Replace non-primitive argument with new primitive argument. */ + jsval *vp = regs.sp - offset; + JS_ASSERT(regs.sp - argc <= vp && vp < regs.sp); + if (recorder) + recorder->set(vp, recorder->get(imacroResult)); + *vp = *imacroResult; + + return vp; +} + +/* + * Initially, concatn takes N arguments on the stack, where N is immediate + * operand. To convert these arguments to primitives, we must repeatedly call + * the defvalue.string imacro. To achieve this iteration, defvalue.string ends + * with imacop. Hence, this function is called multiple times, each time with + * one less non-primitive. To keep track of where we are in the loop, we must + * push an additional index value on the stack. Hence, on all subsequent + * entries, the stack is organized as follows (bottom to top): + * + * prim[1] + * ... + * prim[i-1] + * nonprim[i] argument to imacro + * arg[i+1] + * ... + * arg[N] + * primarg[i] nonprim[i] converted to primitive + * i + * + * Hence, the stack setup on entry to this function (and JSOP_CONCATN in the + * interpreter, on trace abort) is dependent on whether an imacro is in + * progress. When all of concatn's arguments are primitive, it emits a builtin + * call and allows the actual JSOP_CONCATN to be executed by the interpreter. + */ +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_CONCATN() +{ + JSStackFrame *fp = cx->fp; + JSFrameRegs ®s = *fp->regs; + + /* + * If we are in an imacro, we must have just finished a call to + * defvalue.string. Continue where we left off last time. + */ + uint32 argc; + jsval *loopStart; + if (fp->imacpc) { + JS_ASSERT(*fp->imacpc == JSOP_CONCATN); + argc = GET_ARGC(fp->imacpc); + loopStart = js_ConcatPostImacroStackCleanup(argc, regs, this) + 1; + } else { + argc = GET_ARGC(regs.pc); + JS_ASSERT(argc > 0); + loopStart = regs.sp - argc; + + /* Prevent code/alloca explosion. */ + if (argc > sMaxConcatNSize) + return JSRS_STOP; + } + + /* Convert non-primitives to primitives using defvalue.string. */ + for (jsval *vp = loopStart; vp != regs.sp; ++vp) { + if (!JSVAL_IS_PRIMITIVE(*vp)) { + /* + * In addition to the jsval we want the imacro to convert to + * primitive, pass through the offset of the argument on the stack. + */ + jsint offset = regs.sp - vp; + + /* Push the non-primitive to convert. */ + set(regs.sp, get(vp), true); + *regs.sp++ = *vp; + + /* Push the argument index. */ + set(regs.sp, lir->insImm(offset), true); + *regs.sp++ = INT_TO_JSVAL(offset); + + /* Nested imacro call OK because this is a tail call. */ + return call_imacro(defvalue_imacros.string); + } + } + + /* Build an array of the stringified primitives. */ + int32_t bufSize = argc * sizeof(JSString *); + LIns *buf_ins = lir->insAlloc(bufSize); + int32_t d = 0; + for (jsval *vp = regs.sp - argc; vp != regs.sp; ++vp, d += sizeof(void *)) + lir->insStorei(stringify(*vp), buf_ins, d); + + /* Perform concatenation using a builtin. */ + LIns *args[] = { lir->insImm(argc), buf_ins, cx_ins }; + LIns *concat = lir->insCall(&js_ConcatN_ci, args); + guard(false, lir->ins_peq0(concat), OOM_EXIT); + + /* Update tracker with result. */ + jsval *afterPop = regs.sp - (argc - 1); + set(afterPop - 1, concat); + + return JSRS_CONTINUE; +} + +#define DBG_STUB(OP) \ + JS_REQUIRES_STACK JSRecordingStatus \ + TraceRecorder::record_##OP() \ + { \ + ABORT_TRACE("can't trace " #OP); \ + } + +DBG_STUB(JSOP_GETUPVAR_DBG) +DBG_STUB(JSOP_CALLUPVAR_DBG) +DBG_STUB(JSOP_DEFFUN_DBGFC) +DBG_STUB(JSOP_DEFLOCALFUN_DBGFC) +DBG_STUB(JSOP_LAMBDA_DBGFC) + +#ifdef JS_JIT_SPEW +/* + * Print information about entry typemaps and unstable exits for all peers + * at a PC. + */ +void +DumpPeerStability(JSTraceMonitor* tm, const void* ip, JSObject* globalObj, uint32 globalShape, + uint32 argc) +{ + VMFragment* f; + TreeInfo* ti; + bool looped = false; + unsigned length = 0; + + for (f = getLoop(tm, ip, globalObj, globalShape, argc); f != NULL; f = f->peer) { + if (!f->vmprivate) + continue; + debug_only_printf(LC_TMRecorder, "Stability of fragment %p:\nENTRY STACK=", (void*)f); + ti = (TreeInfo*)f->vmprivate; + if (looped) + JS_ASSERT(ti->nStackTypes == length); + for (unsigned i = 0; i < ti->nStackTypes; i++) + debug_only_printf(LC_TMRecorder, "%c", typeChar[ti->stackTypeMap()[i]]); + debug_only_print0(LC_TMRecorder, " GLOBALS="); + for (unsigned i = 0; i < ti->nGlobalTypes(); i++) + debug_only_printf(LC_TMRecorder, "%c", typeChar[ti->globalTypeMap()[i]]); + debug_only_print0(LC_TMRecorder, "\n"); + UnstableExit* uexit = ti->unstableExits; + while (uexit != NULL) { + debug_only_print0(LC_TMRecorder, "EXIT "); + JSTraceType* m = uexit->exit->fullTypeMap(); + debug_only_print0(LC_TMRecorder, "STACK="); + for (unsigned i = 0; i < uexit->exit->numStackSlots; i++) + debug_only_printf(LC_TMRecorder, "%c", typeChar[m[i]]); + debug_only_print0(LC_TMRecorder, " GLOBALS="); + for (unsigned i = 0; i < uexit->exit->numGlobalSlots; i++) { + debug_only_printf(LC_TMRecorder, "%c", + typeChar[m[uexit->exit->numStackSlots + i]]); + } + debug_only_print0(LC_TMRecorder, "\n"); + uexit = uexit->next; + } + length = ti->nStackTypes; + looped = true; + } +} +#endif + +#ifdef MOZ_TRACEVIS + +FILE* traceVisLogFile = NULL; +JSHashTable *traceVisScriptTable = NULL; + +JS_FRIEND_API(bool) +JS_StartTraceVis(const char* filename = "tracevis.dat") +{ + if (traceVisLogFile) { + // If we're currently recording, first we must stop. + JS_StopTraceVis(); + } + + traceVisLogFile = fopen(filename, "wb"); + if (!traceVisLogFile) + return false; + + return true; +} + +JS_FRIEND_API(JSBool) +js_StartTraceVis(JSContext *cx, JSObject *obj, + uintN argc, jsval *argv, jsval *rval) +{ + JSBool ok; + + if (argc > 0 && JSVAL_IS_STRING(argv[0])) { + JSString *str = JSVAL_TO_STRING(argv[0]); + char *filename = js_DeflateString(cx, str->chars(), str->length()); + if (!filename) + goto error; + ok = JS_StartTraceVis(filename); + cx->free(filename); + } else { + ok = JS_StartTraceVis(); + } + + if (ok) { + fprintf(stderr, "started TraceVis recording\n"); + return JS_TRUE; + } + + error: + JS_ReportError(cx, "failed to start TraceVis recording"); + return JS_FALSE; +} + +JS_FRIEND_API(bool) +JS_StopTraceVis() +{ + if (!traceVisLogFile) + return false; + + fclose(traceVisLogFile); // not worth checking the result + traceVisLogFile = NULL; + + return true; +} + +JS_FRIEND_API(JSBool) +js_StopTraceVis(JSContext *cx, JSObject *obj, + uintN argc, jsval *argv, jsval *rval) +{ + JSBool ok = JS_StopTraceVis(); + + if (ok) + fprintf(stderr, "stopped TraceVis recording\n"); + else + JS_ReportError(cx, "TraceVis isn't running"); + + return ok; +} + +#endif /* MOZ_TRACEVIS */ + +#define UNUSED(n) \ + JS_REQUIRES_STACK bool \ + TraceRecorder::record_JSOP_UNUSED##n() { \ + JS_NOT_REACHED("JSOP_UNUSED" # n); \ + return false; \ + } diff --git a/tests/cpp/jstracer_part.cpp b/tests/cpp/jstracer_part.cpp new file mode 100644 index 0000000..30b829b --- /dev/null +++ b/tests/cpp/jstracer_part.cpp @@ -0,0 +1,195 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: set ts=4 sw=4 et tw=99: + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla SpiderMonkey JavaScript 1.9 code, released + * May 28, 2008. + * + * The Initial Developer of the Original Code is + * Brendan Eich + * + * Contributor(s): + * Andreas Gal + * Mike Shaver + * David Anderson + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + + +#include "nanojit/nanojit.h" + +using namespace nanojit; + + +void* +nanojit::Allocator::allocChunk(size_t nbytes) +{ + VMAllocator *vma = (VMAllocator*)this; + JS_ASSERT(!vma->outOfMemory()); + void *p = malloc(nbytes); + if (!p) { + JS_ASSERT(nbytes < sizeof(vma->mReserve)); + vma->mOutOfMemory = true; + p = (void*) &vma->mReserve[0]; + } + vma->mSize += nbytes; + return p; +} + +void +nanojit::Allocator::freeChunk(void *p) { + VMAllocator *vma = (VMAllocator*)this; + if (p != &vma->mReserve[0]) + free(p); +} + +void +nanojit::Allocator::postReset() { + VMAllocator *vma = (VMAllocator*)this; + vma->mOutOfMemory = false; + vma->mSize = 0; +} + + +void +nanojit::StackFilter::getTops(LIns* guard, int& spTop, int& rpTop) +{ + VMSideExit* e = (VMSideExit*)guard->record()->exit; + spTop = e->sp_adj; + rpTop = e->rp_adj; +} + +class AdjustCallerGlobalTypesVisitor : public SlotVisitorBase +{ + TraceRecorder &mRecorder; + JSContext *mCx; + nanojit::LirBuffer *mLirbuf; + nanojit::LirWriter *mLir; + JSTraceType *mTypeMap; +public: + AdjustCallerGlobalTypesVisitor(TraceRecorder &recorder, + JSTraceType *typeMap) : + mRecorder(recorder), + mCx(mRecorder.cx), + mLirbuf(mRecorder.lirbuf), + mLir(mRecorder.lir), + mTypeMap(typeMap) + {} + + JSTraceType* getTypeMap() + { + return mTypeMap; + } + + JS_REQUIRES_STACK JS_ALWAYS_INLINE void + visitGlobalSlot(jsval *vp, unsigned n, unsigned slot) { + LIns *ins = mRecorder.get(vp); + bool isPromote = isPromoteInt(ins); + if (isPromote && *mTypeMap == TT_DOUBLE) { + mLir->insStorei(mRecorder.get(vp), mLirbuf->state, + mRecorder.nativeGlobalOffset(vp)); + + /* + * Aggressively undo speculation so the inner tree will compile + * if this fails. + */ + oracle.markGlobalSlotUndemotable(mCx, slot); + } + JS_ASSERT(!(!isPromote && *mTypeMap == TT_INT32)); + ++mTypeMap; + } +}; + +class AdjustCallerStackTypesVisitor : public SlotVisitorBase +{ + TraceRecorder &mRecorder; + JSContext *mCx; + nanojit::LirBuffer *mLirbuf; + nanojit::LirWriter *mLir; + unsigned mSlotnum; + JSTraceType *mTypeMap; +public: + AdjustCallerStackTypesVisitor(TraceRecorder &recorder, + JSTraceType *typeMap) : + mRecorder(recorder), + mCx(mRecorder.cx), + mLirbuf(mRecorder.lirbuf), + mLir(mRecorder.lir), + mSlotnum(0), + mTypeMap(typeMap) + {} + + JSTraceType* getTypeMap() + { + return mTypeMap; + } + + JS_REQUIRES_STACK JS_ALWAYS_INLINE bool + visitStackSlots(jsval *vp, size_t count, JSStackFrame* fp) { + for (size_t i = 0; i < count; ++i) { + LIns *ins = mRecorder.get(vp); + bool isPromote = isPromoteInt(ins); + if (isPromote && *mTypeMap == TT_DOUBLE) { + mLir->insStorei(mRecorder.get(vp), mLirbuf->sp, + -mRecorder.treeInfo->nativeStackBase + + mRecorder.nativeStackOffset(vp)); + + /* + * Aggressively undo speculation so the inner tree will compile + * if this fails. + */ + oracle.markStackSlotUndemotable(mCx, mSlotnum); + } + JS_ASSERT(!(!isPromote && *mTypeMap == TT_INT32)); + ++vp; + ++mTypeMap; + ++mSlotnum; + } + return true; + } +}; + +#if defined NJ_VERBOSE +void +nanojit::LirNameMap::formatGuard(LIns *i, char *out) +{ + VMSideExit *x; + + x = (VMSideExit *)i->record()->exit; + sprintf(out, + "%s: %s %s -> pc=%p imacpc=%p sp%+ld rp%+ld (GuardID=%03d)", + formatRef(i), + lirNames[i->opcode()], + i->oprnd1() ? formatRef(i->oprnd1()) : "", + (void *)x->pc, + (void *)x->imacpc, + (long int)x->sp_adj, + (long int)x->rp_adj, + i->record()->profGuardID); +} +#endif + diff --git a/tests/cpp/mlprototype.cpp b/tests/cpp/mlprototype.cpp new file mode 100644 index 0000000..628e630 --- /dev/null +++ b/tests/cpp/mlprototype.cpp @@ -0,0 +1,15 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ + + NS_IMETHODIMP + nsThreadClassInfo::GetClassDescription( + char **result, + int foo, + bool blah + ) + { + *result = nsnull; + return NS_OK; + } + +int foo; diff --git a/tests/cpp/multiline.cpp b/tests/cpp/multiline.cpp new file mode 100644 index 0000000..4160fd5 --- /dev/null +++ b/tests/cpp/multiline.cpp @@ -0,0 +1,6 @@ +void* +Foo::bar(int i, + const char const * const * p) +{ + return; +} diff --git a/tests/cpp/nsCycleCollector.cpp b/tests/cpp/nsCycleCollector.cpp new file mode 100644 index 0000000..f263041 --- /dev/null +++ b/tests/cpp/nsCycleCollector.cpp @@ -0,0 +1,3728 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* vim: set cindent tabstop=4 expandtab shiftwidth=4: */ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is mozilla.org code. + * + * The Initial Developer of the Original Code is + * The Mozilla Foundation. + * Portions created by the Initial Developer are Copyright (C) 2006 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * L. David Baron , Mozilla Corporation + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +// +// This file implements a garbage-cycle collector based on the paper +// +// Concurrent Cycle Collection in Reference Counted Systems +// Bacon & Rajan (2001), ECOOP 2001 / Springer LNCS vol 2072 +// +// We are not using the concurrent or acyclic cases of that paper; so +// the green, red and orange colors are not used. +// +// The collector is based on tracking pointers of four colors: +// +// Black nodes are definitely live. If we ever determine a node is +// black, it's ok to forget about, drop from our records. +// +// White nodes are definitely garbage cycles. Once we finish with our +// scanning, we unlink all the white nodes and expect that by +// unlinking them they will self-destruct (since a garbage cycle is +// only keeping itself alive with internal links, by definition). +// +// Grey nodes are being scanned. Nodes that turn grey will turn +// either black if we determine that they're live, or white if we +// determine that they're a garbage cycle. After the main collection +// algorithm there should be no grey nodes. +// +// Purple nodes are *candidates* for being scanned. They are nodes we +// haven't begun scanning yet because they're not old enough, or we're +// still partway through the algorithm. +// +// XPCOM objects participating in garbage-cycle collection are obliged +// to inform us when they ought to turn purple; that is, when their +// refcount transitions from N+1 -> N, for nonzero N. Furthermore we +// require that *after* an XPCOM object has informed us of turning +// purple, they will tell us when they either transition back to being +// black (incremented refcount) or are ultimately deleted. + + +// Safety: +// +// An XPCOM object is either scan-safe or scan-unsafe, purple-safe or +// purple-unsafe. +// +// An object is scan-safe if: +// +// - It can be QI'ed to |nsXPCOMCycleCollectionParticipant|, though this +// operation loses ISupports identity (like nsIClassInfo). +// - The operation |traverse| on the resulting +// nsXPCOMCycleCollectionParticipant does not cause *any* refcount +// adjustment to occur (no AddRef / Release calls). +// +// An object is purple-safe if it satisfies the following properties: +// +// - The object is scan-safe. +// - If the object calls |nsCycleCollector::suspect(this)|, +// it will eventually call |nsCycleCollector::forget(this)|, +// exactly once per call to |suspect|, before being destroyed. +// +// When we receive a pointer |ptr| via +// |nsCycleCollector::suspect(ptr)|, we assume it is purple-safe. We +// can check the scan-safety, but have no way to ensure the +// purple-safety; objects must obey, or else the entire system falls +// apart. Don't involve an object in this scheme if you can't +// guarantee its purple-safety. +// +// When we have a scannable set of purple nodes ready, we begin +// our walks. During the walks, the nodes we |traverse| should only +// feed us more scan-safe nodes, and should not adjust the refcounts +// of those nodes. +// +// We do not |AddRef| or |Release| any objects during scanning. We +// rely on purple-safety of the roots that call |suspect| and +// |forget| to hold, such that we will forget about a purple pointer +// before it is destroyed. The pointers that are merely scan-safe, +// we hold only for the duration of scanning, and there should be no +// objects released from the scan-safe set during the scan (there +// should be no threads involved). +// +// We *do* call |AddRef| and |Release| on every white object, on +// either side of the calls to |Unlink|. This keeps the set of white +// objects alive during the unlinking. +// + +#if !defined(__MINGW32__) +#ifdef WIN32 +#include +#include +#endif +#endif + +#include "base/process_util.h" + +/* This must occur *after* base/process_util.h to avoid typedefs conflicts. */ +#include "mozilla/Util.h" + +#include "nsCycleCollectionParticipant.h" +#include "nsCycleCollectorUtils.h" +#include "nsIProgrammingLanguage.h" +#include "nsBaseHashtable.h" +#include "nsHashKeys.h" +#include "nsDeque.h" +#include "nsCycleCollector.h" +#include "nsThreadUtils.h" +#include "prenv.h" +#include "prprf.h" +#include "plstr.h" +#include "prtime.h" +#include "nsPrintfCString.h" +#include "nsTArray.h" +#include "mozilla/FunctionTimer.h" +#include "nsIObserverService.h" +#include "nsIConsoleService.h" +#include "nsServiceManagerUtils.h" +#include "nsThreadUtils.h" +#include "nsTArray.h" +#include "mozilla/Services.h" +#include "nsICycleCollectorListener.h" +#include "nsIXPConnect.h" +#include "nsIJSRuntimeService.h" +#include "nsIMemoryReporter.h" +#include "xpcpublic.h" +#include +#include +#ifdef WIN32 +#include +#include +#endif + +#ifdef XP_WIN +#include +#endif + +#include "mozilla/Mutex.h" +#include "mozilla/CondVar.h" +#include "mozilla/Telemetry.h" + +using namespace mozilla; + +//#define COLLECT_TIME_DEBUG + +#ifdef DEBUG_CC +#define IF_DEBUG_CC_PARAM(_p) , _p +#define IF_DEBUG_CC_ONLY_PARAM(_p) _p +#else +#define IF_DEBUG_CC_PARAM(_p) +#define IF_DEBUG_CC_ONLY_PARAM(_p) +#endif + +#define DEFAULT_SHUTDOWN_COLLECTIONS 5 +#ifdef DEBUG_CC +#define SHUTDOWN_COLLECTIONS(params) params.mShutdownCollections +#else +#define SHUTDOWN_COLLECTIONS(params) DEFAULT_SHUTDOWN_COLLECTIONS +#endif + +#if defined(XP_WIN) +// Defined in nsThreadManager.cpp. +extern DWORD gTLSThreadIDIndex; +#elif defined(NS_TLS) +// Defined in nsThreadManager.cpp. +extern NS_TLS mozilla::threads::ID gTLSThreadID; +#else +PRThread* gCycleCollectorThread = nsnull; +#endif + +// If true, always log cycle collector graphs. +const bool gAlwaysLogCCGraphs = false; + +// Various parameters of this collector can be tuned using environment +// variables. + +struct nsCycleCollectorParams +{ + bool mDoNothing; + bool mLogGraphs; +#ifdef DEBUG_CC + bool mReportStats; + bool mHookMalloc; + bool mFaultIsFatal; + bool mLogPointers; + PRUint32 mShutdownCollections; +#endif + + nsCycleCollectorParams() : +#ifdef DEBUG_CC + mDoNothing (PR_GetEnv("XPCOM_CC_DO_NOTHING") != NULL), + mLogGraphs (gAlwaysLogCCGraphs || + PR_GetEnv("XPCOM_CC_DRAW_GRAPHS") != NULL), + mReportStats (PR_GetEnv("XPCOM_CC_REPORT_STATS") != NULL), + mHookMalloc (PR_GetEnv("XPCOM_CC_HOOK_MALLOC") != NULL), + mFaultIsFatal (PR_GetEnv("XPCOM_CC_FAULT_IS_FATAL") != NULL), + mLogPointers (PR_GetEnv("XPCOM_CC_LOG_POINTERS") != NULL), + + mShutdownCollections(DEFAULT_SHUTDOWN_COLLECTIONS) +#else + mDoNothing (false), + mLogGraphs (gAlwaysLogCCGraphs) +#endif + { +#ifdef DEBUG_CC + char *s = PR_GetEnv("XPCOM_CC_SHUTDOWN_COLLECTIONS"); + if (s) + PR_sscanf(s, "%d", &mShutdownCollections); +#endif + } +}; + +#ifdef DEBUG_CC +// Various operations involving the collector are recorded in a +// statistics table. These are for diagnostics. + +struct nsCycleCollectorStats +{ + PRUint32 mFailedQI; + PRUint32 mSuccessfulQI; + + PRUint32 mVisitedNode; + PRUint32 mWalkedGraph; + PRUint32 mCollectedBytes; + PRUint32 mFreeCalls; + PRUint32 mFreedBytes; + + PRUint32 mSetColorGrey; + PRUint32 mSetColorBlack; + PRUint32 mSetColorWhite; + + PRUint32 mFailedUnlink; + PRUint32 mCollectedNode; + + PRUint32 mSuspectNode; + PRUint32 mForgetNode; + PRUint32 mFreedWhilePurple; + + PRUint32 mCollection; + + nsCycleCollectorStats() + { + memset(this, 0, sizeof(nsCycleCollectorStats)); + } + + void Dump() + { + fprintf(stderr, "\f\n"); +#define DUMP(entry) fprintf(stderr, "%30.30s: %-20.20d\n", #entry, entry) + DUMP(mFailedQI); + DUMP(mSuccessfulQI); + + DUMP(mVisitedNode); + DUMP(mWalkedGraph); + DUMP(mCollectedBytes); + DUMP(mFreeCalls); + DUMP(mFreedBytes); + + DUMP(mSetColorGrey); + DUMP(mSetColorBlack); + DUMP(mSetColorWhite); + + DUMP(mFailedUnlink); + DUMP(mCollectedNode); + + DUMP(mSuspectNode); + DUMP(mForgetNode); + DUMP(mFreedWhilePurple); + + DUMP(mCollection); +#undef DUMP + } +}; +#endif + +#ifdef DEBUG_CC +static bool nsCycleCollector_shouldSuppress(nsISupports *s); +static void InitMemHook(void); +#endif + +//////////////////////////////////////////////////////////////////////// +// Base types +//////////////////////////////////////////////////////////////////////// + +struct PtrInfo; + +class EdgePool +{ +public: + // EdgePool allocates arrays of void*, primarily to hold PtrInfo*. + // However, at the end of a block, the last two pointers are a null + // and then a void** pointing to the next block. This allows + // EdgePool::Iterators to be a single word but still capable of crossing + // block boundaries. + + EdgePool() + { + mSentinelAndBlocks[0].block = nsnull; + mSentinelAndBlocks[1].block = nsnull; + mNumBlocks = 0; + } + + ~EdgePool() + { + NS_ASSERTION(!mSentinelAndBlocks[0].block && + !mSentinelAndBlocks[1].block, + "Didn't call Clear()?"); + } + + void Clear() + { + Block *b = Blocks(); + while (b) { + Block *next = b->Next(); + delete b; + NS_ASSERTION(mNumBlocks > 0, + "Expected EdgePool mNumBlocks to be positive."); + mNumBlocks--; + b = next; + } + + mSentinelAndBlocks[0].block = nsnull; + mSentinelAndBlocks[1].block = nsnull; + } + +private: + struct Block; + union PtrInfoOrBlock { + // Use a union to avoid reinterpret_cast and the ensuing + // potential aliasing bugs. + PtrInfo *ptrInfo; + Block *block; + }; + struct Block { + enum { BlockSize = 16 * 1024 }; + + PtrInfoOrBlock mPointers[BlockSize]; + Block() { + mPointers[BlockSize - 2].block = nsnull; // sentinel + mPointers[BlockSize - 1].block = nsnull; // next block pointer + } + Block*& Next() + { return mPointers[BlockSize - 1].block; } + PtrInfoOrBlock* Start() + { return &mPointers[0]; } + PtrInfoOrBlock* End() + { return &mPointers[BlockSize - 2]; } + }; + + // Store the null sentinel so that we can have valid iterators + // before adding any edges and without adding any blocks. + PtrInfoOrBlock mSentinelAndBlocks[2]; + PRUint32 mNumBlocks; + + Block*& Blocks() { return mSentinelAndBlocks[1].block; } + +public: + class Iterator + { + public: + Iterator() : mPointer(nsnull) {} + Iterator(PtrInfoOrBlock *aPointer) : mPointer(aPointer) {} + Iterator(const Iterator& aOther) : mPointer(aOther.mPointer) {} + + Iterator& operator++() + { + if (mPointer->ptrInfo == nsnull) { + // Null pointer is a sentinel for link to the next block. + mPointer = (mPointer + 1)->block->mPointers; + } + ++mPointer; + return *this; + } + + PtrInfo* operator*() const + { + if (mPointer->ptrInfo == nsnull) { + // Null pointer is a sentinel for link to the next block. + return (mPointer + 1)->block->mPointers->ptrInfo; + } + return mPointer->ptrInfo; + } + bool operator==(const Iterator& aOther) const + { return mPointer == aOther.mPointer; } + bool operator!=(const Iterator& aOther) const + { return mPointer != aOther.mPointer; } + + private: + PtrInfoOrBlock *mPointer; + }; + + class Builder; + friend class Builder; + class Builder { + public: + Builder(EdgePool &aPool) + : mCurrent(&aPool.mSentinelAndBlocks[0]), + mBlockEnd(&aPool.mSentinelAndBlocks[0]), + mNextBlockPtr(&aPool.Blocks()), + mNumBlocks(aPool.mNumBlocks) + { + } + + Iterator Mark() { return Iterator(mCurrent); } + + void Add(PtrInfo* aEdge) { + if (mCurrent == mBlockEnd) { + Block *b = new Block(); + if (!b) { + // This means we just won't collect (some) cycles. + NS_NOTREACHED("out of memory, ignoring edges"); + return; + } + *mNextBlockPtr = b; + mCurrent = b->Start(); + mBlockEnd = b->End(); + mNextBlockPtr = &b->Next(); + mNumBlocks++; + } + (mCurrent++)->ptrInfo = aEdge; + } + private: + // mBlockEnd points to space for null sentinel + PtrInfoOrBlock *mCurrent, *mBlockEnd; + Block **mNextBlockPtr; + PRUint32 &mNumBlocks; + }; + + size_t BlocksSize() const { + return sizeof(Block) * mNumBlocks; + } + +}; + +#ifdef DEBUG_CC + +struct ReversedEdge { + PtrInfo *mTarget; + nsCString *mEdgeName; + ReversedEdge *mNext; +}; + +#endif + + +enum NodeColor { black, white, grey }; + +// This structure should be kept as small as possible; we may expect +// hundreds of thousands of them to be allocated and touched +// repeatedly during each cycle collection. + +struct PtrInfo +{ + void *mPointer; + nsCycleCollectionParticipant *mParticipant; + PRUint32 mColor : 2; + PRUint32 mInternalRefs : 30; + PRUint32 mRefCount; +private: + EdgePool::Iterator mFirstChild; + +public: +#ifdef DEBUG_CC + size_t mBytes; + char *mName; + PRUint32 mLangID; + + // For finding roots in ExplainLiveExpectedGarbage (when there are + // missing calls to suspect or failures to unlink). + PRUint32 mSCCIndex; // strongly connected component + + // For finding roots in ExplainLiveExpectedGarbage (when nodes + // expected to be garbage are black). + ReversedEdge* mReversedEdges; // linked list + PtrInfo* mShortestPathToExpectedGarbage; + nsCString* mShortestPathToExpectedGarbageEdgeName; + + nsTArray mEdgeNames; +#endif + + PtrInfo(void *aPointer, nsCycleCollectionParticipant *aParticipant + IF_DEBUG_CC_PARAM(PRUint32 aLangID) + ) + : mPointer(aPointer), + mParticipant(aParticipant), + mColor(grey), + mInternalRefs(0), + mRefCount(0), + mFirstChild() +#ifdef DEBUG_CC + , mBytes(0), + mName(nsnull), + mLangID(aLangID), + mSCCIndex(0), + mReversedEdges(nsnull), + mShortestPathToExpectedGarbage(nsnull), + mShortestPathToExpectedGarbageEdgeName(nsnull) +#endif + { + } + +#ifdef DEBUG_CC + void Destroy() { + PL_strfree(mName); + mEdgeNames.~nsTArray(); + } +#endif + + // Allow NodePool::Block's constructor to compile. + PtrInfo() { + NS_NOTREACHED("should never be called"); + } + + EdgePool::Iterator FirstChild() + { + return mFirstChild; + } + + // this PtrInfo must be part of a NodePool + EdgePool::Iterator LastChild() + { + return (this + 1)->mFirstChild; + } + + void SetFirstChild(EdgePool::Iterator aFirstChild) + { + mFirstChild = aFirstChild; + } + + // this PtrInfo must be part of a NodePool + void SetLastChild(EdgePool::Iterator aLastChild) + { + (this + 1)->mFirstChild = aLastChild; + } +}; + +/** + * A structure designed to be used like a linked list of PtrInfo, except + * that allocates the PtrInfo 32K-at-a-time. + */ +class NodePool +{ +private: + enum { BlockSize = 8 * 1024 }; // could be int template parameter + + struct Block { + // We create and destroy Block using NS_Alloc/NS_Free rather + // than new and delete to avoid calling its constructor and + // destructor. + Block() { NS_NOTREACHED("should never be called"); } + ~Block() { NS_NOTREACHED("should never be called"); } + + Block* mNext; + PtrInfo mEntries[BlockSize + 1]; // +1 to store last child of last node + }; + +public: + NodePool() + : mBlocks(nsnull), + mLast(nsnull), + mNumBlocks(0) + { + } + + ~NodePool() + { + NS_ASSERTION(!mBlocks, "Didn't call Clear()?"); + } + + void Clear() + { +#ifdef DEBUG_CC + { + Enumerator queue(*this); + while (!queue.IsDone()) { + queue.GetNext()->Destroy(); + } + } +#endif + Block *b = mBlocks; + while (b) { + Block *n = b->mNext; + NS_Free(b); + NS_ASSERTION(mNumBlocks > 0, + "Expected NodePool mNumBlocks to be positive."); + mNumBlocks--; + b = n; + } + + mBlocks = nsnull; + mLast = nsnull; + } + + class Builder; + friend class Builder; + class Builder { + public: + Builder(NodePool& aPool) + : mNextBlock(&aPool.mBlocks), + mNext(aPool.mLast), + mBlockEnd(nsnull), + mNumBlocks(aPool.mNumBlocks) + { + NS_ASSERTION(aPool.mBlocks == nsnull && aPool.mLast == nsnull, + "pool not empty"); + } + PtrInfo *Add(void *aPointer, nsCycleCollectionParticipant *aParticipant + IF_DEBUG_CC_PARAM(PRUint32 aLangID) + ) + { + if (mNext == mBlockEnd) { + Block *block; + if (!(*mNextBlock = block = + static_cast(NS_Alloc(sizeof(Block))))) + return nsnull; + mNext = block->mEntries; + mBlockEnd = block->mEntries + BlockSize; + block->mNext = nsnull; + mNextBlock = &block->mNext; + mNumBlocks++; + } + return new (mNext++) PtrInfo(aPointer, aParticipant + IF_DEBUG_CC_PARAM(aLangID) + ); + } + private: + Block **mNextBlock; + PtrInfo *&mNext; + PtrInfo *mBlockEnd; + PRUint32 &mNumBlocks; + }; + + class Enumerator; + friend class Enumerator; + class Enumerator { + public: + Enumerator(NodePool& aPool) + : mFirstBlock(aPool.mBlocks), + mCurBlock(nsnull), + mNext(nsnull), + mBlockEnd(nsnull), + mLast(aPool.mLast) + { + } + + bool IsDone() const + { + return mNext == mLast; + } + + bool AtBlockEnd() const + { + return mNext == mBlockEnd; + } + + PtrInfo* GetNext() + { + NS_ASSERTION(!IsDone(), "calling GetNext when done"); + if (mNext == mBlockEnd) { + Block *nextBlock = mCurBlock ? mCurBlock->mNext : mFirstBlock; + mNext = nextBlock->mEntries; + mBlockEnd = mNext + BlockSize; + mCurBlock = nextBlock; + } + return mNext++; + } + private: + Block *mFirstBlock, *mCurBlock; + // mNext is the next value we want to return, unless mNext == mBlockEnd + // NB: mLast is a reference to allow enumerating while building! + PtrInfo *mNext, *mBlockEnd, *&mLast; + }; + + size_t BlocksSize() const { + return sizeof(Block) * mNumBlocks; + } + +private: + Block *mBlocks; + PtrInfo *mLast; + PRUint32 mNumBlocks; +}; + + +struct WeakMapping +{ + // map and key will be null if the corresponding objects are GC marked + PtrInfo *mMap; + PtrInfo *mKey; + PtrInfo *mVal; +}; + +class GCGraphBuilder; + +struct GCGraph +{ + NodePool mNodes; + EdgePool mEdges; + nsTArray mWeakMaps; + PRUint32 mRootCount; +#ifdef DEBUG_CC + ReversedEdge *mReversedEdges; +#endif + + GCGraph() : mRootCount(0) { + } + ~GCGraph() { + } + + size_t BlocksSize() const { + return mNodes.BlocksSize() + mEdges.BlocksSize(); + } + +}; + +// XXX Would be nice to have an nsHashSet API that has +// Add/Remove/Has rather than PutEntry/RemoveEntry/GetEntry. +typedef nsTHashtable PointerSet; + +static inline void +ToParticipant(nsISupports *s, nsXPCOMCycleCollectionParticipant **cp); + +struct nsPurpleBuffer +{ +private: + struct Block { + Block *mNext; + nsPurpleBufferEntry mEntries[255]; + + Block() : mNext(nsnull) {} + }; +public: + // This class wraps a linked list of the elements in the purple + // buffer. + + nsCycleCollectorParams &mParams; + PRUint32 mNumBlocksAlloced; + PRUint32 mCount; + Block mFirstBlock; + nsPurpleBufferEntry *mFreeList; + + // For objects compiled against Gecko 1.9 and 1.9.1. + PointerSet mCompatObjects; +#ifdef DEBUG_CC + PointerSet mNormalObjects; // duplicates our blocks + nsCycleCollectorStats &mStats; +#endif + +#ifdef DEBUG_CC + nsPurpleBuffer(nsCycleCollectorParams ¶ms, + nsCycleCollectorStats &stats) + : mParams(params), + mStats(stats) + { + InitBlocks(); + mNormalObjects.Init(); + mCompatObjects.Init(); + } +#else + nsPurpleBuffer(nsCycleCollectorParams ¶ms) + : mParams(params) + { + InitBlocks(); + mCompatObjects.Init(); + } +#endif + + ~nsPurpleBuffer() + { + FreeBlocks(); + } + + void InitBlocks() + { + mNumBlocksAlloced = 0; + mCount = 0; + mFreeList = nsnull; + StartBlock(&mFirstBlock); + } + + void StartBlock(Block *aBlock) + { + NS_ABORT_IF_FALSE(!mFreeList, "should not have free list"); + + // Put all the entries in the block on the free list. + nsPurpleBufferEntry *entries = aBlock->mEntries; + mFreeList = entries; + for (PRUint32 i = 1; i < ArrayLength(aBlock->mEntries); ++i) { + entries[i - 1].mNextInFreeList = + (nsPurpleBufferEntry*)(PRUword(entries + i) | 1); + } + entries[ArrayLength(aBlock->mEntries) - 1].mNextInFreeList = + (nsPurpleBufferEntry*)1; + } + + void FreeBlocks() + { + if (mCount > 0) + UnmarkRemainingPurple(&mFirstBlock); + Block *b = mFirstBlock.mNext; + while (b) { + if (mCount > 0) + UnmarkRemainingPurple(b); + Block *next = b->mNext; + delete b; + b = next; + NS_ASSERTION(mNumBlocksAlloced > 0, + "Expected positive mNumBlocksAlloced."); + mNumBlocksAlloced--; + } + mFirstBlock.mNext = nsnull; + } + + void UnmarkRemainingPurple(Block *b) + { + for (nsPurpleBufferEntry *e = b->mEntries, + *eEnd = ArrayEnd(b->mEntries); + e != eEnd; ++e) { + if (!(PRUword(e->mObject) & PRUword(1))) { + // This is a real entry (rather than something on the + // free list). + if (e->mObject) { + nsXPCOMCycleCollectionParticipant *cp; + ToParticipant(e->mObject, &cp); + + cp->UnmarkPurple(e->mObject); + } + + if (--mCount == 0) + break; + } + } + } + + void SelectPointers(GCGraphBuilder &builder); + +#ifdef DEBUG_CC + void NoteAll(GCGraphBuilder &builder); + + bool Exists(void *p) const + { + return mNormalObjects.GetEntry(p) || mCompatObjects.GetEntry(p); + } +#endif + + nsPurpleBufferEntry* NewEntry() + { + if (!mFreeList) { + Block *b = new Block; + if (!b) { + return nsnull; + } + mNumBlocksAlloced++; + StartBlock(b); + + // Add the new block as the second block in the list. + b->mNext = mFirstBlock.mNext; + mFirstBlock.mNext = b; + } + + nsPurpleBufferEntry *e = mFreeList; + mFreeList = (nsPurpleBufferEntry*) + (PRUword(mFreeList->mNextInFreeList) & ~PRUword(1)); + return e; + } + + nsPurpleBufferEntry* Put(nsISupports *p) + { + nsPurpleBufferEntry *e = NewEntry(); + if (!e) { + return nsnull; + } + + ++mCount; + + e->mObject = p; + +#ifdef DEBUG_CC + mNormalObjects.PutEntry(p); +#endif + + // Caller is responsible for filling in result's mRefCnt. + return e; + } + + void Remove(nsPurpleBufferEntry *e) + { + NS_ASSERTION(mCount != 0, "must have entries"); + +#ifdef DEBUG_CC + mNormalObjects.RemoveEntry(e->mObject); +#endif + + e->mNextInFreeList = + (nsPurpleBufferEntry*)(PRUword(mFreeList) | PRUword(1)); + mFreeList = e; + + --mCount; + } + + bool PutCompatObject(nsISupports *p) + { + ++mCount; + return !!mCompatObjects.PutEntry(p); + } + + void RemoveCompatObject(nsISupports *p) + { + --mCount; + mCompatObjects.RemoveEntry(p); + } + + PRUint32 Count() const + { + return mCount; + } + + size_t BlocksSize() const + { + return sizeof(Block) * mNumBlocksAlloced; + } + +}; + +struct CallbackClosure +{ + CallbackClosure(nsPurpleBuffer *aPurpleBuffer, GCGraphBuilder &aBuilder) + : mPurpleBuffer(aPurpleBuffer), + mBuilder(aBuilder) + { + } + nsPurpleBuffer *mPurpleBuffer; + GCGraphBuilder &mBuilder; +}; + +static bool +AddPurpleRoot(GCGraphBuilder &builder, nsISupports *root); + +static PLDHashOperator +selectionCallback(nsVoidPtrHashKey* key, void* userArg) +{ + CallbackClosure *closure = static_cast(userArg); + if (AddPurpleRoot(closure->mBuilder, + static_cast( + const_cast(key->GetKey())))) + return PL_DHASH_REMOVE; + + return PL_DHASH_NEXT; +} + +void +nsPurpleBuffer::SelectPointers(GCGraphBuilder &aBuilder) +{ +#ifdef DEBUG_CC + NS_ABORT_IF_FALSE(mCompatObjects.Count() + mNormalObjects.Count() == + mCount, + "count out of sync"); +#endif + + if (mCompatObjects.Count()) { + mCount -= mCompatObjects.Count(); + CallbackClosure closure(this, aBuilder); + mCompatObjects.EnumerateEntries(selectionCallback, &closure); + mCount += mCompatObjects.Count(); // in case of allocation failure + } + + // Walk through all the blocks. + for (Block *b = &mFirstBlock; b; b = b->mNext) { + for (nsPurpleBufferEntry *e = b->mEntries, + *eEnd = ArrayEnd(b->mEntries); + e != eEnd; ++e) { + if (!(PRUword(e->mObject) & PRUword(1))) { + // This is a real entry (rather than something on the + // free list). + if (!e->mObject || AddPurpleRoot(aBuilder, e->mObject)) { +#ifdef DEBUG_CC + mNormalObjects.RemoveEntry(e->mObject); +#endif + --mCount; + // Put this entry on the free list in case some + // call to AddPurpleRoot fails and we don't rebuild + // the free list below. + e->mNextInFreeList = (nsPurpleBufferEntry*) + (PRUword(mFreeList) | PRUword(1)); + mFreeList = e; + } + } + } + } + + NS_WARN_IF_FALSE(mCount == 0, "AddPurpleRoot failed"); + if (mCount == 0) { + FreeBlocks(); + InitBlocks(); + } +} + + + +//////////////////////////////////////////////////////////////////////// +// Implement the LanguageRuntime interface for C++/XPCOM +//////////////////////////////////////////////////////////////////////// + + +struct nsCycleCollectionXPCOMRuntime : + public nsCycleCollectionLanguageRuntime +{ + nsresult BeginCycleCollection(nsCycleCollectionTraversalCallback &cb, + bool explainLiveExpectedGarbage) + { + return NS_OK; + } + + nsresult FinishTraverse() + { + return NS_OK; + } + + nsresult FinishCycleCollection() + { + return NS_OK; + } + + inline nsCycleCollectionParticipant *ToParticipant(void *p); + +#ifdef DEBUG_CC + virtual void PrintAllReferencesTo(void *p) {} +#endif +}; + +struct nsCycleCollector +{ + bool mCollectionInProgress; + bool mScanInProgress; + bool mFollowupCollection; + PRUint32 mCollectedObjects; + TimeStamp mCollectionStart; + + nsCycleCollectionLanguageRuntime *mRuntimes[nsIProgrammingLanguage::MAX+1]; + nsCycleCollectionXPCOMRuntime mXPCOMRuntime; + + GCGraph mGraph; + + nsCycleCollectorParams mParams; + + nsTArray *mWhiteNodes; + PRUint32 mWhiteNodeCount; + + // mVisitedRefCounted and mVisitedGCed are only used for telemetry + PRUint32 mVisitedRefCounted; + PRUint32 mVisitedGCed; + + nsPurpleBuffer mPurpleBuf; + + void RegisterRuntime(PRUint32 langID, + nsCycleCollectionLanguageRuntime *rt); + nsCycleCollectionLanguageRuntime * GetRuntime(PRUint32 langID); + void ForgetRuntime(PRUint32 langID); + + void SelectPurple(GCGraphBuilder &builder); + void MarkRoots(GCGraphBuilder &builder); + void ScanRoots(); + void ScanWeakMaps(); + + // returns whether anything was collected + bool CollectWhite(nsICycleCollectorListener *aListener); + + nsCycleCollector(); + ~nsCycleCollector(); + + // The first pair of Suspect and Forget functions are only used by + // old XPCOM binary components. + bool Suspect(nsISupports *n); + bool Forget(nsISupports *n); + nsPurpleBufferEntry* Suspect2(nsISupports *n); + bool Forget2(nsPurpleBufferEntry *e); + + PRUint32 Collect(PRUint32 aTryCollections, + nsICycleCollectorListener *aListener); + + // Prepare for and cleanup after one or more collection(s). + bool PrepareForCollection(nsTArray *aWhiteNodes); + void GCIfNeeded(bool aForceGC); + void CleanupAfterCollection(); + + // Start and finish an individual collection. + bool BeginCollection(nsICycleCollectorListener *aListener); + bool FinishCollection(nsICycleCollectorListener *aListener); + + PRUint32 SuspectedCount(); + void Shutdown(); + + void ClearGraph() + { + mGraph.mNodes.Clear(); + mGraph.mEdges.Clear(); + mGraph.mWeakMaps.Clear(); + mGraph.mRootCount = 0; + } + +#ifdef DEBUG_CC + nsCycleCollectorStats mStats; + + FILE *mPtrLog; + + void Allocated(void *n, size_t sz); + void Freed(void *n); + + void ExplainLiveExpectedGarbage(); + bool CreateReversedEdges(); + void DestroyReversedEdges(); + void ShouldBeFreed(nsISupports *n); + void WasFreed(nsISupports *n); + PointerSet mExpectedGarbage; +#endif +}; + + +/** + * GraphWalker is templatized over a Visitor class that must provide + * the following two methods: + * + * bool ShouldVisitNode(PtrInfo const *pi); + * void VisitNode(PtrInfo *pi); + */ +template +class GraphWalker +{ +private: + Visitor mVisitor; + + void DoWalk(nsDeque &aQueue); + +public: + void Walk(PtrInfo *s0); + void WalkFromRoots(GCGraph &aGraph); + // copy-constructing the visitor should be cheap, and less + // indirection than using a reference + GraphWalker(const Visitor aVisitor) : mVisitor(aVisitor) {} +}; + + +//////////////////////////////////////////////////////////////////////// +// The static collector object +//////////////////////////////////////////////////////////////////////// + + +static nsCycleCollector *sCollector = nsnull; + + +//////////////////////////////////////////////////////////////////////// +// Utility functions +//////////////////////////////////////////////////////////////////////// + +class CCRunnableFaultReport : public nsRunnable { +public: + CCRunnableFaultReport(const nsCString& report) + { + CopyUTF8toUTF16(report, mReport); + } + + NS_IMETHOD Run() { + nsCOMPtr obs = + do_GetService(NS_OBSERVERSERVICE_CONTRACTID); + if (obs) { + obs->NotifyObservers(nsnull, "cycle-collector-fault", + mReport.get()); + } + + nsCOMPtr cons = + do_GetService(NS_CONSOLESERVICE_CONTRACTID); + if (cons) { + cons->LogStringMessage(mReport.get()); + } + return NS_OK; + } + +private: + nsString mReport; +}; + +static void +Fault(const char *msg, const void *ptr=nsnull) +{ +#ifdef DEBUG_CC + // This should be nearly impossible, but just in case. + if (!sCollector) + return; + + if (sCollector->mParams.mFaultIsFatal) { + + if (ptr) + printf("Fatal fault in cycle collector: %s (ptr: %p)\n", msg, ptr); + else + printf("Fatal fault in cycle collector: %s\n", msg); + + exit(1); + } +#endif + + nsPrintfCString str(256, "Fault in cycle collector: %s (ptr: %p)\n", + msg, ptr); + NS_NOTREACHED(str.get()); + + // When faults are not fatal, we assume we're running in a + // production environment and we therefore want to disable the + // collector on a fault. This will unfortunately cause the browser + // to leak pretty fast wherever creates cyclical garbage, but it's + // probably a better user experience than crashing. Besides, we + // *should* never hit a fault. + + sCollector->mParams.mDoNothing = true; + + // Report to observers off an event so we don't run JS under GC + // (which is where we might be right now). + nsCOMPtr ev = new CCRunnableFaultReport(str); + NS_DispatchToMainThread(ev); +} + +#ifdef DEBUG_CC +static void +Fault(const char *msg, PtrInfo *pi) +{ + printf("Fault in cycle collector: %s\n" + " while operating on pointer %p %s\n", + msg, pi->mPointer, pi->mName); + if (pi->mInternalRefs) { + printf(" which has internal references from:\n"); + NodePool::Enumerator queue(sCollector->mGraph.mNodes); + while (!queue.IsDone()) { + PtrInfo *ppi = queue.GetNext(); + for (EdgePool::Iterator e = ppi->FirstChild(), + e_end = ppi->LastChild(); + e != e_end; ++e) { + if (*e == pi) { + printf(" %p %s\n", ppi->mPointer, ppi->mName); + } + } + } + } + + Fault(msg, pi->mPointer); +} +#else +inline void +Fault(const char *msg, PtrInfo *pi) +{ + Fault(msg, pi->mPointer); +} +#endif + +static inline void +AbortIfOffMainThreadIfCheckFast() +{ +#if defined(XP_WIN) || defined(NS_TLS) + if (!NS_IsMainThread() && !NS_IsCycleCollectorThread()) { + NS_RUNTIMEABORT("Main-thread-only object used off the main thread"); + } +#endif +} + +static nsISupports * +canonicalize(nsISupports *in) +{ + nsISupports* child; + in->QueryInterface(NS_GET_IID(nsCycleCollectionISupports), + reinterpret_cast(&child)); + return child; +} + +static inline void +ToParticipant(nsISupports *s, nsXPCOMCycleCollectionParticipant **cp) +{ + // We use QI to move from an nsISupports to an + // nsXPCOMCycleCollectionParticipant, which is a per-class singleton helper + // object that implements traversal and unlinking logic for the nsISupports + // in question. + CallQueryInterface(s, cp); +#ifdef DEBUG_CC + if (cp) + ++sCollector->mStats.mSuccessfulQI; + else + ++sCollector->mStats.mFailedQI; +#endif +} + +nsCycleCollectionParticipant * +nsCycleCollectionXPCOMRuntime::ToParticipant(void *p) +{ + nsXPCOMCycleCollectionParticipant *cp; + ::ToParticipant(static_cast(p), &cp); + return cp; +} + + +template +void +GraphWalker::Walk(PtrInfo *s0) +{ + nsDeque queue; + queue.Push(s0); + DoWalk(queue); +} + +template +void +GraphWalker::WalkFromRoots(GCGraph& aGraph) +{ + nsDeque queue; + NodePool::Enumerator etor(aGraph.mNodes); + for (PRUint32 i = 0; i < aGraph.mRootCount; ++i) { + queue.Push(etor.GetNext()); + } + DoWalk(queue); +} + +template +void +GraphWalker::DoWalk(nsDeque &aQueue) +{ + // Use a aQueue to match the breadth-first traversal used when we + // built the graph, for hopefully-better locality. + while (aQueue.GetSize() > 0) { + PtrInfo *pi = static_cast(aQueue.PopFront()); + + if (mVisitor.ShouldVisitNode(pi)) { + mVisitor.VisitNode(pi); + for (EdgePool::Iterator child = pi->FirstChild(), + child_end = pi->LastChild(); + child != child_end; ++child) { + aQueue.Push(*child); + } + } + }; + +#ifdef DEBUG_CC + sCollector->mStats.mWalkedGraph++; +#endif +} + + +class nsCycleCollectorLogger : public nsICycleCollectorListener +{ +public: + nsCycleCollectorLogger() : mStream(nsnull) + { + } + ~nsCycleCollectorLogger() + { + if (mStream) { + fclose(mStream); + } + } + NS_DECL_ISUPPORTS + + NS_IMETHOD Begin() + { + char name[255]; + sprintf(name, "cc-edges-%d.%d.log", ++gLogCounter, base::GetCurrentProcId()); + mStream = fopen(name, "w"); + + return mStream ? NS_OK : NS_ERROR_FAILURE; + } + NS_IMETHOD NoteRefCountedObject(PRUint64 aAddress, PRUint32 refCount, + const char *aObjectDescription) + { + fprintf(mStream, "%p [rc=%u] %s\n", (void*)aAddress, refCount, + aObjectDescription); + + return NS_OK; + } + NS_IMETHOD NoteGCedObject(PRUint64 aAddress, bool aMarked, + const char *aObjectDescription) + { + fprintf(mStream, "%p [gc%s] %s\n", (void*)aAddress, + aMarked ? ".marked" : "", aObjectDescription); + + return NS_OK; + } + NS_IMETHOD NoteEdge(PRUint64 aToAddress, const char *aEdgeName) + { + fprintf(mStream, "> %p %s\n", (void*)aToAddress, aEdgeName); + + return NS_OK; + } + NS_IMETHOD BeginResults() + { + fputs("==========\n", mStream); + + return NS_OK; + } + NS_IMETHOD DescribeRoot(PRUint64 aAddress, PRUint32 aKnownEdges) + { + fprintf(mStream, "%p [known=%u]\n", (void*)aAddress, aKnownEdges); + + return NS_OK; + } + NS_IMETHOD DescribeGarbage(PRUint64 aAddress) + { + fprintf(mStream, "%p [garbage]\n", (void*)aAddress); + + return NS_OK; + } + NS_IMETHOD End() + { + fclose(mStream); + mStream = nsnull; + + return NS_OK; + } + +private: + FILE *mStream; + + static PRUint32 gLogCounter; +}; + +NS_IMPL_ISUPPORTS1(nsCycleCollectorLogger, nsICycleCollectorListener) + +PRUint32 nsCycleCollectorLogger::gLogCounter = 0; + +nsresult +nsCycleCollectorLoggerConstructor(nsISupports* aOuter, + const nsIID& aIID, + void* *aInstancePtr) +{ + NS_ENSURE_TRUE(!aOuter, NS_ERROR_NO_AGGREGATION); + + nsISupports *logger = new nsCycleCollectorLogger(); + + return logger->QueryInterface(aIID, aInstancePtr); +} + +//////////////////////////////////////////////////////////////////////// +// Bacon & Rajan's |MarkRoots| routine. +//////////////////////////////////////////////////////////////////////// + +struct PtrToNodeEntry : public PLDHashEntryHdr +{ + // The key is mNode->mPointer + PtrInfo *mNode; +}; + +static bool +PtrToNodeMatchEntry(PLDHashTable *table, + const PLDHashEntryHdr *entry, + const void *key) +{ + const PtrToNodeEntry *n = static_cast(entry); + return n->mNode->mPointer == key; +} + +static PLDHashTableOps PtrNodeOps = { + PL_DHashAllocTable, + PL_DHashFreeTable, + PL_DHashVoidPtrKeyStub, + PtrToNodeMatchEntry, + PL_DHashMoveEntryStub, + PL_DHashClearEntryStub, + PL_DHashFinalizeStub, + nsnull +}; + +class GCGraphBuilder : public nsCycleCollectionTraversalCallback +{ +private: + NodePool::Builder mNodeBuilder; + EdgePool::Builder mEdgeBuilder; + nsTArray &mWeakMaps; + PLDHashTable mPtrToNodeMap; + PtrInfo *mCurrPi; + nsCycleCollectionLanguageRuntime **mRuntimes; // weak, from nsCycleCollector + nsCString mNextEdgeName; + nsICycleCollectorListener *mListener; + +public: + GCGraphBuilder(GCGraph &aGraph, + nsCycleCollectionLanguageRuntime **aRuntimes, + nsICycleCollectorListener *aListener); + ~GCGraphBuilder(); + bool Initialized(); + + PRUint32 Count() const { return mPtrToNodeMap.entryCount; } + +#ifdef DEBUG_CC + PtrInfo* AddNode(void *s, nsCycleCollectionParticipant *aParticipant, + PRUint32 aLangID); +#else + PtrInfo* AddNode(void *s, nsCycleCollectionParticipant *aParticipant); + PtrInfo* AddNode(void *s, nsCycleCollectionParticipant *aParticipant, + PRUint32 aLangID) + { + return AddNode(s, aParticipant); + } +#endif + PtrInfo* AddWeakMapNode(void* node); + void Traverse(PtrInfo* aPtrInfo); + void SetLastChild(); + + // nsCycleCollectionTraversalCallback methods. + NS_IMETHOD_(void) NoteXPCOMRoot(nsISupports *root); + +private: + void DescribeNode(PRUint32 refCount, + size_t objSz, + const char *objName) + { + mCurrPi->mRefCount = refCount; +#ifdef DEBUG_CC + mCurrPi->mBytes = objSz; + mCurrPi->mName = PL_strdup(objName); + sCollector->mStats.mVisitedNode++; +#endif + } + + NS_IMETHOD_(void) DescribeRefCountedNode(nsrefcnt refCount, size_t objSz, + const char *objName); + NS_IMETHOD_(void) DescribeGCedNode(bool isMarked, size_t objSz, + const char *objName); + NS_IMETHOD_(void) NoteRoot(PRUint32 langID, void *child, + nsCycleCollectionParticipant* participant); + NS_IMETHOD_(void) NoteXPCOMChild(nsISupports *child); + NS_IMETHOD_(void) NoteNativeChild(void *child, + nsCycleCollectionParticipant *participant); + NS_IMETHOD_(void) NoteScriptChild(PRUint32 langID, void *child); + NS_IMETHOD_(void) NoteNextEdgeName(const char* name); + NS_IMETHOD_(void) NoteWeakMapping(void *map, void *key, void *val); +}; + +GCGraphBuilder::GCGraphBuilder(GCGraph &aGraph, + nsCycleCollectionLanguageRuntime **aRuntimes, + nsICycleCollectorListener *aListener) + : mNodeBuilder(aGraph.mNodes), + mEdgeBuilder(aGraph.mEdges), + mWeakMaps(aGraph.mWeakMaps), + mRuntimes(aRuntimes), + mListener(aListener) +{ + if (!PL_DHashTableInit(&mPtrToNodeMap, &PtrNodeOps, nsnull, + sizeof(PtrToNodeEntry), 32768)) + mPtrToNodeMap.ops = nsnull; + // We want all edges and all info if DEBUG_CC is set or if we have a + // listener. Do we want them all the time? +#ifndef DEBUG_CC + if (mListener) +#endif + { + mFlags |= nsCycleCollectionTraversalCallback::WANT_DEBUG_INFO | + nsCycleCollectionTraversalCallback::WANT_ALL_TRACES; + } +} + +GCGraphBuilder::~GCGraphBuilder() +{ + if (mPtrToNodeMap.ops) + PL_DHashTableFinish(&mPtrToNodeMap); +} + +bool +GCGraphBuilder::Initialized() +{ + return !!mPtrToNodeMap.ops; +} + +PtrInfo* +GCGraphBuilder::AddNode(void *s, nsCycleCollectionParticipant *aParticipant + IF_DEBUG_CC_PARAM(PRUint32 aLangID) + ) +{ + PtrToNodeEntry *e = static_cast(PL_DHashTableOperate(&mPtrToNodeMap, s, PL_DHASH_ADD)); + if (!e) + return nsnull; + + PtrInfo *result; + if (!e->mNode) { + // New entry. + result = mNodeBuilder.Add(s, aParticipant + IF_DEBUG_CC_PARAM(aLangID) + ); + if (!result) { + PL_DHashTableRawRemove(&mPtrToNodeMap, e); + return nsnull; + } + e->mNode = result; + } else { + result = e->mNode; + NS_ASSERTION(result->mParticipant == aParticipant, + "nsCycleCollectionParticipant shouldn't change!"); + } + return result; +} + +void +GCGraphBuilder::Traverse(PtrInfo* aPtrInfo) +{ + mCurrPi = aPtrInfo; + +#ifdef DEBUG_CC + if (!mCurrPi->mParticipant) { + Fault("unknown pointer during walk", aPtrInfo); + return; + } +#endif + + mCurrPi->SetFirstChild(mEdgeBuilder.Mark()); + + nsresult rv = aPtrInfo->mParticipant->Traverse(aPtrInfo->mPointer, *this); + if (NS_FAILED(rv)) { + Fault("script pointer traversal failed", aPtrInfo); + } +} + +void +GCGraphBuilder::SetLastChild() +{ + mCurrPi->SetLastChild(mEdgeBuilder.Mark()); +} + +NS_IMETHODIMP_(void) +GCGraphBuilder::NoteXPCOMRoot(nsISupports *root) +{ + root = canonicalize(root); + NS_ASSERTION(root, + "Don't add objects that don't participate in collection!"); + +#ifdef DEBUG_CC + if (nsCycleCollector_shouldSuppress(root)) + return; +#endif + + nsXPCOMCycleCollectionParticipant *cp; + ToParticipant(root, &cp); + + NoteRoot(nsIProgrammingLanguage::CPLUSPLUS, root, cp); +} + + +NS_IMETHODIMP_(void) +GCGraphBuilder::NoteRoot(PRUint32 langID, void *root, + nsCycleCollectionParticipant* participant) +{ + NS_ASSERTION(root, "Don't add a null root!"); + + if (langID > nsIProgrammingLanguage::MAX || !mRuntimes[langID]) { + Fault("adding root for unregistered language", root); + return; + } + + AddNode(root, participant, langID); +} + +NS_IMETHODIMP_(void) +GCGraphBuilder::DescribeRefCountedNode(nsrefcnt refCount, size_t objSz, + const char *objName) +{ + if (refCount == 0) + Fault("zero refcount", mCurrPi); + if (refCount == PR_UINT32_MAX) + Fault("overflowing refcount", mCurrPi); + sCollector->mVisitedRefCounted++; + + if (mListener) { + mListener->NoteRefCountedObject((PRUint64)mCurrPi->mPointer, refCount, + objName); + } + + DescribeNode(refCount, objSz, objName); +} + +NS_IMETHODIMP_(void) +GCGraphBuilder::DescribeGCedNode(bool isMarked, size_t objSz, + const char *objName) +{ + PRUint32 refCount = isMarked ? PR_UINT32_MAX : 0; + sCollector->mVisitedGCed++; + + if (mListener) { + mListener->NoteGCedObject((PRUint64)mCurrPi->mPointer, isMarked, + objName); + } + + DescribeNode(refCount, objSz, objName); +} + +NS_IMETHODIMP_(void) +GCGraphBuilder::NoteXPCOMChild(nsISupports *child) +{ + nsCString edgeName; + if (WantDebugInfo()) { + edgeName.Assign(mNextEdgeName); + mNextEdgeName.Truncate(); + } + if (!child || !(child = canonicalize(child))) + return; + +#ifdef DEBUG_CC + if (nsCycleCollector_shouldSuppress(child)) + return; +#endif + + nsXPCOMCycleCollectionParticipant *cp; + ToParticipant(child, &cp); + if (cp) { + PtrInfo *childPi = AddNode(child, cp, nsIProgrammingLanguage::CPLUSPLUS); + if (!childPi) + return; + mEdgeBuilder.Add(childPi); +#ifdef DEBUG_CC + mCurrPi->mEdgeNames.AppendElement(edgeName); +#endif + if (mListener) { + mListener->NoteEdge((PRUint64)child, edgeName.get()); + } + ++childPi->mInternalRefs; + } +} + +NS_IMETHODIMP_(void) +GCGraphBuilder::NoteNativeChild(void *child, + nsCycleCollectionParticipant *participant) +{ + nsCString edgeName; + if (WantDebugInfo()) { + edgeName.Assign(mNextEdgeName); + mNextEdgeName.Truncate(); + } + if (!child) + return; + + NS_ASSERTION(participant, "Need a nsCycleCollectionParticipant!"); + + PtrInfo *childPi = AddNode(child, participant, nsIProgrammingLanguage::CPLUSPLUS); + if (!childPi) + return; + mEdgeBuilder.Add(childPi); +#ifdef DEBUG_CC + mCurrPi->mEdgeNames.AppendElement(edgeName); +#endif + if (mListener) { + mListener->NoteEdge((PRUint64)child, edgeName.get()); + } + ++childPi->mInternalRefs; +} + +NS_IMETHODIMP_(void) +GCGraphBuilder::NoteScriptChild(PRUint32 langID, void *child) +{ + nsCString edgeName; + if (WantDebugInfo()) { + edgeName.Assign(mNextEdgeName); + mNextEdgeName.Truncate(); + } + if (!child) + return; + + if (langID > nsIProgrammingLanguage::MAX) { + Fault("traversing pointer for unknown language", child); + return; + } + + if (!mRuntimes[langID]) { + NS_WARNING("Not collecting cycles involving objects for scripting " + "languages that don't participate in cycle collection."); + return; + } + + // skip over non-grey JS children + if (langID == nsIProgrammingLanguage::JAVASCRIPT && + !xpc_GCThingIsGrayCCThing(child) && !WantAllTraces()) { + return; + } + + nsCycleCollectionParticipant *cp = mRuntimes[langID]->ToParticipant(child); + if (!cp) + return; + + PtrInfo *childPi = AddNode(child, cp, langID); + if (!childPi) + return; + mEdgeBuilder.Add(childPi); +#ifdef DEBUG_CC + mCurrPi->mEdgeNames.AppendElement(edgeName); +#endif + if (mListener) { + mListener->NoteEdge((PRUint64)child, edgeName.get()); + } + ++childPi->mInternalRefs; +} + +NS_IMETHODIMP_(void) +GCGraphBuilder::NoteNextEdgeName(const char* name) +{ + if (WantDebugInfo()) { + mNextEdgeName = name; + } +} + +PtrInfo* +GCGraphBuilder::AddWeakMapNode(void *node) +{ + nsCycleCollectionParticipant *cp; + NS_ASSERTION(node, "Weak map node should be non-null."); + + if (!xpc_GCThingIsGrayCCThing(node) && !WantAllTraces()) + return nsnull; + + cp = mRuntimes[nsIProgrammingLanguage::JAVASCRIPT]->ToParticipant(node); + NS_ASSERTION(cp, "Javascript runtime participant should be non-null."); + return AddNode(node, cp); +} + +NS_IMETHODIMP_(void) +GCGraphBuilder::NoteWeakMapping(void *map, void *key, void *val) +{ + PtrInfo *valNode = AddWeakMapNode(val); + + if (!valNode) + return; + + WeakMapping *mapping = mWeakMaps.AppendElement(); + mapping->mMap = map ? AddWeakMapNode(map) : nsnull; + mapping->mKey = key ? AddWeakMapNode(key) : nsnull; + mapping->mVal = valNode; +} + +static bool +AddPurpleRoot(GCGraphBuilder &builder, nsISupports *root) +{ + root = canonicalize(root); + NS_ASSERTION(root, + "Don't add objects that don't participate in collection!"); + + nsXPCOMCycleCollectionParticipant *cp; + ToParticipant(root, &cp); + + PtrInfo *pinfo = builder.AddNode(root, cp, + nsIProgrammingLanguage::CPLUSPLUS); + if (!pinfo) { + return false; + } + + cp->UnmarkPurple(root); + + return true; +} + +#ifdef DEBUG_CC +static PLDHashOperator +noteAllCallback(nsVoidPtrHashKey* key, void* userArg) +{ + GCGraphBuilder *builder = static_cast(userArg); + builder->NoteXPCOMRoot( + static_cast(const_cast(key->GetKey()))); + return PL_DHASH_NEXT; +} + +void +nsPurpleBuffer::NoteAll(GCGraphBuilder &builder) +{ + mCompatObjects.EnumerateEntries(noteAllCallback, &builder); + + for (Block *b = &mFirstBlock; b; b = b->mNext) { + for (nsPurpleBufferEntry *e = b->mEntries, + *eEnd = ArrayEnd(b->mEntries); + e != eEnd; ++e) { + if (!(PRUword(e->mObject) & PRUword(1)) && e->mObject) { + builder.NoteXPCOMRoot(e->mObject); + } + } + } +} +#endif + +void +nsCycleCollector::SelectPurple(GCGraphBuilder &builder) +{ + mPurpleBuf.SelectPointers(builder); +} + +void +nsCycleCollector::MarkRoots(GCGraphBuilder &builder) +{ + mGraph.mRootCount = builder.Count(); + + // read the PtrInfo out of the graph that we are building + NodePool::Enumerator queue(mGraph.mNodes); + while (!queue.IsDone()) { + PtrInfo *pi = queue.GetNext(); + builder.Traverse(pi); + if (queue.AtBlockEnd()) + builder.SetLastChild(); + } + if (mGraph.mRootCount > 0) + builder.SetLastChild(); +} + + +//////////////////////////////////////////////////////////////////////// +// Bacon & Rajan's |ScanRoots| routine. +//////////////////////////////////////////////////////////////////////// + + +struct ScanBlackVisitor +{ + ScanBlackVisitor(PRUint32 &aWhiteNodeCount) + : mWhiteNodeCount(aWhiteNodeCount) + { + } + + bool ShouldVisitNode(PtrInfo const *pi) + { + return pi->mColor != black; + } + + void VisitNode(PtrInfo *pi) + { + if (pi->mColor == white) + --mWhiteNodeCount; + pi->mColor = black; +#ifdef DEBUG_CC + sCollector->mStats.mSetColorBlack++; +#endif + } + + PRUint32 &mWhiteNodeCount; +}; + + +struct scanVisitor +{ + scanVisitor(PRUint32 &aWhiteNodeCount) : mWhiteNodeCount(aWhiteNodeCount) + { + } + + bool ShouldVisitNode(PtrInfo const *pi) + { + return pi->mColor == grey; + } + + void VisitNode(PtrInfo *pi) + { + if (pi->mInternalRefs > pi->mRefCount && pi->mRefCount > 0) + Fault("traversed refs exceed refcount", pi); + + if (pi->mInternalRefs == pi->mRefCount || pi->mRefCount == 0) { + pi->mColor = white; + ++mWhiteNodeCount; +#ifdef DEBUG_CC + sCollector->mStats.mSetColorWhite++; +#endif + } else { + GraphWalker(ScanBlackVisitor(mWhiteNodeCount)).Walk(pi); + NS_ASSERTION(pi->mColor == black, + "Why didn't ScanBlackVisitor make pi black?"); + } + } + + PRUint32 &mWhiteNodeCount; +}; + +// Iterate over the WeakMaps. If we mark anything while iterating +// over the WeakMaps, we must iterate over all of the WeakMaps again. +void +nsCycleCollector::ScanWeakMaps() +{ + bool anyChanged; + do { + anyChanged = false; + for (PRUint32 i = 0; i < mGraph.mWeakMaps.Length(); i++) { + WeakMapping *wm = &mGraph.mWeakMaps[i]; + + // If mMap or mKey are null, the original object was marked black. + uint32 mColor = wm->mMap ? wm->mMap->mColor : black; + uint32 kColor = wm->mKey ? wm->mKey->mColor : black; + PtrInfo *v = wm->mVal; + + // All non-null weak mapping maps, keys and values are + // roots (in the sense of WalkFromRoots) in the cycle + // collector graph, and thus should have been colored + // either black or white in ScanRoots(). + NS_ASSERTION(mColor != grey, "Uncolored weak map"); + NS_ASSERTION(kColor != grey, "Uncolored weak map key"); + NS_ASSERTION(v->mColor != grey, "Uncolored weak map value"); + + if (mColor == black && kColor == black && v->mColor != black) { + GraphWalker(ScanBlackVisitor(mWhiteNodeCount)).Walk(v); + anyChanged = true; + } + } + } while (anyChanged); +} + +void +nsCycleCollector::ScanRoots() +{ + mWhiteNodeCount = 0; + + // On the assumption that most nodes will be black, it's + // probably faster to use a GraphWalker than a + // NodePool::Enumerator. + GraphWalker(scanVisitor(mWhiteNodeCount)).WalkFromRoots(mGraph); + + ScanWeakMaps(); + +#ifdef DEBUG_CC + // Sanity check: scan should have colored all grey nodes black or + // white. So we ensure we have no grey nodes at this point. + NodePool::Enumerator etor(mGraph.mNodes); + while (!etor.IsDone()) + { + PtrInfo *pinfo = etor.GetNext(); + if (pinfo->mColor == grey) { + Fault("valid grey node after scanning", pinfo); + } + } +#endif +} + + +//////////////////////////////////////////////////////////////////////// +// Bacon & Rajan's |CollectWhite| routine, somewhat modified. +//////////////////////////////////////////////////////////////////////// + +bool +nsCycleCollector::CollectWhite(nsICycleCollectorListener *aListener) +{ + // Explanation of "somewhat modified": we have no way to collect the + // set of whites "all at once", we have to ask each of them to drop + // their outgoing links and assume this will cause the garbage cycle + // to *mostly* self-destruct (except for the reference we continue + // to hold). + // + // To do this "safely" we must make sure that the white nodes we're + // operating on are stable for the duration of our operation. So we + // make 3 sets of calls to language runtimes: + // + // - Root(whites), which should pin the whites in memory. + // - Unlink(whites), which drops outgoing links on each white. + // - Unroot(whites), which returns the whites to normal GC. + + nsresult rv; + + NS_ASSERTION(mWhiteNodes->IsEmpty(), + "FinishCollection wasn't called?"); + + mWhiteNodes->SetCapacity(mWhiteNodeCount); + + NodePool::Enumerator etor(mGraph.mNodes); + while (!etor.IsDone()) + { + PtrInfo *pinfo = etor.GetNext(); + if (pinfo->mColor == white && mWhiteNodes->AppendElement(pinfo)) { + rv = pinfo->mParticipant->Root(pinfo->mPointer); + if (NS_FAILED(rv)) { + Fault("Failed root call while unlinking", pinfo); + mWhiteNodes->RemoveElementAt(mWhiteNodes->Length() - 1); + } + } + } + +#if defined(DEBUG_CC) && !defined(__MINGW32__) && defined(WIN32) + struct _CrtMemState ms1, ms2; + _CrtMemCheckpoint(&ms1); +#endif + + PRUint32 i, count = mWhiteNodes->Length(); + + if (aListener) { + for (i = 0; i < count; ++i) { + PtrInfo *pinfo = mWhiteNodes->ElementAt(i); + aListener->DescribeGarbage((PRUint64)pinfo->mPointer); + } + aListener->End(); + } + + for (i = 0; i < count; ++i) { + PtrInfo *pinfo = mWhiteNodes->ElementAt(i); + rv = pinfo->mParticipant->Unlink(pinfo->mPointer); + if (NS_FAILED(rv)) { + Fault("Failed unlink call while unlinking", pinfo); +#ifdef DEBUG_CC + mStats.mFailedUnlink++; +#endif + } + else { +#ifdef DEBUG_CC + ++mStats.mCollectedNode; +#endif + } + } + + for (i = 0; i < count; ++i) { + PtrInfo *pinfo = mWhiteNodes->ElementAt(i); + rv = pinfo->mParticipant->Unroot(pinfo->mPointer); + if (NS_FAILED(rv)) + Fault("Failed unroot call while unlinking", pinfo); + } + +#if defined(DEBUG_CC) && !defined(__MINGW32__) && defined(WIN32) + _CrtMemCheckpoint(&ms2); + if (ms2.lTotalCount < ms1.lTotalCount) + mStats.mFreedBytes += (ms1.lTotalCount - ms2.lTotalCount); +#endif + + mCollectedObjects += count; + return count > 0; +} + + +#ifdef DEBUG_CC +//////////////////////////////////////////////////////////////////////// +// Memory-hooking stuff +// When debugging wild pointers, it sometimes helps to hook malloc and +// free. This stuff is disabled unless you set an environment variable. +//////////////////////////////////////////////////////////////////////// + +static bool hookedMalloc = false; + +#if defined(__GLIBC__) && !defined(__UCLIBC__) +#include + +static void* (*old_memalign_hook)(size_t, size_t, const void *); +static void* (*old_realloc_hook)(void *, size_t, const void *); +static void* (*old_malloc_hook)(size_t, const void *); +static void (*old_free_hook)(void *, const void *); + +static void* my_memalign_hook(size_t, size_t, const void *); +static void* my_realloc_hook(void *, size_t, const void *); +static void* my_malloc_hook(size_t, const void *); +static void my_free_hook(void *, const void *); + +static inline void +install_old_hooks() +{ + __memalign_hook = old_memalign_hook; + __realloc_hook = old_realloc_hook; + __malloc_hook = old_malloc_hook; + __free_hook = old_free_hook; +} + +static inline void +save_old_hooks() +{ + // Glibc docs recommend re-saving old hooks on + // return from recursive calls. Strangely when + // we do this, we find ourselves in infinite + // recursion. + + // old_memalign_hook = __memalign_hook; + // old_realloc_hook = __realloc_hook; + // old_malloc_hook = __malloc_hook; + // old_free_hook = __free_hook; +} + +static inline void +install_new_hooks() +{ + __memalign_hook = my_memalign_hook; + __realloc_hook = my_realloc_hook; + __malloc_hook = my_malloc_hook; + __free_hook = my_free_hook; +} + +static void* +my_realloc_hook(void *ptr, size_t size, const void *caller) +{ + void *result; + + install_old_hooks(); + result = realloc(ptr, size); + save_old_hooks(); + + if (sCollector) { + sCollector->Freed(ptr); + sCollector->Allocated(result, size); + } + + install_new_hooks(); + + return result; +} + + +static void* +my_memalign_hook(size_t size, size_t alignment, const void *caller) +{ + void *result; + + install_old_hooks(); + result = memalign(size, alignment); + save_old_hooks(); + + if (sCollector) + sCollector->Allocated(result, size); + + install_new_hooks(); + + return result; +} + + +static void +my_free_hook (void *ptr, const void *caller) +{ + install_old_hooks(); + free(ptr); + save_old_hooks(); + + if (sCollector) + sCollector->Freed(ptr); + + install_new_hooks(); +} + + +static void* +my_malloc_hook (size_t size, const void *caller) +{ + void *result; + + install_old_hooks(); + result = malloc (size); + save_old_hooks(); + + if (sCollector) + sCollector->Allocated(result, size); + + install_new_hooks(); + + return result; +} + + +static void +InitMemHook(void) +{ + if (!hookedMalloc) { + save_old_hooks(); + install_new_hooks(); + hookedMalloc = true; + } +} + +#elif defined(WIN32) +#ifndef __MINGW32__ + +static int +AllocHook(int allocType, void *userData, size_t size, int + blockType, long requestNumber, const unsigned char *filename, int + lineNumber) +{ + if (allocType == _HOOK_FREE) + sCollector->Freed(userData); + return 1; +} + + +static void InitMemHook(void) +{ + if (!hookedMalloc) { + _CrtSetAllocHook (AllocHook); + hookedMalloc = true; + } +} +#endif // __MINGW32__ + +#elif 0 // defined(XP_MACOSX) + +#include + +static void (*old_free)(struct _malloc_zone_t *zone, void *ptr); + +static void +freehook(struct _malloc_zone_t *zone, void *ptr) +{ + if (sCollector) + sCollector->Freed(ptr); + old_free(zone, ptr); +} + + +static void +InitMemHook(void) +{ + if (!hookedMalloc) { + malloc_zone_t *default_zone = malloc_default_zone(); + old_free = default_zone->free; + default_zone->free = freehook; + hookedMalloc = true; + } +} + + +#else + +static void +InitMemHook(void) +{ +} + +#endif // GLIBC / WIN32 / OSX +#endif // DEBUG_CC + +//////////////////////////////////////////////////////////////////////// +// Collector implementation +//////////////////////////////////////////////////////////////////////// + +nsCycleCollector::nsCycleCollector() : + mCollectionInProgress(false), + mScanInProgress(false), + mCollectedObjects(0), + mWhiteNodes(nsnull), + mWhiteNodeCount(0), + mVisitedRefCounted(0), + mVisitedGCed(0), +#ifdef DEBUG_CC + mPurpleBuf(mParams, mStats), + mPtrLog(nsnull) +#else + mPurpleBuf(mParams) +#endif +{ +#ifdef DEBUG_CC + mExpectedGarbage.Init(); +#endif + + memset(mRuntimes, 0, sizeof(mRuntimes)); + mRuntimes[nsIProgrammingLanguage::CPLUSPLUS] = &mXPCOMRuntime; +} + + +nsCycleCollector::~nsCycleCollector() +{ +} + + +void +nsCycleCollector::RegisterRuntime(PRUint32 langID, + nsCycleCollectionLanguageRuntime *rt) +{ + if (mParams.mDoNothing) + return; + + if (langID > nsIProgrammingLanguage::MAX) + Fault("unknown language runtime in registration"); + + if (mRuntimes[langID]) + Fault("multiple registrations of language runtime", rt); + + mRuntimes[langID] = rt; +} + +nsCycleCollectionLanguageRuntime * +nsCycleCollector::GetRuntime(PRUint32 langID) +{ + if (langID > nsIProgrammingLanguage::MAX) + return nsnull; + + return mRuntimes[langID]; +} + +void +nsCycleCollector::ForgetRuntime(PRUint32 langID) +{ + if (mParams.mDoNothing) + return; + + if (langID > nsIProgrammingLanguage::MAX) + Fault("unknown language runtime in deregistration"); + + if (! mRuntimes[langID]) + Fault("forgetting non-registered language runtime"); + + mRuntimes[langID] = nsnull; +} + +#ifdef DEBUG_CC + +class Suppressor : + public nsCycleCollectionTraversalCallback +{ +protected: + static char *sSuppressionList; + static bool sInitialized; + bool mSuppressThisNode; +public: + Suppressor() + { + } + + bool shouldSuppress(nsISupports *s) + { + if (!sInitialized) { + sSuppressionList = PR_GetEnv("XPCOM_CC_SUPPRESS"); + sInitialized = true; + } + if (sSuppressionList == nsnull) { + mSuppressThisNode = false; + } else { + nsresult rv; + nsXPCOMCycleCollectionParticipant *cp; + rv = CallQueryInterface(s, &cp); + if (NS_FAILED(rv)) { + Fault("checking suppression on wrong type of pointer", s); + return true; + } + cp->Traverse(s, *this); + } + return mSuppressThisNode; + } + + NS_IMETHOD_(void) DescribeRefCountedNode(nsrefcnt refCount, size_t objSz, + const char *objName) + { + mSuppressThisNode = (PL_strstr(sSuppressionList, objName) != nsnull); + } + + NS_IMETHOD_(void) DescribeGCedNode(bool isMarked, size_t objSz, + const char *objName) + { + mSuppressThisNode = (PL_strstr(sSuppressionList, objName) != nsnull); + } + + NS_IMETHOD_(void) NoteXPCOMRoot(nsISupports *root) {}; + NS_IMETHOD_(void) NoteRoot(PRUint32 langID, void *root, + nsCycleCollectionParticipant* participant) {}; + NS_IMETHOD_(void) NoteXPCOMChild(nsISupports *child) {} + NS_IMETHOD_(void) NoteScriptChild(PRUint32 langID, void *child) {} + NS_IMETHOD_(void) NoteNativeChild(void *child, + nsCycleCollectionParticipant *participant) {} + NS_IMETHOD_(void) NoteNextEdgeName(const char* name) {} + NS_IMETHOD_(void) NoteWeakMapping(void *map, void *key, void *val) {} +}; + +char *Suppressor::sSuppressionList = nsnull; +bool Suppressor::sInitialized = false; + +static bool +nsCycleCollector_shouldSuppress(nsISupports *s) +{ + Suppressor supp; + return supp.shouldSuppress(s); +} +#endif + +#ifdef DEBUG +static bool +nsCycleCollector_isScanSafe(nsISupports *s) +{ + if (!s) + return false; + + nsXPCOMCycleCollectionParticipant *cp; + ToParticipant(s, &cp); + + return cp != nsnull; +} +#endif + +bool +nsCycleCollector::Suspect(nsISupports *n) +{ + AbortIfOffMainThreadIfCheckFast(); + + // Re-entering ::Suspect during collection used to be a fault, but + // we are canonicalizing nsISupports pointers using QI, so we will + // see some spurious refcount traffic here. + + if (mScanInProgress) + return false; + + NS_ASSERTION(nsCycleCollector_isScanSafe(n), + "suspected a non-scansafe pointer"); + + if (mParams.mDoNothing) + return false; + +#ifdef DEBUG_CC + mStats.mSuspectNode++; + + if (nsCycleCollector_shouldSuppress(n)) + return false; + +#ifndef __MINGW32__ + if (mParams.mHookMalloc) + InitMemHook(); +#endif + + if (mParams.mLogPointers) { + if (!mPtrLog) + mPtrLog = fopen("pointer_log", "w"); + fprintf(mPtrLog, "S %p\n", static_cast(n)); + } +#endif + + return mPurpleBuf.PutCompatObject(n); +} + + +bool +nsCycleCollector::Forget(nsISupports *n) +{ + AbortIfOffMainThreadIfCheckFast(); + + // Re-entering ::Forget during collection used to be a fault, but + // we are canonicalizing nsISupports pointers using QI, so we will + // see some spurious refcount traffic here. + + if (mScanInProgress) + return false; + + if (mParams.mDoNothing) + return true; // it's as good as forgotten + +#ifdef DEBUG_CC + mStats.mForgetNode++; + +#ifndef __MINGW32__ + if (mParams.mHookMalloc) + InitMemHook(); +#endif + + if (mParams.mLogPointers) { + if (!mPtrLog) + mPtrLog = fopen("pointer_log", "w"); + fprintf(mPtrLog, "F %p\n", static_cast(n)); + } +#endif + + mPurpleBuf.RemoveCompatObject(n); + return true; +} + +nsPurpleBufferEntry* +nsCycleCollector::Suspect2(nsISupports *n) +{ + AbortIfOffMainThreadIfCheckFast(); + + // Re-entering ::Suspect during collection used to be a fault, but + // we are canonicalizing nsISupports pointers using QI, so we will + // see some spurious refcount traffic here. + + if (mScanInProgress) + return nsnull; + + NS_ASSERTION(nsCycleCollector_isScanSafe(n), + "suspected a non-scansafe pointer"); + + if (mParams.mDoNothing) + return nsnull; + +#ifdef DEBUG_CC + mStats.mSuspectNode++; + + if (nsCycleCollector_shouldSuppress(n)) + return nsnull; + +#ifndef __MINGW32__ + if (mParams.mHookMalloc) + InitMemHook(); +#endif + + if (mParams.mLogPointers) { + if (!mPtrLog) + mPtrLog = fopen("pointer_log", "w"); + fprintf(mPtrLog, "S %p\n", static_cast(n)); + } +#endif + + // Caller is responsible for filling in result's mRefCnt. + return mPurpleBuf.Put(n); +} + + +bool +nsCycleCollector::Forget2(nsPurpleBufferEntry *e) +{ + AbortIfOffMainThreadIfCheckFast(); + + // Re-entering ::Forget during collection used to be a fault, but + // we are canonicalizing nsISupports pointers using QI, so we will + // see some spurious refcount traffic here. + + if (mScanInProgress) + return false; + +#ifdef DEBUG_CC + mStats.mForgetNode++; + +#ifndef __MINGW32__ + if (mParams.mHookMalloc) + InitMemHook(); +#endif + + if (mParams.mLogPointers) { + if (!mPtrLog) + mPtrLog = fopen("pointer_log", "w"); + fprintf(mPtrLog, "F %p\n", static_cast(e->mObject)); + } +#endif + + mPurpleBuf.Remove(e); + return true; +} + +#ifdef DEBUG_CC +void +nsCycleCollector::Allocated(void *n, size_t sz) +{ +} + +void +nsCycleCollector::Freed(void *n) +{ + mStats.mFreeCalls++; + + if (!n) { + // Ignore null pointers coming through + return; + } + + if (mPurpleBuf.Exists(n)) { + mStats.mForgetNode++; + mStats.mFreedWhilePurple++; + Fault("freed while purple", n); + + if (mParams.mLogPointers) { + if (!mPtrLog) + mPtrLog = fopen("pointer_log", "w"); + fprintf(mPtrLog, "R %p\n", n); + } + } +} +#endif + +// The cycle collector uses the mark bitmap to discover what JS objects +// were reachable only from XPConnect roots that might participate in +// cycles. We ask the JS runtime whether we need to force a GC before +// this CC. It returns true on startup (before the mark bits have been set), +// and also when UnmarkGray has run out of stack. We also force GCs on shut +// down to collect cycles involving both DOM and JS. +void +nsCycleCollector::GCIfNeeded(bool aForceGC) +{ + NS_ASSERTION(NS_IsMainThread(), + "nsCycleCollector::GCIfNeeded() must be called on the main thread."); + + if (mParams.mDoNothing) + return; + + if (!mRuntimes[nsIProgrammingLanguage::JAVASCRIPT]) + return; + + nsCycleCollectionJSRuntime* rt = + static_cast + (mRuntimes[nsIProgrammingLanguage::JAVASCRIPT]); + if (!rt->NeedCollect() && !aForceGC) + return; + +#ifdef COLLECT_TIME_DEBUG + PRTime start = PR_Now(); +#endif + // rt->Collect() must be called from the main thread, + // because it invokes XPCJSRuntime::GCCallback(cx, JSGC_BEGIN) + // which returns false if not in the main thread. + rt->Collect(); +#ifdef COLLECT_TIME_DEBUG + printf("cc: GC() took %lldms\n", (PR_Now() - start) / PR_USEC_PER_MSEC); +#endif +} + +bool +nsCycleCollector::PrepareForCollection(nsTArray *aWhiteNodes) +{ +#if defined(DEBUG_CC) && !defined(__MINGW32__) + if (!mParams.mDoNothing && mParams.mHookMalloc) + InitMemHook(); +#endif + + // This can legitimately happen in a few cases. See bug 383651. + if (mCollectionInProgress) + return false; + + NS_TIME_FUNCTION; + +#ifdef COLLECT_TIME_DEBUG + printf("cc: nsCycleCollector::PrepareForCollection()\n"); +#endif + mCollectionStart = TimeStamp::Now(); + mVisitedRefCounted = 0; + mVisitedGCed = 0; + + mCollectionInProgress = true; + + nsCOMPtr obs = + mozilla::services::GetObserverService(); + if (obs) + obs->NotifyObservers(nsnull, "cycle-collector-begin", nsnull); + + mFollowupCollection = false; + mCollectedObjects = 0; + + mWhiteNodes = aWhiteNodes; + + return true; +} + +void +nsCycleCollector::CleanupAfterCollection() +{ + mWhiteNodes = nsnull; + mCollectionInProgress = false; + +#ifdef XP_OS2 + // Now that the cycle collector has freed some memory, we can try to + // force the C library to give back as much memory to the system as + // possible. + _heapmin(); +#endif + + PRUint32 interval((TimeStamp::Now() - mCollectionStart).ToMilliseconds()); +#ifdef COLLECT_TIME_DEBUG + printf("cc: CleanupAfterCollection(), total time %ums\n", interval); +#endif + Telemetry::Accumulate(Telemetry::CYCLE_COLLECTOR, interval); + Telemetry::Accumulate(Telemetry::CYCLE_COLLECTOR_VISITED_REF_COUNTED, mVisitedRefCounted); + Telemetry::Accumulate(Telemetry::CYCLE_COLLECTOR_VISITED_GCED, mVisitedGCed); + Telemetry::Accumulate(Telemetry::CYCLE_COLLECTOR_COLLECTED, mWhiteNodeCount); + +#ifdef DEBUG_CC + ExplainLiveExpectedGarbage(); +#endif +} + +PRUint32 +nsCycleCollector::Collect(PRUint32 aTryCollections, + nsICycleCollectorListener *aListener) +{ + nsAutoTArray whiteNodes; + + if (!PrepareForCollection(&whiteNodes)) + return 0; + + PRUint32 totalCollections = 0; + while (aTryCollections > totalCollections) { + // Synchronous cycle collection. Always force a JS GC beforehand. + GCIfNeeded(true); + if (aListener && NS_FAILED(aListener->Begin())) + aListener = nsnull; + if (!(BeginCollection(aListener) && + FinishCollection(aListener))) + break; + + ++totalCollections; + } + + CleanupAfterCollection(); + + return mCollectedObjects; +} + +bool +nsCycleCollector::BeginCollection(nsICycleCollectorListener *aListener) +{ + // aListener should be Begin()'d before this + if (mParams.mDoNothing) + return false; + + GCGraphBuilder builder(mGraph, mRuntimes, aListener); + if (!builder.Initialized()) + return false; + +#ifdef COLLECT_TIME_DEBUG + PRTime now = PR_Now(); +#endif + for (PRUint32 i = 0; i <= nsIProgrammingLanguage::MAX; ++i) { + if (mRuntimes[i]) + mRuntimes[i]->BeginCycleCollection(builder, false); + } + +#ifdef COLLECT_TIME_DEBUG + printf("cc: mRuntimes[*]->BeginCycleCollection() took %lldms\n", + (PR_Now() - now) / PR_USEC_PER_MSEC); + + now = PR_Now(); +#endif + +#ifdef DEBUG_CC + PRUint32 purpleStart = builder.Count(); +#endif + mScanInProgress = true; + SelectPurple(builder); +#ifdef DEBUG_CC + PRUint32 purpleEnd = builder.Count(); + + if (purpleStart != purpleEnd) { +#ifndef __MINGW32__ + if (mParams.mHookMalloc) + InitMemHook(); +#endif + if (mParams.mLogPointers && !mPtrLog) + mPtrLog = fopen("pointer_log", "w"); + + PRUint32 i = 0; + NodePool::Enumerator queue(mGraph.mNodes); + while (i++ < purpleStart) { + queue.GetNext(); + } + while (i++ < purpleEnd) { + mStats.mForgetNode++; + if (mParams.mLogPointers) + fprintf(mPtrLog, "F %p\n", queue.GetNext()->mPointer); + } + } +#endif + +#ifdef COLLECT_TIME_DEBUG + printf("cc: SelectPurple() took %lldms\n", + (PR_Now() - now) / PR_USEC_PER_MSEC); +#endif + + if (builder.Count() > 0) { + // The main Bacon & Rajan collection algorithm. + +#ifdef COLLECT_TIME_DEBUG + now = PR_Now(); +#endif + + MarkRoots(builder); + +#ifdef COLLECT_TIME_DEBUG + { + PRTime then = PR_Now(); + printf("cc: MarkRoots() took %lldms\n", + (then - now) / PR_USEC_PER_MSEC); + now = then; + } +#endif + + ScanRoots(); + +#ifdef COLLECT_TIME_DEBUG + printf("cc: ScanRoots() took %lldms\n", + (PR_Now() - now) / PR_USEC_PER_MSEC); +#endif + + mScanInProgress = false; + + if (aListener) { + aListener->BeginResults(); + + NodePool::Enumerator etor(mGraph.mNodes); + while (!etor.IsDone()) { + PtrInfo *pi = etor.GetNext(); + if (pi->mColor == black && + pi->mRefCount > 0 && pi->mRefCount < PR_UINT32_MAX && + pi->mInternalRefs != pi->mRefCount) { + aListener->DescribeRoot((PRUint64)pi->mPointer, + pi->mInternalRefs); + } + } + } + +#ifdef DEBUG_CC + if (mFollowupCollection && purpleStart != purpleEnd) { + PRUint32 i = 0; + NodePool::Enumerator queue(mGraph.mNodes); + while (i++ < purpleStart) { + queue.GetNext(); + } + while (i++ < purpleEnd) { + PtrInfo *pi = queue.GetNext(); + if (pi->mColor == white) { + printf("nsCycleCollector: a later shutdown collection collected the additional\n" + " suspect %p %s\n" + " (which could be fixed by improving traversal)\n", + pi->mPointer, pi->mName); + } + } + } +#endif + + for (PRUint32 i = 0; i <= nsIProgrammingLanguage::MAX; ++i) { + if (mRuntimes[i]) + mRuntimes[i]->FinishTraverse(); + } + } + else { + mScanInProgress = false; + } + + return true; +} + +bool +nsCycleCollector::FinishCollection(nsICycleCollectorListener *aListener) +{ +#ifdef COLLECT_TIME_DEBUG + PRTime now = PR_Now(); +#endif + + bool collected = CollectWhite(aListener); + +#ifdef COLLECT_TIME_DEBUG + printf("cc: CollectWhite() took %lldms\n", + (PR_Now() - now) / PR_USEC_PER_MSEC); +#endif + +#ifdef DEBUG_CC + mStats.mCollection++; + if (mParams.mReportStats) + mStats.Dump(); +#endif + + for (PRUint32 i = 0; i <= nsIProgrammingLanguage::MAX; ++i) { + if (mRuntimes[i]) + mRuntimes[i]->FinishCycleCollection(); + } + + mFollowupCollection = true; + +#ifdef DEBUG_CC + // We wait until after FinishCollection to check the white nodes because + // some objects may outlive CollectWhite but then be freed by + // FinishCycleCollection (like XPConnect's deferred release of native + // objects). + PRUint32 i, count = mWhiteNodes->Length(); + for (i = 0; i < count; ++i) { + PtrInfo *pinfo = mWhiteNodes->ElementAt(i); + if (pinfo->mLangID == nsIProgrammingLanguage::CPLUSPLUS && + mPurpleBuf.Exists(pinfo->mPointer)) { + printf("nsCycleCollector: %s object @%p is still alive after\n" + " calling RootAndUnlinkJSObjects, Unlink, and Unroot on" + " it! This probably\n" + " means the Unlink implementation was insufficient.\n", + pinfo->mName, pinfo->mPointer); + } + } +#endif + + mWhiteNodes->Clear(); + ClearGraph(); + + mParams.mDoNothing = false; + + return collected; +} + +PRUint32 +nsCycleCollector::SuspectedCount() +{ + return mPurpleBuf.Count(); +} + +void +nsCycleCollector::Shutdown() +{ + // Here we want to run a final collection and then permanently + // disable the collector because the program is shutting down. + + nsCOMPtr listener; + if (mParams.mLogGraphs) { + listener = new nsCycleCollectorLogger(); + } + Collect(SHUTDOWN_COLLECTIONS(mParams), listener); + +#ifdef DEBUG_CC + GCGraphBuilder builder(mGraph, mRuntimes, nsnull); + mScanInProgress = true; + SelectPurple(builder); + mScanInProgress = false; + if (builder.Count() != 0) { + printf("Might have been able to release more cycles if the cycle collector would " + "run once more at shutdown.\n"); + } + ClearGraph(); +#endif + mParams.mDoNothing = true; +} + +#ifdef DEBUG_CC + +static PLDHashOperator +AddExpectedGarbage(nsVoidPtrHashKey *p, void *arg) +{ + GCGraphBuilder *builder = static_cast(arg); + nsISupports *root = + static_cast(const_cast(p->GetKey())); + builder->NoteXPCOMRoot(root); + return PL_DHASH_NEXT; +} + +struct SetSCCVisitor +{ + SetSCCVisitor(PRUint32 aIndex) : mIndex(aIndex) {} + bool ShouldVisitNode(PtrInfo const *pi) { return pi->mSCCIndex == 0; } + void VisitNode(PtrInfo *pi) { pi->mSCCIndex = mIndex; } +private: + PRUint32 mIndex; +}; + +struct SetNonRootGreyVisitor +{ + bool ShouldVisitNode(PtrInfo const *pi) { return pi->mColor == white; } + void VisitNode(PtrInfo *pi) { pi->mColor = grey; } +}; + +static void +PrintPathToExpectedGarbage(PtrInfo *pi) +{ + printf(" An object expected to be garbage could be " + "reached from it by the path:\n"); + for (PtrInfo *path = pi, *prev = nsnull; prev != path; + prev = path, + path = path->mShortestPathToExpectedGarbage) { + if (prev) { + nsCString *edgeName = prev + ->mShortestPathToExpectedGarbageEdgeName; + printf(" via %s\n", + edgeName->IsEmpty() ? "" + : edgeName->get()); + } + printf(" %s %p\n", path->mName, path->mPointer); + } +} + +void +nsCycleCollector::ExplainLiveExpectedGarbage() +{ + if (mScanInProgress || mCollectionInProgress) + Fault("can't explain expected garbage during collection itself"); + + if (mParams.mDoNothing) { + printf("nsCycleCollector: not explaining expected garbage since\n" + " cycle collection disabled\n"); + return; + } + + mCollectionInProgress = true; + mScanInProgress = true; + + { + GCGraphBuilder builder(mGraph, mRuntimes, nsnull); + + // Instead of adding roots from the purple buffer, we add them + // from the list of nodes we were expected to collect. + // Put the expected garbage in *before* calling + // BeginCycleCollection so that we can separate the expected + // garbage from the NoteRoot calls in such a way that something + // that's in both is considered expected garbage. + mExpectedGarbage.EnumerateEntries(&AddExpectedGarbage, &builder); + + PRUint32 expectedGarbageCount = builder.Count(); + + for (PRUint32 i = 0; i <= nsIProgrammingLanguage::MAX; ++i) { + if (mRuntimes[i]) + mRuntimes[i]->BeginCycleCollection(builder, true); + } + + // But just for extra information, add entries from the purple + // buffer too, since it may give us extra information about + // traversal deficiencies. + mPurpleBuf.NoteAll(builder); + + MarkRoots(builder); + ScanRoots(); + + mScanInProgress = false; + + for (PRUint32 i = 0; i <= nsIProgrammingLanguage::MAX; ++i) { + if (mRuntimes[i]) { + mRuntimes[i]->FinishTraverse(); + } + } + + bool describeExtraRefcounts = false; + bool findCycleRoots = false; + { + NodePool::Enumerator queue(mGraph.mNodes); + PRUint32 i = 0; + while (!queue.IsDone()) { + PtrInfo *pi = queue.GetNext(); + if (pi->mColor == white) { + findCycleRoots = true; + } + + if (pi->mInternalRefs != pi->mRefCount && + (i < expectedGarbageCount || i >= mGraph.mRootCount)) { + // This check isn't particularly useful anymore + // given that we need to enter this part for i >= + // mGraph.mRootCount and there are plenty of + // NoteRoot roots. + describeExtraRefcounts = true; + } + ++i; + } + } + + if ((describeExtraRefcounts || findCycleRoots) && + CreateReversedEdges()) { + // Note that the external references may have been external + // to a different node in the cycle collection that just + // happened, if that different node was purple and then + // black. + + // Use mSCCIndex temporarily to track whether we've reached + // nodes in the breadth-first search. + const PRUint32 INDEX_UNREACHED = 0; + const PRUint32 INDEX_REACHED = 1; + NodePool::Enumerator etor_clear(mGraph.mNodes); + while (!etor_clear.IsDone()) { + PtrInfo *pi = etor_clear.GetNext(); + pi->mSCCIndex = INDEX_UNREACHED; + } + + nsDeque queue; // for breadth-first search + NodePool::Enumerator etor_roots(mGraph.mNodes); + for (PRUint32 i = 0; i < mGraph.mRootCount; ++i) { + PtrInfo *root_pi = etor_roots.GetNext(); + if (i < expectedGarbageCount) { + root_pi->mSCCIndex = INDEX_REACHED; + root_pi->mShortestPathToExpectedGarbage = root_pi; + queue.Push(root_pi); + } + } + + while (queue.GetSize() > 0) { + PtrInfo *pi = (PtrInfo*)queue.PopFront(); + for (ReversedEdge *e = pi->mReversedEdges; e; e = e->mNext) { + if (e->mTarget->mSCCIndex == INDEX_UNREACHED) { + e->mTarget->mSCCIndex = INDEX_REACHED; + PtrInfo *target = e->mTarget; + if (!target->mShortestPathToExpectedGarbage) { + target->mShortestPathToExpectedGarbage = pi; + target->mShortestPathToExpectedGarbageEdgeName = + e->mEdgeName; + } + queue.Push(target); + } + } + + if (pi->mRefCount == PR_UINT32_MAX || + (pi->mInternalRefs != pi->mRefCount && pi->mRefCount > 0)) { + if (pi->mRefCount == PR_UINT32_MAX) { + printf("nsCycleCollector: %s %p was not collected due " + "to \n" + " external references\n", + pi->mName, pi->mPointer); + } + else { + printf("nsCycleCollector: %s %p was not collected due " + "to %d\n" + " external references (%d total - %d known)\n", + pi->mName, pi->mPointer, + pi->mRefCount - pi->mInternalRefs, + pi->mRefCount, pi->mInternalRefs); + } + + PrintPathToExpectedGarbage(pi); + + if (pi->mRefCount == PR_UINT32_MAX) { + printf(" The known references to it were from:\n"); + } + else { + printf(" The %d known references to it were from:\n", + pi->mInternalRefs); + } + for (ReversedEdge *e = pi->mReversedEdges; + e; e = e->mNext) { + printf(" %s %p", + e->mTarget->mName, e->mTarget->mPointer); + if (!e->mEdgeName->IsEmpty()) { + printf(" via %s", e->mEdgeName->get()); + } + printf("\n"); + } + mRuntimes[pi->mLangID]->PrintAllReferencesTo(pi->mPointer); + } + } + + if (findCycleRoots) { + // NOTE: This code changes the white nodes that are not + // roots to gray. + + // Put the nodes in post-order traversal order from a + // depth-first search. + nsDeque DFSPostOrder; + + { + // Use mSCCIndex temporarily to track the DFS numbering: + const PRUint32 INDEX_UNREACHED = 0; + const PRUint32 INDEX_TRAVERSING = 1; + const PRUint32 INDEX_NUMBERED = 2; + + NodePool::Enumerator etor_clear(mGraph.mNodes); + while (!etor_clear.IsDone()) { + PtrInfo *pi = etor_clear.GetNext(); + pi->mSCCIndex = INDEX_UNREACHED; + } + + nsDeque stack; + + NodePool::Enumerator etor_roots(mGraph.mNodes); + for (PRUint32 i = 0; i < mGraph.mRootCount; ++i) { + PtrInfo *root_pi = etor_roots.GetNext(); + stack.Push(root_pi); + } + + while (stack.GetSize() > 0) { + PtrInfo *pi = (PtrInfo*)stack.Peek(); + if (pi->mSCCIndex == INDEX_UNREACHED) { + pi->mSCCIndex = INDEX_TRAVERSING; + for (EdgePool::Iterator child = pi->FirstChild(), + child_end = pi->LastChild(); + child != child_end; ++child) { + stack.Push(*child); + } + } else { + stack.Pop(); + // Somebody else might have numbered it already + // (since this is depth-first, not breadth-first). + // This happens if a node is pushed on the stack + // a second time while it is on the stack in + // UNREACHED state. + if (pi->mSCCIndex == INDEX_TRAVERSING) { + pi->mSCCIndex = INDEX_NUMBERED; + DFSPostOrder.Push(pi); + } + } + } + } + + // Put the nodes into strongly-connected components. + { + NodePool::Enumerator etor_clear(mGraph.mNodes); + while (!etor_clear.IsDone()) { + PtrInfo *pi = etor_clear.GetNext(); + pi->mSCCIndex = 0; + } + + PRUint32 currentSCC = 1; + + while (DFSPostOrder.GetSize() > 0) { + GraphWalker(SetSCCVisitor(currentSCC)).Walk((PtrInfo*)DFSPostOrder.PopFront()); + ++currentSCC; + } + } + + // Mark any white nodes reachable from other components as + // grey. + { + NodePool::Enumerator queue(mGraph.mNodes); + while (!queue.IsDone()) { + PtrInfo *pi = queue.GetNext(); + if (pi->mColor != white) + continue; + for (EdgePool::Iterator child = pi->FirstChild(), + child_end = pi->LastChild(); + child != child_end; ++child) { + if ((*child)->mSCCIndex != pi->mSCCIndex) { + GraphWalker(SetNonRootGreyVisitor()).Walk(*child); + } + } + } + } + + { + NodePool::Enumerator queue(mGraph.mNodes); + while (!queue.IsDone()) { + PtrInfo *pi = queue.GetNext(); + if (pi->mColor == white) { + if (pi->mLangID == + nsIProgrammingLanguage::CPLUSPLUS && + mPurpleBuf.Exists(pi->mPointer)) { + printf( +"nsCycleCollector: %s %p in component %d\n" +" which was reference counted during the root/unlink/unroot phase of the\n" +" last collection was not collected due to failure to unlink (see other\n" +" warnings) or deficiency in traverse that causes cycles referenced only\n" +" from other cycles to require multiple rounds of cycle collection in which\n" +" this object was likely the reachable object\n", + pi->mName, pi->mPointer, pi->mSCCIndex); + } else { + printf( +"nsCycleCollector: %s %p in component %d\n" +" was not collected due to missing call to suspect, failure to unlink (see\n" +" other warnings), or deficiency in traverse that causes cycles referenced\n" +" only from other cycles to require multiple rounds of cycle collection\n", + pi->mName, pi->mPointer, pi->mSCCIndex); + } + if (pi->mShortestPathToExpectedGarbage) + PrintPathToExpectedGarbage(pi); + } + } + } + } + + DestroyReversedEdges(); + } + } + + ClearGraph(); + + mCollectionInProgress = false; + + for (PRUint32 i = 0; i <= nsIProgrammingLanguage::MAX; ++i) { + if (mRuntimes[i]) + mRuntimes[i]->FinishCycleCollection(); + } +} + +bool +nsCycleCollector::CreateReversedEdges() +{ + // Count the edges in the graph. + PRUint32 edgeCount = 0; + NodePool::Enumerator countQueue(mGraph.mNodes); + while (!countQueue.IsDone()) { + PtrInfo *pi = countQueue.GetNext(); + for (EdgePool::Iterator e = pi->FirstChild(), e_end = pi->LastChild(); + e != e_end; ++e, ++edgeCount) { + } + } + + // Allocate a pool to hold all of the edges. + mGraph.mReversedEdges = new ReversedEdge[edgeCount]; + if (mGraph.mReversedEdges == nsnull) { + NS_NOTREACHED("allocation failure creating reversed edges"); + return false; + } + + // Fill in the reversed edges by scanning all forward edges. + ReversedEdge *current = mGraph.mReversedEdges; + NodePool::Enumerator buildQueue(mGraph.mNodes); + while (!buildQueue.IsDone()) { + PtrInfo *pi = buildQueue.GetNext(); + PRInt32 i = 0; + for (EdgePool::Iterator e = pi->FirstChild(), e_end = pi->LastChild(); + e != e_end; ++e) { + current->mTarget = pi; + current->mEdgeName = &pi->mEdgeNames[i]; + current->mNext = (*e)->mReversedEdges; + (*e)->mReversedEdges = current; + ++current; + ++i; + } + } + NS_ASSERTION(current - mGraph.mReversedEdges == ptrdiff_t(edgeCount), + "misallocation"); + return true; +} + +void +nsCycleCollector::DestroyReversedEdges() +{ + NodePool::Enumerator queue(mGraph.mNodes); + while (!queue.IsDone()) { + PtrInfo *pi = queue.GetNext(); + pi->mReversedEdges = nsnull; + } + + delete mGraph.mReversedEdges; + mGraph.mReversedEdges = nsnull; +} + +void +nsCycleCollector::ShouldBeFreed(nsISupports *n) +{ + if (n) { + mExpectedGarbage.PutEntry(n); + } +} + +void +nsCycleCollector::WasFreed(nsISupports *n) +{ + if (n) { + mExpectedGarbage.RemoveEntry(n); + } +} +#endif + + +//////////////////////// +// Memory reporter +//////////////////////// + +static PRInt64 +ReportCycleCollectorMem() +{ + if (!sCollector) + return 0; + PRInt64 size = sizeof(nsCycleCollector) + + sCollector->mPurpleBuf.BlocksSize() + + sCollector->mGraph.BlocksSize(); + if (sCollector->mWhiteNodes) + size += sCollector->mWhiteNodes->Capacity() * sizeof(PtrInfo*); + return size; +} + +NS_MEMORY_REPORTER_IMPLEMENT(CycleCollector, + "explicit/cycle-collector", + KIND_HEAP, + UNITS_BYTES, + ReportCycleCollectorMem, + "Memory used by the cycle collector. This " + "includes the cycle collector structure, the " + "purple buffer, the graph, and the white nodes. " + "The latter two are expected to be empty when the " + "cycle collector is idle.") + + +//////////////////////////////////////////////////////////////////////// +// Module public API (exported in nsCycleCollector.h) +// Just functions that redirect into the singleton, once it's built. +//////////////////////////////////////////////////////////////////////// + +void +nsCycleCollector_registerRuntime(PRUint32 langID, + nsCycleCollectionLanguageRuntime *rt) +{ + static bool regMemReport = true; + if (sCollector) + sCollector->RegisterRuntime(langID, rt); + if (regMemReport) { + regMemReport = false; + NS_RegisterMemoryReporter(new NS_MEMORY_REPORTER_NAME(CycleCollector)); + } +} + +nsCycleCollectionLanguageRuntime * +nsCycleCollector_getRuntime(PRUint32 langID) +{ + if (sCollector) + sCollector->GetRuntime(langID); + return nsnull; +} + +void +nsCycleCollector_forgetRuntime(PRUint32 langID) +{ + if (sCollector) + sCollector->ForgetRuntime(langID); +} + + +bool +NS_CycleCollectorSuspect(nsISupports *n) +{ + if (sCollector) + return sCollector->Suspect(n); + return false; +} + +bool +NS_CycleCollectorForget(nsISupports *n) +{ + return sCollector ? sCollector->Forget(n) : true; +} + +nsPurpleBufferEntry* +NS_CycleCollectorSuspect2(nsISupports *n) +{ + if (sCollector) + return sCollector->Suspect2(n); + return nsnull; +} + +bool +NS_CycleCollectorForget2(nsPurpleBufferEntry *e) +{ + return sCollector ? sCollector->Forget2(e) : true; +} + +PRUint32 +nsCycleCollector_suspectedCount() +{ + return sCollector ? sCollector->SuspectedCount() : 0; +} + +#ifdef DEBUG +void +nsCycleCollector_DEBUG_shouldBeFreed(nsISupports *n) +{ +#ifdef DEBUG_CC + if (sCollector) + sCollector->ShouldBeFreed(n); +#endif +} + +void +nsCycleCollector_DEBUG_wasFreed(nsISupports *n) +{ +#ifdef DEBUG_CC + if (sCollector) + sCollector->WasFreed(n); +#endif +} +#endif + +class nsCycleCollectorRunner : public nsRunnable +{ + nsCycleCollector *mCollector; + nsICycleCollectorListener *mListener; + Mutex mLock; + CondVar mRequest; + CondVar mReply; + bool mRunning; + bool mShutdown; + bool mCollected; + + nsCycleCollectionJSRuntime *GetJSRuntime() + { + return static_cast + (mCollector->mRuntimes[nsIProgrammingLanguage::JAVASCRIPT]); + } + +public: + NS_IMETHOD Run() + { +#ifdef XP_WIN + TlsSetValue(gTLSThreadIDIndex, + (void*) mozilla::threads::CycleCollector); +#elif defined(NS_TLS) + gTLSThreadID = mozilla::threads::CycleCollector; +#else + gCycleCollectorThread = PR_GetCurrentThread(); +#endif + + NS_ASSERTION(NS_IsCycleCollectorThread() && !NS_IsMainThread(), + "Wrong thread!"); + + MutexAutoLock autoLock(mLock); + + if (mShutdown) + return NS_OK; + + mRunning = true; + + while (1) { + mRequest.Wait(); + + if (!mRunning) { + mReply.Notify(); + return NS_OK; + } + + GetJSRuntime()->NotifyEnterCycleCollectionThread(); + mCollected = mCollector->BeginCollection(mListener); + GetJSRuntime()->NotifyLeaveCycleCollectionThread(); + + mReply.Notify(); + } + + return NS_OK; + } + + nsCycleCollectorRunner(nsCycleCollector *collector) + : mCollector(collector), + mListener(nsnull), + mLock("cycle collector lock"), + mRequest(mLock, "cycle collector request condvar"), + mReply(mLock, "cycle collector reply condvar"), + mRunning(false), + mShutdown(false), + mCollected(false) + { + NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); + } + + PRUint32 Collect(nsICycleCollectorListener* aListener) + { + NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); + + mCollector->GCIfNeeded(false); + + MutexAutoLock autoLock(mLock); + + if (!mRunning) + return 0; + + nsAutoTArray whiteNodes; + if (!mCollector->PrepareForCollection(&whiteNodes)) + return 0; + + NS_ASSERTION(!mListener, "Should have cleared this already!"); + if (aListener && NS_FAILED(aListener->Begin())) + aListener = nsnull; + mListener = aListener; + + GetJSRuntime()->NotifyLeaveMainThread(); + mRequest.Notify(); + mReply.Wait(); + GetJSRuntime()->NotifyEnterMainThread(); + + mListener = nsnull; + + if (mCollected) { + mCollected = mCollector->FinishCollection(aListener); + + mCollector->CleanupAfterCollection(); + + return mCollected ? mCollector->mCollectedObjects : 0; + } + + return 0; + } + + void Shutdown() + { + NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); + + MutexAutoLock autoLock(mLock); + + mShutdown = true; + + if (!mRunning) + return; + + mRunning = false; + mRequest.Notify(); + mReply.Wait(); + } +}; + +// Holds a reference. +static nsCycleCollectorRunner* sCollectorRunner; + +// Holds a reference. +static nsIThread* sCollectorThread; + +nsresult +nsCycleCollector_startup() +{ + NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); + NS_ASSERTION(!sCollector, "Forgot to call nsCycleCollector_shutdown?"); + + sCollector = new nsCycleCollector(); + + nsRefPtr runner = + new nsCycleCollectorRunner(sCollector); + + nsCOMPtr thread; + nsresult rv = NS_NewThread(getter_AddRefs(thread), runner); + NS_ENSURE_SUCCESS(rv, rv); + + runner.swap(sCollectorRunner); + thread.swap(sCollectorThread); + + return rv; +} + +PRUint32 +nsCycleCollector_collect(nsICycleCollectorListener *aListener) +{ + NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); + nsCOMPtr listener(aListener); + if (!aListener && sCollector && sCollector->mParams.mLogGraphs) { + listener = new nsCycleCollectorLogger(); + } + + if (sCollectorRunner) + return sCollectorRunner->Collect(listener); + return sCollector ? sCollector->Collect(1, listener) : 0; +} + +void +nsCycleCollector_shutdownThreads() +{ + NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); + if (sCollectorRunner) { + nsRefPtr runner; + runner.swap(sCollectorRunner); + runner->Shutdown(); + } + + if (sCollectorThread) { + nsCOMPtr thread; + thread.swap(sCollectorThread); + thread->Shutdown(); + } +} + +void +nsCycleCollector_shutdown() +{ + NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); + NS_ASSERTION(!sCollectorRunner, "Should have finished before!"); + NS_ASSERTION(!sCollectorThread, "Should have finished before!"); + + if (sCollector) { + sCollector->Shutdown(); + delete sCollector; + sCollector = nsnull; + } +} diff --git a/tests/cpp/nsIOThreadPool.cpp b/tests/cpp/nsIOThreadPool.cpp new file mode 100644 index 0000000..36d2042 --- /dev/null +++ b/tests/cpp/nsIOThreadPool.cpp @@ -0,0 +1,310 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla. + * + * The Initial Developer of the Original Code is IBM Corporation. + * Portions created by IBM Corporation are Copyright (C) 2003 + * IBM Corporation. All Rights Reserved. + * + * Contributor(s): + * IBM Corp. + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#include "nsIEventTarget.h" +#include "nsIServiceManager.h" +#include "nsIObserverService.h" +#include "nsIObserver.h" +#include "nsAutoLock.h" +#include "nsCOMPtr.h" +#include "prclist.h" +#include "prlog.h" + +#if defined(PR_LOGGING) +// +// NSPR_LOG_MODULES=nsIOThreadPool:5 +// +static PRLogModuleInfo *gIOThreadPoolLog = nsnull; +#endif +#define LOG(args) PR_LOG(gIOThreadPoolLog, PR_LOG_DEBUG, args) + +// this number specifies the maximum number of threads. +#define MAX_THREADS 4 + +// this number specifies how long to wait before killing an idle thread. it's +// important to pick a large enough value here to minimize thread churn. +#define IDLE_TIMEOUT PR_SecondsToInterval(60) + +#define PLEVENT_FROM_LINK(_link) \ + ((PLEvent*) ((char*) (_link) - offsetof(PLEvent, link))) + +//----------------------------------------------------------------------------- +// pool of joinable threads used for general purpose i/o tasks +// +// the main entry point to this class is nsIEventTarget. events posted to +// the thread pool are dispatched on one of the threads. a variable number +// of threads are maintained. the threads die off if they remain idle for +// more than THREAD_IDLE_TIMEOUT. the thread pool shuts down when it receives +// the "xpcom-shutdown" event. +//----------------------------------------------------------------------------- + +class nsIOThreadPool : public nsIEventTarget + , public nsIObserver +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIEVENTTARGET + NS_DECL_NSIOBSERVER + + nsresult Init(); + void Shutdown(); + +private: + virtual ~nsIOThreadPool(); + + PR_STATIC_CALLBACK(void) ThreadFunc(void *); + + // mLock protects all (exceptions during Init and Shutdown) + PRLock *mLock; + PRCondVar *mIdleThreadCV; // notified to wake up an idle thread + PRCondVar *mExitThreadCV; // notified when a thread exits + PRUint32 mNumThreads; // number of active + idle threads + PRUint32 mNumIdleThreads; // number of idle threads + PRCList mEventQ; // queue of PLEvent structs + PRBool mShutdown; // set to true if shutting down +}; + +NS_IMPL_THREADSAFE_ISUPPORTS2(nsIOThreadPool, nsIEventTarget, nsIObserver) + +nsresult +nsIOThreadPool::Init() +{ +#if defined(PR_LOGGING) + if (!gIOThreadPoolLog) + gIOThreadPoolLog = PR_NewLogModule("nsIOThreadPool"); +#endif + + mNumThreads = 0; + mNumIdleThreads = 0; + mShutdown = PR_FALSE; + + mLock = PR_NewLock(); + if (!mLock) + return NS_ERROR_OUT_OF_MEMORY; + + mIdleThreadCV = PR_NewCondVar(mLock); + if (!mIdleThreadCV) + return NS_ERROR_OUT_OF_MEMORY; + + mExitThreadCV = PR_NewCondVar(mLock); + if (!mExitThreadCV) + return NS_ERROR_OUT_OF_MEMORY; + + PR_INIT_CLIST(&mEventQ); + + // we want to shutdown the i/o thread pool at xpcom-shutdown time... + nsCOMPtr os = do_GetService("@mozilla.org/observer-service;1"); + if (os) + os->AddObserver(this, "xpcom-shutdown", PR_FALSE); + return NS_OK; +} + +nsIOThreadPool::~nsIOThreadPool() +{ + LOG(("Destroying nsIOThreadPool @%p\n", this)); + +#ifdef DEBUG + NS_ASSERTION(PR_CLIST_IS_EMPTY(&mEventQ), "leaking events"); + NS_ASSERTION(mNumThreads == 0, "leaking thread(s)"); +#endif + + if (mIdleThreadCV) + PR_DestroyCondVar(mIdleThreadCV); + if (mExitThreadCV) + PR_DestroyCondVar(mExitThreadCV); + if (mLock) + PR_DestroyLock(mLock); +} + +void +nsIOThreadPool::Shutdown() +{ + LOG(("nsIOThreadPool::Shutdown\n")); + + // synchronize with background threads... + { + nsAutoLock lock(mLock); + mShutdown = PR_TRUE; + + PR_NotifyAllCondVar(mIdleThreadCV); + + while (mNumThreads != 0) + PR_WaitCondVar(mExitThreadCV, PR_INTERVAL_NO_TIMEOUT); + } +} + +NS_IMETHODIMP +nsIOThreadPool::PostEvent(PLEvent *event) +{ + LOG(("nsIOThreadPool::PostEvent [event=%p]\n", event)); + + nsAutoLock lock(mLock); + + // if we are shutting down, then prevent additional events from being + // added to the queue... + if (mShutdown) + return NS_ERROR_UNEXPECTED; + + nsresult rv = NS_OK; + + PR_APPEND_LINK(&event->link, &mEventQ); + + // now, look for an available idle thread... + if (mNumIdleThreads) + PR_NotifyCondVar(mIdleThreadCV); // wake up an idle thread + + // or, try to create a new thread unless we have reached our maximum... + else if (mNumThreads < MAX_THREADS) { + NS_ADDREF_THIS(); // the thread owns a reference to us + mNumThreads++; + PRThread *thread = PR_CreateThread(PR_USER_THREAD, + ThreadFunc, + this, + PR_PRIORITY_NORMAL, + PR_GLOBAL_THREAD, + PR_UNJOINABLE_THREAD, + 0); + if (!thread) { + NS_RELEASE_THIS(); + mNumThreads--; + rv = NS_ERROR_OUT_OF_MEMORY; + } + } + // else, we expect one of the active threads to process the event queue. + + return rv; +} + +NS_IMETHODIMP +nsIOThreadPool::IsOnCurrentThread(PRBool *result) +{ + // no one should be calling this method. if this assertion gets hit, + // then we need to think carefully about what this method should be + // returning. + NS_NOTREACHED("nsIOThreadPool::IsOnCurrentThread"); + + // fudging this a bit since we actually cover several threads... + *result = PR_FALSE; + return NS_OK; +} + +NS_IMETHODIMP +nsIOThreadPool::Observe(nsISupports *, const char *topic, const PRUnichar *) +{ + NS_ASSERTION(strcmp(topic, "xpcom-shutdown") == 0, "unexpected topic"); + Shutdown(); + return NS_OK; +} + +void +nsIOThreadPool::ThreadFunc(void *arg) +{ + nsIOThreadPool *pool = (nsIOThreadPool *) arg; + + LOG(("entering ThreadFunc\n")); + + { + nsAutoLock lock(pool->mLock); + + for (;;) { + PRIntervalTime start = PR_IntervalNow(), timeout = IDLE_TIMEOUT; + // + // wait for one or more of the following to occur: + // (1) the event queue has an event to process + // (2) the shutdown flag has been set + // (3) the thread has been idle for too long + // + // PR_WaitCondVar will return when any of these conditions is true. + // + while (PR_CLIST_IS_EMPTY(&pool->mEventQ) && !pool->mShutdown) { + pool->mNumIdleThreads++; + PR_WaitCondVar(pool->mIdleThreadCV, timeout); + pool->mNumIdleThreads--; + + PRIntervalTime delta = PR_IntervalNow() - start; + if (delta >= timeout) + break; + timeout -= delta; + start += delta; + } + + // if the queue is still empty, then kill this thread (either we + // are shutting down or the thread exceeded the idle timeout)... + if (PR_CLIST_IS_EMPTY(&pool->mEventQ)) + break; + + // handle one event at a time: we don't want this one thread to hog + // all the events while other threads may be able to help out ;-) + do { + PLEvent *event = PLEVENT_FROM_LINK(PR_LIST_HEAD(&pool->mEventQ)); + PR_REMOVE_AND_INIT_LINK(&event->link); + + LOG(("event:%p\n", event)); + + // release lock! + lock.unlock(); + PL_HandleEvent(event); + lock.lock(); + } + while (!PR_CLIST_IS_EMPTY(&pool->mEventQ)); + } + + // thread is going away... + pool->mNumThreads--; + PR_NotifyCondVar(pool->mExitThreadCV); + } + + // release our reference to the pool + NS_RELEASE(pool); + + LOG(("leaving ThreadFunc\n")); +} + +//----------------------------------------------------------------------------- + +NS_METHOD +net_NewIOThreadPool(nsISupports *outer, REFNSIID iid, void **result) +{ + nsIOThreadPool *pool = new nsIOThreadPool(); + if (!pool) + return NS_ERROR_OUT_OF_MEMORY; + NS_ADDREF(pool); + nsresult rv = pool->Init(); + if (NS_SUCCEEDED(rv)) + rv = pool->QueryInterface(iid, result); + NS_RELEASE(pool); + return rv; +} diff --git a/tests/cpp/nsTextFrameThebes.cpp b/tests/cpp/nsTextFrameThebes.cpp new file mode 100644 index 0000000..014368a --- /dev/null +++ b/tests/cpp/nsTextFrameThebes.cpp @@ -0,0 +1,6873 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is mozilla.org code. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Robert O'Callahan + * Roger B. Sidje + * Pierre Phaneuf + * Prabhat Hegde + * Tomi Leppikangas + * Roland Mainz + * Daniel Glazman + * Neil Deakin + * Masayuki Nakano + * Mats Palmgren + * Uri Bernstein + * Stephen Blackheath + * Michael Ventnor + * Ehsan Akhgari + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/* rendering object for textual content of elements */ + +#include "nsCOMPtr.h" +#include "nsHTMLParts.h" +#include "nsCRT.h" +#include "nsSplittableFrame.h" +#include "nsLineLayout.h" +#include "nsString.h" +#include "nsUnicharUtils.h" +#include "nsPresContext.h" +#include "nsIContent.h" +#include "nsStyleConsts.h" +#include "nsStyleContext.h" +#include "nsCoord.h" +#include "nsIFontMetrics.h" +#include "nsIRenderingContext.h" +#include "nsIPresShell.h" +#include "nsITimer.h" +#include "nsTArray.h" +#include "nsIDOMText.h" +#include "nsIDocument.h" +#include "nsIDeviceContext.h" +#include "nsCSSPseudoElements.h" +#include "nsCompatibility.h" +#include "nsCSSColorUtils.h" +#include "nsLayoutUtils.h" +#include "nsDisplayList.h" +#include "nsFrame.h" +#include "nsTextFrameUtils.h" +#include "nsTextRunTransformations.h" +#include "nsFrameManager.h" +#include "nsTextFrameTextRunCache.h" +#include "nsExpirationTracker.h" +#include "nsTextFrame.h" +#include "nsICaseConversion.h" +#include "nsIUGenCategory.h" +#include "nsUnicharUtilCIID.h" + +#include "nsTextFragment.h" +#include "nsGkAtoms.h" +#include "nsFrameSelection.h" +#include "nsISelection.h" +#include "nsIDOMRange.h" +#include "nsILookAndFeel.h" +#include "nsCSSRendering.h" +#include "nsContentUtils.h" +#include "nsLineBreaker.h" +#include "nsIWordBreaker.h" +#include "nsGenericDOMDataNode.h" + +#include "nsILineIterator.h" + +#include "nsIServiceManager.h" +#ifdef ACCESSIBILITY +#include "nsIAccessible.h" +#include "nsIAccessibilityService.h" +#endif +#include "nsAutoPtr.h" +#include "nsStyleSet.h" + +#include "nsBidiFrames.h" +#include "nsBidiPresUtils.h" +#include "nsBidiUtils.h" + +#include "nsIThebesFontMetrics.h" +#include "gfxFont.h" +#include "gfxContext.h" +#include "gfxTextRunWordCache.h" +#include "gfxImageSurface.h" + +#ifdef NS_DEBUG +#undef NOISY_BLINK +#undef NOISY_REFLOW +#undef NOISY_TRIM +#else +#undef NOISY_BLINK +#undef NOISY_REFLOW +#undef NOISY_TRIM +#endif + +// The following flags are set during reflow + +// This bit is set on the first frame in a continuation indicating +// that it was chopped short because of :first-letter style. +#define TEXT_FIRST_LETTER 0x00100000 +// This bit is set on frames that are logically adjacent to the start of the +// line (i.e. no prior frame on line with actual displayed in-flow content). +#define TEXT_START_OF_LINE 0x00200000 +// This bit is set on frames that are logically adjacent to the end of the +// line (i.e. no following on line with actual displayed in-flow content). +#define TEXT_END_OF_LINE 0x00400000 +// This bit is set on frames that end with a hyphenated break. +#define TEXT_HYPHEN_BREAK 0x00800000 +// This bit is set on frames that trimmed trailing whitespace characters when +// calculating their width during reflow. +#define TEXT_TRIMMED_TRAILING_WHITESPACE 0x01000000 +// This bit is set on frames that have justification enabled. We record +// this in a state bit because we don't always have the containing block +// easily available to check text-align on. +#define TEXT_JUSTIFICATION_ENABLED 0x02000000 +// Set this bit if the textframe has overflow area for IME/spellcheck underline. +#define TEXT_SELECTION_UNDERLINE_OVERFLOWED 0x04000000 + +#define TEXT_REFLOW_FLAGS \ + (TEXT_FIRST_LETTER|TEXT_START_OF_LINE|TEXT_END_OF_LINE|TEXT_HYPHEN_BREAK| \ + TEXT_TRIMMED_TRAILING_WHITESPACE|TEXT_JUSTIFICATION_ENABLED| \ + TEXT_HAS_NONCOLLAPSED_CHARACTERS|TEXT_SELECTION_UNDERLINE_OVERFLOWED) + +// Cache bits for IsEmpty(). +// Set this bit if the textframe is known to be only collapsible whitespace. +#define TEXT_IS_ONLY_WHITESPACE 0x08000000 +// Set this bit if the textframe is known to be not only collapsible whitespace. +#define TEXT_ISNOT_ONLY_WHITESPACE 0x10000000 + +#define TEXT_WHITESPACE_FLAGS 0x18000000 + +// nsTextFrame.h has +// #define TEXT_BLINK_ON_OR_PRINTING 0x20000000 + +// Set when this text frame is mentioned in the userdata for a textrun +#define TEXT_IN_TEXTRUN_USER_DATA 0x40000000 + +// nsTextFrame.h has +// #define TEXT_HAS_NONCOLLAPSED_CHARACTERS 0x80000000 + +/* + * Some general notes + * + * Text frames delegate work to gfxTextRun objects. The gfxTextRun object + * transforms text to positioned glyphs. It can report the geometry of the + * glyphs and paint them. Text frames configure gfxTextRuns by providing text, + * spacing, language, and other information. + * + * A gfxTextRun can cover more than one DOM text node. This is necessary to + * get kerning, ligatures and shaping for text that spans multiple text nodes + * but is all the same font. The userdata for a gfxTextRun object is a + * TextRunUserData* or an nsIFrame*. + * + * We go to considerable effort to make sure things work even if in-flow + * siblings have different style contexts (i.e., first-letter and first-line). + * + * Our convention is that unsigned integer character offsets are offsets into + * the transformed string. Signed integer character offsets are offsets into + * the DOM string. + * + * XXX currently we don't handle hyphenated breaks between text frames where the + * hyphen occurs at the end of the first text frame, e.g. + * Kit­ty + */ + +/** + * We use an array of these objects to record which text frames + * are associated with the textrun. mStartFrame is the start of a list of + * text frames. Some sequence of its continuations are covered by the textrun. + * A content textnode can have at most one TextRunMappedFlow associated with it + * for a given textrun. + * + * mDOMOffsetToBeforeTransformOffset is added to DOM offsets for those frames to obtain + * the offset into the before-transformation text of the textrun. It can be + * positive (when a text node starts in the middle of a text run) or + * negative (when a text run starts in the middle of a text node). Of course + * it can also be zero. + */ +struct TextRunMappedFlow { + nsTextFrame* mStartFrame; + PRInt32 mDOMOffsetToBeforeTransformOffset; + // The text mapped starts at mStartFrame->GetContentOffset() and is this long + PRUint32 mContentLength; +}; + +/** + * This is our user data for the textrun, when textRun->GetFlags() does not + * have TEXT_SIMPLE_FLOW set. When TEXT_SIMPLE_FLOW is set, there is just one + * flow, the textrun's user data pointer is a pointer to mStartFrame + * for that flow, mDOMOffsetToBeforeTransformOffset is zero, and mContentLength + * is the length of the text node. + */ +struct TextRunUserData { + TextRunMappedFlow* mMappedFlows; + PRInt32 mMappedFlowCount; + + PRUint32 mLastFlowIndex; +}; + +/** + * This helper object computes colors used for painting, and also IME + * underline information. The data is computed lazily and cached as necessary. + * These live for just the duration of one paint operation. + */ +class nsTextPaintStyle { +public: + nsTextPaintStyle(nsTextFrame* aFrame); + + nscolor GetTextColor(); + /** + * Compute the colors for normally-selected text. Returns false if + * the normal selection is not being displayed. + */ + PRBool GetSelectionColors(nscolor* aForeColor, + nscolor* aBackColor); + void GetHighlightColors(nscolor* aForeColor, + nscolor* aBackColor); + void GetIMESelectionColors(PRInt32 aIndex, + nscolor* aForeColor, + nscolor* aBackColor); + // if this returns PR_FALSE, we don't need to draw underline. + PRBool GetSelectionUnderlineForPaint(PRInt32 aIndex, + nscolor* aLineColor, + float* aRelativeSize, + PRUint8* aStyle); + + // if this returns PR_FALSE, we don't need to draw underline. + static PRBool GetSelectionUnderline(nsPresContext* aPresContext, + PRInt32 aIndex, + nscolor* aLineColor, + float* aRelativeSize, + PRUint8* aStyle); + + nsPresContext* PresContext() { return mPresContext; } + + enum { + eIndexRawInput = 0, + eIndexSelRawText, + eIndexConvText, + eIndexSelConvText, + eIndexSpellChecker + }; + + static PRInt32 GetUnderlineStyleIndexForSelectionType(PRInt32 aSelectionType) + { + switch (aSelectionType) { + case nsISelectionController::SELECTION_IME_RAWINPUT: + return eIndexRawInput; + case nsISelectionController::SELECTION_IME_SELECTEDRAWTEXT: + return eIndexSelRawText; + case nsISelectionController::SELECTION_IME_CONVERTEDTEXT: + return eIndexConvText; + case nsISelectionController::SELECTION_IME_SELECTEDCONVERTEDTEXT: + return eIndexSelConvText; + case nsISelectionController::SELECTION_SPELLCHECK: + return eIndexSpellChecker; + default: + NS_WARNING("non-IME selection type"); + return eIndexRawInput; + } + } + +protected: + nsTextFrame* mFrame; + nsPresContext* mPresContext; + PRPackedBool mInitCommonColors; + PRPackedBool mInitSelectionColors; + + // Selection data + + PRInt16 mSelectionStatus; // see nsIDocument.h SetDisplaySelection() + nscolor mSelectionTextColor; + nscolor mSelectionBGColor; + + // Common data + + PRInt32 mSufficientContrast; + nscolor mFrameBackgroundColor; + + // selection colors and underline info, the colors are resolved colors, + // i.e., the foreground color and background color are swapped if it's needed. + // And also line color will be resolved from them. + struct nsSelectionStyle { + PRBool mInit; + nscolor mTextColor; + nscolor mBGColor; + nscolor mUnderlineColor; + PRUint8 mUnderlineStyle; + float mUnderlineRelativeSize; + }; + nsSelectionStyle mSelectionStyle[5]; + + // Color initializations + void InitCommonColors(); + PRBool InitSelectionColors(); + + nsSelectionStyle* GetSelectionStyle(PRInt32 aIndex); + void InitSelectionStyle(PRInt32 aIndex); + + PRBool EnsureSufficientContrast(nscolor *aForeColor, nscolor *aBackColor); + + nscolor GetResolvedForeColor(nscolor aColor, nscolor aDefaultForeColor, + nscolor aBackColor); +}; + +static void +DestroyUserData(void* aUserData) +{ + TextRunUserData* userData = static_cast(aUserData); + if (userData) { + nsMemory::Free(userData); + } +} + +// Remove the textrun from the frame continuation chain starting at aFrame, +// which should be marked as a textrun owner. +static void +ClearAllTextRunReferences(nsTextFrame* aFrame, gfxTextRun* aTextRun) +{ + aFrame->RemoveStateBits(TEXT_IN_TEXTRUN_USER_DATA); + while (aFrame) { + NS_ASSERTION(aFrame->GetType() == nsGkAtoms::textFrame, + "Bad frame"); + if (aFrame->GetTextRun() != aTextRun) + break; + aFrame->SetTextRun(nsnull); + aFrame = static_cast(aFrame->GetNextContinuation()); + } +} + +// Figure out which frames +static void +UnhookTextRunFromFrames(gfxTextRun* aTextRun) +{ + if (!aTextRun->GetUserData()) + return; + + // Kill all references to the textrun. It could be referenced by any of its + // owners, and all their in-flows. + if (aTextRun->GetFlags() & nsTextFrameUtils::TEXT_IS_SIMPLE_FLOW) { + nsIFrame* firstInFlow = static_cast(aTextRun->GetUserData()); + ClearAllTextRunReferences(static_cast(firstInFlow), aTextRun); + } else { + TextRunUserData* userData = + static_cast(aTextRun->GetUserData()); + PRInt32 i; + for (i = 0; i < userData->mMappedFlowCount; ++i) { + ClearAllTextRunReferences(userData->mMappedFlows[i].mStartFrame, aTextRun); + } + DestroyUserData(userData); + } + aTextRun->SetUserData(nsnull); +} + +class FrameTextRunCache; + +static FrameTextRunCache *gTextRuns = nsnull; + +/* + * Cache textruns and expire them after 3*10 seconds of no use. + */ +class FrameTextRunCache : public nsExpirationTracker { +public: + enum { TIMEOUT_SECONDS = 10 }; + FrameTextRunCache() + : nsExpirationTracker(TIMEOUT_SECONDS*1000) {} + ~FrameTextRunCache() { + AgeAllGenerations(); + } + + void RemoveFromCache(gfxTextRun* aTextRun) { + if (aTextRun->GetExpirationState()->IsTracked()) { + RemoveObject(aTextRun); + } + if (aTextRun->GetFlags() & gfxTextRunWordCache::TEXT_IN_CACHE) { + gfxTextRunWordCache::RemoveTextRun(aTextRun); + } + } + + // This gets called when the timeout has expired on a gfxTextRun + virtual void NotifyExpired(gfxTextRun* aTextRun) { + UnhookTextRunFromFrames(aTextRun); + RemoveFromCache(aTextRun); + delete aTextRun; + } +}; + +static gfxTextRun * +MakeTextRun(const PRUnichar *aText, PRUint32 aLength, + gfxFontGroup *aFontGroup, const gfxFontGroup::Parameters* aParams, + PRUint32 aFlags) +{ + nsAutoPtr textRun; + if (aLength == 0) { + textRun = aFontGroup->MakeEmptyTextRun(aParams, aFlags); + } else if (aLength == 1 && aText[0] == ' ') { + textRun = aFontGroup->MakeSpaceTextRun(aParams, aFlags); + } else { + textRun = gfxTextRunWordCache::MakeTextRun(aText, aLength, aFontGroup, + aParams, aFlags); + } + if (!textRun) + return nsnull; + nsresult rv = gTextRuns->AddObject(textRun); + if (NS_FAILED(rv)) { + gTextRuns->RemoveFromCache(textRun); + return nsnull; + } + return textRun.forget(); +} + +static gfxTextRun * +MakeTextRun(const PRUint8 *aText, PRUint32 aLength, + gfxFontGroup *aFontGroup, const gfxFontGroup::Parameters* aParams, + PRUint32 aFlags) +{ + nsAutoPtr textRun; + if (aLength == 0) { + textRun = aFontGroup->MakeEmptyTextRun(aParams, aFlags); + } else if (aLength == 1 && aText[0] == ' ') { + textRun = aFontGroup->MakeSpaceTextRun(aParams, aFlags); + } else { + textRun = gfxTextRunWordCache::MakeTextRun(aText, aLength, aFontGroup, + aParams, aFlags); + } + if (!textRun) + return nsnull; + nsresult rv = gTextRuns->AddObject(textRun); + if (NS_FAILED(rv)) { + gTextRuns->RemoveFromCache(textRun); + return nsnull; + } + return textRun.forget(); +} + +nsresult +nsTextFrameTextRunCache::Init() { + gTextRuns = new FrameTextRunCache(); + return gTextRuns ? NS_OK : NS_ERROR_OUT_OF_MEMORY; +} + +void +nsTextFrameTextRunCache::Shutdown() { + delete gTextRuns; + gTextRuns = nsnull; +} + +PRInt32 nsTextFrame::GetContentEnd() const { + nsTextFrame* next = static_cast(GetNextContinuation()); + return next ? next->GetContentOffset() : GetFragment()->GetLength(); +} + +PRInt32 nsTextFrame::GetInFlowContentLength() { +#ifdef IBMBIDI + nsTextFrame* nextBidi = nsnull; + PRInt32 start = -1, end; + + if (mState & NS_FRAME_IS_BIDI) { + nextBidi = static_cast(GetLastInFlow()->GetNextContinuation()); + if (nextBidi) { + nextBidi->GetOffsets(start, end); + return start - mContentOffset; + } + } +#endif //IBMBIDI + return GetFragment()->GetLength() - mContentOffset; +} + +// Smarter versions of XP_IS_SPACE. +// Unicode is really annoying; sometimes a space character isn't whitespace --- +// when it combines with another character +// So we have several versions of IsSpace for use in different contexts. + +static PRBool IsSpaceCombiningSequenceTail(const nsTextFragment* aFrag, PRUint32 aPos) +{ + NS_ASSERTION(aPos <= aFrag->GetLength(), "Bad offset"); + if (!aFrag->Is2b()) + return PR_FALSE; + return nsTextFrameUtils::IsSpaceCombiningSequenceTail( + aFrag->Get2b() + aPos, aFrag->GetLength() - aPos); +} + +// Check whether aPos is a space for CSS 'word-spacing' purposes +static PRBool IsCSSWordSpacingSpace(const nsTextFragment* aFrag, + PRUint32 aPos, const nsStyleText* aStyleText) +{ + NS_ASSERTION(aPos < aFrag->GetLength(), "No text for IsSpace!"); + + PRUnichar ch = aFrag->CharAt(aPos); + switch (ch) { + case ' ': + case CH_NBSP: + return !IsSpaceCombiningSequenceTail(aFrag, aPos + 1); + case '\t': return !aStyleText->WhiteSpaceIsSignificant(); + case '\n': return !aStyleText->NewlineIsSignificant(); + default: return PR_FALSE; + } +} + +// Check whether the string aChars/aLength starts with space that's +// trimmable according to CSS 'white-space:normal/nowrap'. +static PRBool IsTrimmableSpace(const PRUnichar* aChars, PRUint32 aLength) +{ + NS_ASSERTION(aLength > 0, "No text for IsSpace!"); + + PRUnichar ch = *aChars; + if (ch == ' ') + return !nsTextFrameUtils::IsSpaceCombiningSequenceTail(aChars + 1, aLength - 1); + return ch == '\t' || ch == '\f' || ch == '\n'; +} + +// Check whether the character aCh is trimmable according to CSS +// 'white-space:normal/nowrap' +static PRBool IsTrimmableSpace(char aCh) +{ + return aCh == ' ' || aCh == '\t' || aCh == '\f' || aCh == '\n'; +} + +static PRBool IsTrimmableSpace(const nsTextFragment* aFrag, PRUint32 aPos, + const nsStyleText* aStyleText) +{ + NS_ASSERTION(aPos < aFrag->GetLength(), "No text for IsSpace!"); + + switch (aFrag->CharAt(aPos)) { + case ' ': return !aStyleText->WhiteSpaceIsSignificant() && + !IsSpaceCombiningSequenceTail(aFrag, aPos + 1); + case '\n': return !aStyleText->NewlineIsSignificant(); + case '\t': + case '\f': return !aStyleText->WhiteSpaceIsSignificant(); + default: return PR_FALSE; + } +} + +static PRBool IsSelectionSpace(const nsTextFragment* aFrag, PRUint32 aPos) +{ + NS_ASSERTION(aPos < aFrag->GetLength(), "No text for IsSpace!"); + PRUnichar ch = aFrag->CharAt(aPos); + if (ch == ' ' || ch == CH_NBSP) + return !IsSpaceCombiningSequenceTail(aFrag, aPos + 1); + return ch == '\t' || ch == '\n' || ch == '\f'; +} + +// Count the amount of trimmable whitespace (as per CSS +// 'white-space:normal/nowrap') in a text fragment. The first +// character is at offset aStartOffset; the maximum number of characters +// to check is aLength. aDirection is -1 or 1 depending on whether we should +// progress backwards or forwards. +static PRUint32 +GetTrimmableWhitespaceCount(const nsTextFragment* aFrag, + PRInt32 aStartOffset, PRInt32 aLength, + PRInt32 aDirection) +{ + PRInt32 count = 0; + if (aFrag->Is2b()) { + const PRUnichar* str = aFrag->Get2b() + aStartOffset; + PRInt32 fragLen = aFrag->GetLength() - aStartOffset; + for (; count < aLength; ++count) { + if (!IsTrimmableSpace(str, fragLen)) + break; + str += aDirection; + fragLen -= aDirection; + } + } else { + const char* str = aFrag->Get1b() + aStartOffset; + for (; count < aLength; ++count) { + if (!IsTrimmableSpace(*str)) + break; + str += aDirection; + } + } + return count; +} + +static PRBool +IsAllWhitespace(const nsTextFragment* aFrag, PRBool aAllowNewline) +{ + if (aFrag->Is2b()) + return PR_FALSE; + PRInt32 len = aFrag->GetLength(); + const char* str = aFrag->Get1b(); + for (PRInt32 i = 0; i < len; ++i) { + char ch = str[i]; + if (ch == ' ' || ch == '\t' || (ch == '\n' && aAllowNewline)) + continue; + return PR_FALSE; + } + return PR_TRUE; +} + +/** + * This class accumulates state as we scan a paragraph of text. It detects + * textrun boundaries (changes from text to non-text, hard + * line breaks, and font changes) and builds a gfxTextRun at each boundary. + * It also detects linebreaker run boundaries (changes from text to non-text, + * and hard line breaks) and at each boundary runs the linebreaker to compute + * potential line breaks. It also records actual line breaks to store them in + * the textruns. + */ +class BuildTextRunsScanner { +public: + BuildTextRunsScanner(nsPresContext* aPresContext, gfxContext* aContext, + nsIFrame* aLineContainer) : + mCurrentFramesAllSameTextRun(nsnull), + mContext(aContext), + mLineContainer(aLineContainer), + mBidiEnabled(aPresContext->BidiEnabled()), + mSkipIncompleteTextRuns(PR_FALSE), + mNextRunContextInfo(nsTextFrameUtils::INCOMING_NONE), + mCurrentRunContextInfo(nsTextFrameUtils::INCOMING_NONE) { + ResetRunInfo(); + } + ~BuildTextRunsScanner() { + NS_ASSERTION(mBreakSinks.IsEmpty(), "Should have been cleared"); + NS_ASSERTION(mTextRunsToDelete.IsEmpty(), "Should have been cleared"); + NS_ASSERTION(mLineBreakBeforeFrames.IsEmpty(), "Should have been cleared"); + NS_ASSERTION(mMappedFlows.IsEmpty(), "Should have been cleared"); + } + + void SetAtStartOfLine() { + mStartOfLine = PR_TRUE; + mCanStopOnThisLine = PR_FALSE; + } + void SetSkipIncompleteTextRuns(PRBool aSkip) { + mSkipIncompleteTextRuns = aSkip; + } + void SetCommonAncestorWithLastFrame(nsIFrame* aFrame) { + mCommonAncestorWithLastFrame = aFrame; + } + PRBool CanStopOnThisLine() { + return mCanStopOnThisLine; + } + nsIFrame* GetCommonAncestorWithLastFrame() { + return mCommonAncestorWithLastFrame; + } + void LiftCommonAncestorWithLastFrameToParent(nsIFrame* aFrame) { + if (mCommonAncestorWithLastFrame && + mCommonAncestorWithLastFrame->GetParent() == aFrame) { + mCommonAncestorWithLastFrame = aFrame; + } + } + void ScanFrame(nsIFrame* aFrame); + PRBool IsTextRunValidForMappedFlows(gfxTextRun* aTextRun); + void FlushFrames(PRBool aFlushLineBreaks, PRBool aSuppressTrailingBreak); + void FlushLineBreaks(gfxTextRun* aTrailingTextRun); + void ResetRunInfo() { + mLastFrame = nsnull; + mMappedFlows.Clear(); + mLineBreakBeforeFrames.Clear(); + mMaxTextLength = 0; + mDoubleByteText = PR_FALSE; + } + void AccumulateRunInfo(nsTextFrame* aFrame); + /** + * @return null to indicate either textrun construction failed or + * we constructed just a partial textrun to set up linebreaker and other + * state for following textruns. + */ + gfxTextRun* BuildTextRunForFrames(void* aTextBuffer); + void AssignTextRun(gfxTextRun* aTextRun); + nsTextFrame* GetNextBreakBeforeFrame(PRUint32* aIndex); + void SetupBreakSinksForTextRun(gfxTextRun* aTextRun, PRBool aIsExistingTextRun, + PRBool aSuppressSink); + struct FindBoundaryState { + nsIFrame* mStopAtFrame; + nsTextFrame* mFirstTextFrame; + nsTextFrame* mLastTextFrame; + PRPackedBool mSeenTextRunBoundaryOnLaterLine; + PRPackedBool mSeenTextRunBoundaryOnThisLine; + PRPackedBool mSeenSpaceForLineBreakingOnThisLine; + }; + enum FindBoundaryResult { + FB_CONTINUE, + FB_STOPPED_AT_STOP_FRAME, + FB_FOUND_VALID_TEXTRUN_BOUNDARY + }; + FindBoundaryResult FindBoundaries(nsIFrame* aFrame, FindBoundaryState* aState); + + PRBool ContinueTextRunAcrossFrames(nsTextFrame* aFrame1, nsTextFrame* aFrame2); + + // Like TextRunMappedFlow but with some differences. mStartFrame to mEndFrame + // (exclusive) are a sequence of in-flow frames (if mEndFrame is null, then + // continuations starting from mStartFrame are a sequence of in-flow frames). + struct MappedFlow { + nsTextFrame* mStartFrame; + nsTextFrame* mEndFrame; + // When we consider breaking between elements, the nearest common + // ancestor of the elements containing the characters is the one whose + // CSS 'white-space' property governs. So this records the nearest common + // ancestor of mStartFrame and the previous text frame, or null if there + // was no previous text frame on this line. + nsIFrame* mAncestorControllingInitialBreak; + + PRInt32 GetContentEnd() { + return mEndFrame ? mEndFrame->GetContentOffset() + : mStartFrame->GetFragment()->GetLength(); + } + }; + + class BreakSink : public nsILineBreakSink { + public: + BreakSink(gfxTextRun* aTextRun, gfxContext* aContext, PRUint32 aOffsetIntoTextRun, + PRBool aExistingTextRun) : + mTextRun(aTextRun), mContext(aContext), + mOffsetIntoTextRun(aOffsetIntoTextRun), + mChangedBreaks(PR_FALSE), mExistingTextRun(aExistingTextRun) {} + + virtual void SetBreaks(PRUint32 aOffset, PRUint32 aLength, + PRPackedBool* aBreakBefore) { + if (mTextRun->SetPotentialLineBreaks(aOffset + mOffsetIntoTextRun, aLength, + aBreakBefore, mContext)) { + mChangedBreaks = PR_TRUE; + // Be conservative and assume that some breaks have been set + mTextRun->ClearFlagBits(nsTextFrameUtils::TEXT_NO_BREAKS); + } + } + + virtual void SetCapitalization(PRUint32 aOffset, PRUint32 aLength, + PRPackedBool* aCapitalize) { + NS_ASSERTION(mTextRun->GetFlags() & nsTextFrameUtils::TEXT_IS_TRANSFORMED, + "Text run should be transformed!"); + nsTransformedTextRun* transformedTextRun = + static_cast(mTextRun); + transformedTextRun->SetCapitalization(aOffset + mOffsetIntoTextRun, aLength, + aCapitalize, mContext); + } + + void Finish() { + NS_ASSERTION(!(mTextRun->GetFlags() & + (gfxTextRunWordCache::TEXT_UNUSED_FLAGS | + nsTextFrameUtils::TEXT_UNUSED_FLAG)), + "Flag set that should never be set! (memory safety error?)"); + if (mTextRun->GetFlags() & nsTextFrameUtils::TEXT_IS_TRANSFORMED) { + nsTransformedTextRun* transformedTextRun = + static_cast(mTextRun); + transformedTextRun->FinishSettingProperties(mContext); + } + } + + gfxTextRun* mTextRun; + gfxContext* mContext; + PRUint32 mOffsetIntoTextRun; + PRPackedBool mChangedBreaks; + PRPackedBool mExistingTextRun; + }; + +private: + nsAutoTArray mMappedFlows; + nsAutoTArray mLineBreakBeforeFrames; + nsAutoTArray,10> mBreakSinks; + nsAutoTArray mTextRunsToDelete; + nsLineBreaker mLineBreaker; + gfxTextRun* mCurrentFramesAllSameTextRun; + gfxContext* mContext; + nsIFrame* mLineContainer; + nsTextFrame* mLastFrame; + // The common ancestor of the current frame and the previous leaf frame + // on the line, or null if there was no previous leaf frame. + nsIFrame* mCommonAncestorWithLastFrame; + // mMaxTextLength is an upper bound on the size of the text in all mapped frames + PRUint32 mMaxTextLength; + PRPackedBool mDoubleByteText; + PRPackedBool mBidiEnabled; + PRPackedBool mStartOfLine; + PRPackedBool mSkipIncompleteTextRuns; + PRPackedBool mCanStopOnThisLine; + PRUint8 mNextRunContextInfo; + PRUint8 mCurrentRunContextInfo; +}; + +static nsIFrame* +FindLineContainer(nsIFrame* aFrame) +{ + while (aFrame && aFrame->CanContinueTextRun()) { + aFrame = aFrame->GetParent(); + } + return aFrame; +} + +static PRBool +TextContainsLineBreakerWhiteSpace(const void* aText, PRUint32 aLength, + PRBool aIsDoubleByte) +{ + PRUint32 i; + if (aIsDoubleByte) { + const PRUnichar* chars = static_cast(aText); + for (i = 0; i < aLength; ++i) { + if (nsLineBreaker::IsSpace(chars[i])) + return PR_TRUE; + } + return PR_FALSE; + } else { + const PRUint8* chars = static_cast(aText); + for (i = 0; i < aLength; ++i) { + if (nsLineBreaker::IsSpace(chars[i])) + return PR_TRUE; + } + return PR_FALSE; + } +} + +struct FrameTextTraversal { + // These fields identify which frames should be recursively scanned + // The first normal frame to scan (or null, if no such frame should be scanned) + nsIFrame* mFrameToScan; + // The first overflow frame to scan (or null, if no such frame should be scanned) + nsIFrame* mOverflowFrameToScan; + // Whether to scan the siblings of mFrameToDescendInto/mOverflowFrameToDescendInto + PRPackedBool mScanSiblings; + + // These identify the boundaries of the context required for + // line breaking or textrun construction + PRPackedBool mLineBreakerCanCrossFrameBoundary; + PRPackedBool mTextRunCanCrossFrameBoundary; + + nsIFrame* NextFrameToScan() { + nsIFrame* f; + if (mFrameToScan) { + f = mFrameToScan; + mFrameToScan = mScanSiblings ? f->GetNextSibling() : nsnull; + } else if (mOverflowFrameToScan) { + f = mOverflowFrameToScan; + mOverflowFrameToScan = mScanSiblings ? f->GetNextSibling() : nsnull; + } else { + f = nsnull; + } + return f; + } +}; + +static FrameTextTraversal +CanTextCrossFrameBoundary(nsIFrame* aFrame, nsIAtom* aType) +{ + NS_ASSERTION(aType == aFrame->GetType(), "Wrong type"); + + FrameTextTraversal result; + + PRBool continuesTextRun = aFrame->CanContinueTextRun(); + if (aType == nsGkAtoms::placeholderFrame) { + // placeholders are "invisible", so a text run should be able to span + // across one. But don't descend into the out-of-flow. + result.mLineBreakerCanCrossFrameBoundary = PR_TRUE; + result.mOverflowFrameToScan = nsnull; + if (continuesTextRun) { + // ... Except for first-letter floats, which are really in-flow + // from the point of view of capitalization etc, so we'd better + // descend into them. But we actually need to break the textrun for + // first-letter floats since things look bad if, say, we try to make a + // ligature across the float boundary. + result.mFrameToScan = + (static_cast(aFrame))->GetOutOfFlowFrame(); + result.mScanSiblings = PR_FALSE; + result.mTextRunCanCrossFrameBoundary = PR_FALSE; + } else { + result.mFrameToScan = nsnull; + result.mTextRunCanCrossFrameBoundary = PR_TRUE; + } + } else { + if (continuesTextRun) { + result.mFrameToScan = aFrame->GetFirstChild(nsnull); + result.mOverflowFrameToScan = aFrame->GetFirstChild(nsGkAtoms::overflowList); + NS_WARN_IF_FALSE(!result.mOverflowFrameToScan, + "Scanning overflow inline frames is something we should avoid"); + result.mScanSiblings = PR_TRUE; + result.mTextRunCanCrossFrameBoundary = PR_TRUE; + result.mLineBreakerCanCrossFrameBoundary = PR_TRUE; + } else { + result.mFrameToScan = nsnull; + result.mOverflowFrameToScan = nsnull; + result.mTextRunCanCrossFrameBoundary = PR_FALSE; + result.mLineBreakerCanCrossFrameBoundary = PR_FALSE; + } + } + return result; +} + +BuildTextRunsScanner::FindBoundaryResult +BuildTextRunsScanner::FindBoundaries(nsIFrame* aFrame, FindBoundaryState* aState) +{ + nsIAtom* frameType = aFrame->GetType(); + nsTextFrame* textFrame = frameType == nsGkAtoms::textFrame + ? static_cast(aFrame) : nsnull; + if (textFrame) { + if (aState->mLastTextFrame && + textFrame != aState->mLastTextFrame->GetNextInFlow() && + !ContinueTextRunAcrossFrames(aState->mLastTextFrame, textFrame)) { + aState->mSeenTextRunBoundaryOnThisLine = PR_TRUE; + if (aState->mSeenSpaceForLineBreakingOnThisLine) + return FB_FOUND_VALID_TEXTRUN_BOUNDARY; + } + if (!aState->mFirstTextFrame) { + aState->mFirstTextFrame = textFrame; + } + aState->mLastTextFrame = textFrame; + } + + if (aFrame == aState->mStopAtFrame) + return FB_STOPPED_AT_STOP_FRAME; + + if (textFrame) { + if (!aState->mSeenSpaceForLineBreakingOnThisLine) { + const nsTextFragment* frag = textFrame->GetFragment(); + PRUint32 start = textFrame->GetContentOffset(); + const void* text = frag->Is2b() + ? static_cast(frag->Get2b() + start) + : static_cast(frag->Get1b() + start); + if (TextContainsLineBreakerWhiteSpace(text, textFrame->GetContentLength(), + frag->Is2b())) { + aState->mSeenSpaceForLineBreakingOnThisLine = PR_TRUE; + if (aState->mSeenTextRunBoundaryOnLaterLine) + return FB_FOUND_VALID_TEXTRUN_BOUNDARY; + } + } + return FB_CONTINUE; + } + + FrameTextTraversal traversal = + CanTextCrossFrameBoundary(aFrame, frameType); + if (!traversal.mTextRunCanCrossFrameBoundary) { + aState->mSeenTextRunBoundaryOnThisLine = PR_TRUE; + if (aState->mSeenSpaceForLineBreakingOnThisLine) + return FB_FOUND_VALID_TEXTRUN_BOUNDARY; + } + + for (nsIFrame* f = traversal.NextFrameToScan(); f; + f = traversal.NextFrameToScan()) { + FindBoundaryResult result = FindBoundaries(f, aState); + if (result != FB_CONTINUE) + return result; + } + + if (!traversal.mTextRunCanCrossFrameBoundary) { + aState->mSeenTextRunBoundaryOnThisLine = PR_TRUE; + if (aState->mSeenSpaceForLineBreakingOnThisLine) + return FB_FOUND_VALID_TEXTRUN_BOUNDARY; + } + + return FB_CONTINUE; +} + +// build text runs for the 200 lines following aForFrame, and stop after that +// when we get a chance. +#define NUM_LINES_TO_BUILD_TEXT_RUNS 200 + +/** + * General routine for building text runs. This is hairy because of the need + * to build text runs that span content nodes. + * + * @param aForFrameLine the line containing aForFrame; if null, we'll figure + * out the line (slowly) + * @param aLineContainer the line container containing aForFrame; if null, + * we'll walk the ancestors to find it. It's required to be non-null when + * aForFrameLine is non-null. + */ +static void +BuildTextRuns(gfxContext* aContext, nsTextFrame* aForFrame, + nsIFrame* aLineContainer, + const nsLineList::iterator* aForFrameLine) +{ + NS_ASSERTION(aForFrame || aLineContainer, + "One of aForFrame or aLineContainer must be set!"); + NS_ASSERTION(!aForFrameLine || aLineContainer, + "line but no line container"); + + if (!aLineContainer) { + aLineContainer = FindLineContainer(aForFrame); + } else { + NS_ASSERTION(!aForFrame || + (aLineContainer == FindLineContainer(aForFrame) || + (aLineContainer->GetType() == nsGkAtoms::letterFrame && + aLineContainer->GetStyleDisplay()->IsFloating())), + "Wrong line container hint"); + } + + nsPresContext* presContext = aLineContainer->PresContext(); + BuildTextRunsScanner scanner(presContext, aContext, aLineContainer); + + nsBlockFrame* block = nsLayoutUtils::GetAsBlock(aLineContainer); + + if (!block) { + NS_ASSERTION(!aLineContainer->GetPrevInFlow() && !aLineContainer->GetNextInFlow(), + "Breakable non-block line containers not supported"); + // Just loop through all the children of the linecontainer ... it's really + // just one line + scanner.SetAtStartOfLine(); + scanner.SetCommonAncestorWithLastFrame(nsnull); + nsIFrame* child = aLineContainer->GetFirstChild(nsnull); + while (child) { + scanner.ScanFrame(child); + child = child->GetNextSibling(); + } + // Set mStartOfLine so FlushFrames knows its textrun ends a line + scanner.SetAtStartOfLine(); + scanner.FlushFrames(PR_TRUE, PR_FALSE); + return; + } + + // Find the line containing aForFrame + + PRBool isValid = PR_TRUE; + nsBlockInFlowLineIterator backIterator(block, &isValid); + if (aForFrameLine) { + backIterator = nsBlockInFlowLineIterator(block, *aForFrameLine, PR_FALSE); + } else { + backIterator = nsBlockInFlowLineIterator(block, aForFrame, &isValid); + NS_ASSERTION(isValid, "aForFrame not found in block, someone lied to us"); + NS_ASSERTION(backIterator.GetContainer() == block, + "Someone lied to us about the block"); + } + nsBlockFrame::line_iterator startLine = backIterator.GetLine(); + + // Find a line where we can start building text runs. We choose the last line + // where: + // -- there is a textrun boundary between the start of the line and the + // start of aForFrame + // -- there is a space between the start of the line and the textrun boundary + // (this is so we can be sure the line breaks will be set properly + // on the textruns we construct). + // The possibly-partial text runs up to and including the first space + // are not reconstructed. We construct partial text runs for that text --- + // for the sake of simplifying the code and feeding the linebreaker --- + // but we discard them instead of assigning them to frames. + // This is a little awkward because we traverse lines in the reverse direction + // but we traverse the frames in each line in the forward direction. + nsBlockInFlowLineIterator forwardIterator = backIterator; + nsTextFrame* stopAtFrame = aForFrame; + nsTextFrame* nextLineFirstTextFrame = nsnull; + PRBool seenTextRunBoundaryOnLaterLine = PR_FALSE; + PRBool mayBeginInTextRun = PR_TRUE; + while (PR_TRUE) { + forwardIterator = backIterator; + nsBlockFrame::line_iterator line = backIterator.GetLine(); + if (!backIterator.Prev() || backIterator.GetLine()->IsBlock()) { + mayBeginInTextRun = PR_FALSE; + break; + } + + BuildTextRunsScanner::FindBoundaryState state = { stopAtFrame, nsnull, nsnull, + seenTextRunBoundaryOnLaterLine, PR_FALSE, PR_FALSE }; + nsIFrame* child = line->mFirstChild; + PRBool foundBoundary = PR_FALSE; + PRInt32 i; + for (i = line->GetChildCount() - 1; i >= 0; --i) { + BuildTextRunsScanner::FindBoundaryResult result = + scanner.FindBoundaries(child, &state); + if (result == BuildTextRunsScanner::FB_FOUND_VALID_TEXTRUN_BOUNDARY) { + foundBoundary = PR_TRUE; + break; + } else if (result == BuildTextRunsScanner::FB_STOPPED_AT_STOP_FRAME) { + break; + } + child = child->GetNextSibling(); + } + if (foundBoundary) + break; + if (!stopAtFrame && state.mLastTextFrame && nextLineFirstTextFrame && + !scanner.ContinueTextRunAcrossFrames(state.mLastTextFrame, nextLineFirstTextFrame)) { + // Found a usable textrun boundary at the end of the line + if (state.mSeenSpaceForLineBreakingOnThisLine) + break; + seenTextRunBoundaryOnLaterLine = PR_TRUE; + } else if (state.mSeenTextRunBoundaryOnThisLine) { + seenTextRunBoundaryOnLaterLine = PR_TRUE; + } + stopAtFrame = nsnull; + if (state.mFirstTextFrame) { + nextLineFirstTextFrame = state.mFirstTextFrame; + } + } + scanner.SetSkipIncompleteTextRuns(mayBeginInTextRun); + + // Now iterate over all text frames starting from the current line. First-in-flow + // text frames will be accumulated into textRunFrames as we go. When a + // text run boundary is required we flush textRunFrames ((re)building their + // gfxTextRuns as necessary). + PRBool seenStartLine = PR_FALSE; + PRUint32 linesAfterStartLine = 0; + do { + nsBlockFrame::line_iterator line = forwardIterator.GetLine(); + if (line->IsBlock()) + break; + line->SetInvalidateTextRuns(PR_FALSE); + scanner.SetAtStartOfLine(); + scanner.SetCommonAncestorWithLastFrame(nsnull); + nsIFrame* child = line->mFirstChild; + PRInt32 i; + for (i = line->GetChildCount() - 1; i >= 0; --i) { + scanner.ScanFrame(child); + child = child->GetNextSibling(); + } + if (line.get() == startLine.get()) { + seenStartLine = PR_TRUE; + } + if (seenStartLine) { + ++linesAfterStartLine; + if (linesAfterStartLine >= NUM_LINES_TO_BUILD_TEXT_RUNS && scanner.CanStopOnThisLine()) { + // Don't flush frames; we may be in the middle of a textrun + // that we can't end here. That's OK, we just won't build it. + // Note that we must already have finished the textrun for aForFrame, + // because we've seen the end of a textrun in a line after the line + // containing aForFrame. + scanner.FlushLineBreaks(nsnull); + // This flushes out mMappedFlows and mLineBreakBeforeFrames, which + // silences assertions in the scanner destructor. + scanner.ResetRunInfo(); + return; + } + } + } while (forwardIterator.Next()); + + // Set mStartOfLine so FlushFrames knows its textrun ends a line + scanner.SetAtStartOfLine(); + scanner.FlushFrames(PR_TRUE, PR_FALSE); +} + +static PRUnichar* +ExpandBuffer(PRUnichar* aDest, PRUint8* aSrc, PRUint32 aCount) +{ + while (aCount) { + *aDest = *aSrc; + ++aDest; + ++aSrc; + --aCount; + } + return aDest; +} + +PRBool BuildTextRunsScanner::IsTextRunValidForMappedFlows(gfxTextRun* aTextRun) +{ + if (aTextRun->GetFlags() & nsTextFrameUtils::TEXT_IS_SIMPLE_FLOW) + return mMappedFlows.Length() == 1 && + mMappedFlows[0].mStartFrame == static_cast(aTextRun->GetUserData()) && + mMappedFlows[0].mEndFrame == nsnull; + + TextRunUserData* userData = static_cast(aTextRun->GetUserData()); + if (userData->mMappedFlowCount != PRInt32(mMappedFlows.Length())) + return PR_FALSE; + PRUint32 i; + for (i = 0; i < mMappedFlows.Length(); ++i) { + if (userData->mMappedFlows[i].mStartFrame != mMappedFlows[i].mStartFrame || + PRInt32(userData->mMappedFlows[i].mContentLength) != + mMappedFlows[i].GetContentEnd() - mMappedFlows[i].mStartFrame->GetContentOffset()) + return PR_FALSE; + } + return PR_TRUE; +} + +/** + * This gets called when we need to make a text run for the current list of + * frames. + */ +void BuildTextRunsScanner::FlushFrames(PRBool aFlushLineBreaks, PRBool aSuppressTrailingBreak) +{ + gfxTextRun* textRun = nsnull; + if (!mMappedFlows.IsEmpty()) { + if (!mSkipIncompleteTextRuns && mCurrentFramesAllSameTextRun && + ((mCurrentFramesAllSameTextRun->GetFlags() & nsTextFrameUtils::TEXT_INCOMING_WHITESPACE) != 0) == + ((mCurrentRunContextInfo & nsTextFrameUtils::INCOMING_WHITESPACE) != 0) && + ((mCurrentFramesAllSameTextRun->GetFlags() & gfxTextRunWordCache::TEXT_INCOMING_ARABICCHAR) != 0) == + ((mCurrentRunContextInfo & nsTextFrameUtils::INCOMING_ARABICCHAR) != 0) && + IsTextRunValidForMappedFlows(mCurrentFramesAllSameTextRun)) { + // Optimization: We do not need to (re)build the textrun. + textRun = mCurrentFramesAllSameTextRun; + + // Feed this run's text into the linebreaker to provide context. This also + // updates mNextRunContextInfo appropriately. + SetupBreakSinksForTextRun(textRun, PR_TRUE, PR_FALSE); + mNextRunContextInfo = nsTextFrameUtils::INCOMING_NONE; + if (textRun->GetFlags() & nsTextFrameUtils::TEXT_TRAILING_WHITESPACE) { + mNextRunContextInfo |= nsTextFrameUtils::INCOMING_WHITESPACE; + } + if (textRun->GetFlags() & gfxTextRunWordCache::TEXT_TRAILING_ARABICCHAR) { + mNextRunContextInfo |= nsTextFrameUtils::INCOMING_ARABICCHAR; + } + } else { + nsAutoTArray buffer; + if (!buffer.AppendElements(mMaxTextLength*(mDoubleByteText ? 2 : 1))) + return; + textRun = BuildTextRunForFrames(buffer.Elements()); + } + } + + if (aFlushLineBreaks) { + FlushLineBreaks(aSuppressTrailingBreak ? nsnull : textRun); + } + + mCanStopOnThisLine = PR_TRUE; + ResetRunInfo(); +} + +void BuildTextRunsScanner::FlushLineBreaks(gfxTextRun* aTrailingTextRun) +{ + PRBool trailingLineBreak; + nsresult rv = mLineBreaker.Reset(&trailingLineBreak); + // textRun may be null for various reasons, including because we constructed + // a partial textrun just to get the linebreaker and other state set up + // to build the next textrun. + if (NS_SUCCEEDED(rv) && trailingLineBreak && aTrailingTextRun) { + aTrailingTextRun->SetFlagBits(nsTextFrameUtils::TEXT_HAS_TRAILING_BREAK); + } + + PRUint32 i; + for (i = 0; i < mBreakSinks.Length(); ++i) { + if (!mBreakSinks[i]->mExistingTextRun || mBreakSinks[i]->mChangedBreaks) { + // TODO cause frames associated with the textrun to be reflowed, if they + // aren't being reflowed already! + } + mBreakSinks[i]->Finish(); + } + mBreakSinks.Clear(); + + for (i = 0; i < mTextRunsToDelete.Length(); ++i) { + gfxTextRun* deleteTextRun = mTextRunsToDelete[i]; + gTextRuns->RemoveFromCache(deleteTextRun); + delete deleteTextRun; + } + mTextRunsToDelete.Clear(); +} + +void BuildTextRunsScanner::AccumulateRunInfo(nsTextFrame* aFrame) +{ + NS_ASSERTION(mMaxTextLength <= mMaxTextLength + aFrame->GetContentLength(), "integer overflow"); + mMaxTextLength += aFrame->GetContentLength(); + mDoubleByteText |= aFrame->GetFragment()->Is2b(); + mLastFrame = aFrame; + mCommonAncestorWithLastFrame = aFrame->GetParent(); + + MappedFlow* mappedFlow = &mMappedFlows[mMappedFlows.Length() - 1]; + NS_ASSERTION(mappedFlow->mStartFrame == aFrame || + mappedFlow->GetContentEnd() == aFrame->GetContentOffset(), + "Overlapping or discontiguous frames => BAD"); + mappedFlow->mEndFrame = static_cast(aFrame->GetNextContinuation()); + if (mCurrentFramesAllSameTextRun != aFrame->GetTextRun()) { + mCurrentFramesAllSameTextRun = nsnull; + } + + if (mStartOfLine) { + mLineBreakBeforeFrames.AppendElement(aFrame); + mStartOfLine = PR_FALSE; + } +} + +static nscoord StyleToCoord(const nsStyleCoord& aCoord) +{ + if (eStyleUnit_Coord == aCoord.GetUnit()) { + return aCoord.GetCoordValue(); + } else { + return 0; + } +} + +static PRBool +HasTerminalNewline(const nsTextFrame* aFrame) +{ + if (aFrame->GetContentLength() == 0) + return PR_FALSE; + const nsTextFragment* frag = aFrame->GetFragment(); + return frag->CharAt(aFrame->GetContentEnd() - 1) == '\n'; +} + +PRBool +BuildTextRunsScanner::ContinueTextRunAcrossFrames(nsTextFrame* aFrame1, nsTextFrame* aFrame2) +{ + if (mBidiEnabled && + NS_GET_EMBEDDING_LEVEL(aFrame1) != NS_GET_EMBEDDING_LEVEL(aFrame2)) + return PR_FALSE; + + nsStyleContext* sc1 = aFrame1->GetStyleContext(); + const nsStyleText* textStyle1 = sc1->GetStyleText(); + // If the first frame ends in a preformatted newline, then we end the textrun + // here. This avoids creating giant textruns for an entire plain text file. + // Note that we create a single text frame for a preformatted text node, + // even if it has newlines in it, so typically we won't see trailing newlines + // until after reflow has broken up the frame into one (or more) frames per + // line. That's OK though. + if (textStyle1->NewlineIsSignificant() && HasTerminalNewline(aFrame1)) + return PR_FALSE; + + if (aFrame1->GetContent() == aFrame2->GetContent() && + aFrame1->GetNextInFlow() != aFrame2) { + // aFrame2 must be a non-fluid continuation of aFrame1. This can happen + // sometimes when the unicode-bidi property is used; the bidi resolver + // breaks text into different frames even though the text has the same + // direction. We can't allow these two frames to share the same textrun + // because that would violate our invariant that two flows in the same + // textrun have different content elements. + return PR_FALSE; + } + + nsStyleContext* sc2 = aFrame2->GetStyleContext(); + if (sc1 == sc2) + return PR_TRUE; + const nsStyleFont* fontStyle1 = sc1->GetStyleFont(); + const nsStyleFont* fontStyle2 = sc2->GetStyleFont(); + const nsStyleText* textStyle2 = sc2->GetStyleText(); + return fontStyle1->mFont.BaseEquals(fontStyle2->mFont) && + sc1->GetStyleVisibility()->mLangGroup == sc2->GetStyleVisibility()->mLangGroup && + nsLayoutUtils::GetTextRunFlagsForStyle(sc1, textStyle1, fontStyle1) == + nsLayoutUtils::GetTextRunFlagsForStyle(sc2, textStyle2, fontStyle2); +} + +void BuildTextRunsScanner::ScanFrame(nsIFrame* aFrame) +{ + // First check if we can extend the current mapped frame block. This is common. + if (mMappedFlows.Length() > 0) { + MappedFlow* mappedFlow = &mMappedFlows[mMappedFlows.Length() - 1]; + if (mappedFlow->mEndFrame == aFrame && + (aFrame->GetStateBits() & NS_FRAME_IS_FLUID_CONTINUATION)) { + NS_ASSERTION(aFrame->GetType() == nsGkAtoms::textFrame, + "Flow-sibling of a text frame is not a text frame?"); + + // Don't do this optimization if mLastFrame has a terminal newline... + // it's quite likely preformatted and we might want to end the textrun here. + // This is almost always true: + if (mLastFrame->GetStyleContext() == aFrame->GetStyleContext() && + !HasTerminalNewline(mLastFrame)) { + AccumulateRunInfo(static_cast(aFrame)); + return; + } + } + } + + nsIAtom* frameType = aFrame->GetType(); + // Now see if we can add a new set of frames to the current textrun + if (frameType == nsGkAtoms::textFrame) { + nsTextFrame* frame = static_cast(aFrame); + + if (mLastFrame) { + if (!ContinueTextRunAcrossFrames(mLastFrame, frame)) { + FlushFrames(PR_FALSE, PR_FALSE); + } else { + if (mLastFrame->GetContent() == frame->GetContent()) { + AccumulateRunInfo(frame); + return; + } + } + } + + MappedFlow* mappedFlow = mMappedFlows.AppendElement(); + if (!mappedFlow) + return; + + mappedFlow->mStartFrame = frame; + mappedFlow->mAncestorControllingInitialBreak = mCommonAncestorWithLastFrame; + + AccumulateRunInfo(frame); + if (mMappedFlows.Length() == 1) { + mCurrentFramesAllSameTextRun = frame->GetTextRun(); + mCurrentRunContextInfo = mNextRunContextInfo; + } + return; + } + + FrameTextTraversal traversal = + CanTextCrossFrameBoundary(aFrame, frameType); + PRBool isBR = frameType == nsGkAtoms::brFrame; + if (!traversal.mLineBreakerCanCrossFrameBoundary) { + // BR frames are special. We do not need or want to record a break opportunity + // before a BR frame. + FlushFrames(PR_TRUE, isBR); + mCommonAncestorWithLastFrame = aFrame; + mNextRunContextInfo &= ~nsTextFrameUtils::INCOMING_WHITESPACE; + mStartOfLine = PR_FALSE; + } else if (!traversal.mTextRunCanCrossFrameBoundary) { + FlushFrames(PR_FALSE, PR_FALSE); + } + + for (nsIFrame* f = traversal.NextFrameToScan(); f; + f = traversal.NextFrameToScan()) { + ScanFrame(f); + } + + if (!traversal.mLineBreakerCanCrossFrameBoundary) { + // Really if we're a BR frame this is unnecessary since descendInto will be + // false. In fact this whole "if" statement should move into the descendInto. + FlushFrames(PR_TRUE, isBR); + mCommonAncestorWithLastFrame = aFrame; + mNextRunContextInfo &= ~nsTextFrameUtils::INCOMING_WHITESPACE; + } else if (!traversal.mTextRunCanCrossFrameBoundary) { + FlushFrames(PR_FALSE, PR_FALSE); + } + + LiftCommonAncestorWithLastFrameToParent(aFrame->GetParent()); +} + +nsTextFrame* +BuildTextRunsScanner::GetNextBreakBeforeFrame(PRUint32* aIndex) +{ + PRUint32 index = *aIndex; + if (index >= mLineBreakBeforeFrames.Length()) + return nsnull; + *aIndex = index + 1; + return static_cast(mLineBreakBeforeFrames.ElementAt(index)); +} + +static PRUint32 +GetSpacingFlags(nscoord spacing) +{ + return spacing ? gfxTextRunFactory::TEXT_ENABLE_SPACING : 0; +} + +static gfxFontGroup* +GetFontGroupForFrame(nsIFrame* aFrame, + nsIFontMetrics** aOutFontMetrics = nsnull) +{ + if (aOutFontMetrics) + *aOutFontMetrics = nsnull; + + nsCOMPtr metrics; + nsLayoutUtils::GetFontMetricsForFrame(aFrame, getter_AddRefs(metrics)); + + if (!metrics) + return nsnull; + + nsIFontMetrics* metricsRaw = metrics; + if (aOutFontMetrics) { + *aOutFontMetrics = metricsRaw; + NS_ADDREF(*aOutFontMetrics); + } + nsIThebesFontMetrics* fm = static_cast(metricsRaw); + // XXX this is a bit bogus, we're releasing 'metrics' so the returned font-group + // might actually be torn down, although because of the way the device context + // caches font metrics, this seems to not actually happen. But we should fix + // this. + return fm->GetThebesFontGroup(); +} + +static already_AddRefed +GetReferenceRenderingContext(nsTextFrame* aTextFrame, nsIRenderingContext* aRC) +{ + nsCOMPtr tmp = aRC; + if (!tmp) { + nsresult rv = aTextFrame->PresContext()->PresShell()-> + CreateRenderingContext(aTextFrame, getter_AddRefs(tmp)); + if (NS_FAILED(rv)) + return nsnull; + } + + gfxContext* ctx = tmp->ThebesContext(); + NS_ADDREF(ctx); + return ctx; +} + +/** + * The returned textrun must be released via gfxTextRunCache::ReleaseTextRun + * or gfxTextRunCache::AutoTextRun. + */ +static gfxTextRun* +GetHyphenTextRun(gfxTextRun* aTextRun, gfxContext* aContext, nsTextFrame* aTextFrame) +{ + nsRefPtr ctx = aContext; + if (!ctx) { + ctx = GetReferenceRenderingContext(aTextFrame, nsnull); + } + if (!ctx) + return nsnull; + + gfxFontGroup* fontGroup = aTextRun->GetFontGroup(); + PRUint32 flags = gfxFontGroup::TEXT_IS_PERSISTENT; + + // only use U+2010 if it is supported by the first font in the group; + // it's better to use ASCII '-' from the primary font than to fall back to U+2010 + // from some other, possibly poorly-matching face + static const PRUnichar unicodeHyphen = 0x2010; + gfxFont *font = fontGroup->GetFontAt(0); + if (font && font->HasCharacter(unicodeHyphen)) { + return gfxTextRunCache::MakeTextRun(&unicodeHyphen, 1, fontGroup, ctx, + aTextRun->GetAppUnitsPerDevUnit(), flags); + } + + static const PRUint8 dash = '-'; + return gfxTextRunCache::MakeTextRun(&dash, 1, fontGroup, ctx, + aTextRun->GetAppUnitsPerDevUnit(), + flags); +} + +static gfxFont::Metrics +GetFirstFontMetrics(gfxFontGroup* aFontGroup) +{ + if (!aFontGroup) + return gfxFont::Metrics(); + gfxFont* font = aFontGroup->GetFontAt(0); + if (!font) + return gfxFont::Metrics(); + return font->GetMetrics(); +} + +PR_STATIC_ASSERT(NS_STYLE_WHITESPACE_NORMAL == 0); +PR_STATIC_ASSERT(NS_STYLE_WHITESPACE_PRE == 1); +PR_STATIC_ASSERT(NS_STYLE_WHITESPACE_NOWRAP == 2); +PR_STATIC_ASSERT(NS_STYLE_WHITESPACE_PRE_WRAP == 3); +PR_STATIC_ASSERT(NS_STYLE_WHITESPACE_PRE_LINE == 4); + +static const nsTextFrameUtils::CompressionMode CSSWhitespaceToCompressionMode[] = +{ + nsTextFrameUtils::COMPRESS_WHITESPACE_NEWLINE, // normal + nsTextFrameUtils::COMPRESS_NONE, // pre + nsTextFrameUtils::COMPRESS_WHITESPACE_NEWLINE, // nowrap + nsTextFrameUtils::COMPRESS_NONE, // pre-wrap + nsTextFrameUtils::COMPRESS_WHITESPACE // pre-line +}; + +gfxTextRun* +BuildTextRunsScanner::BuildTextRunForFrames(void* aTextBuffer) +{ + gfxSkipCharsBuilder builder; + + const void* textPtr = aTextBuffer; + PRBool anySmallcapsStyle = PR_FALSE; + PRBool anyTextTransformStyle = PR_FALSE; + PRInt32 endOfLastContent = 0; + PRUint32 textFlags = nsTextFrameUtils::TEXT_NO_BREAKS; + + if (mCurrentRunContextInfo & nsTextFrameUtils::INCOMING_WHITESPACE) { + textFlags |= nsTextFrameUtils::TEXT_INCOMING_WHITESPACE; + } + if (mCurrentRunContextInfo & nsTextFrameUtils::INCOMING_ARABICCHAR) { + textFlags |= gfxTextRunWordCache::TEXT_INCOMING_ARABICCHAR; + } + + nsAutoTArray textBreakPoints; + TextRunUserData dummyData; + TextRunMappedFlow dummyMappedFlow; + + TextRunUserData* userData; + TextRunUserData* userDataToDestroy; + // If the situation is particularly simple (and common) we don't need to + // allocate userData. + if (mMappedFlows.Length() == 1 && !mMappedFlows[0].mEndFrame && + mMappedFlows[0].mStartFrame->GetContentOffset() == 0) { + userData = &dummyData; + userDataToDestroy = nsnull; + dummyData.mMappedFlows = &dummyMappedFlow; + } else { + userData = static_cast + (nsMemory::Alloc(sizeof(TextRunUserData) + mMappedFlows.Length()*sizeof(TextRunMappedFlow))); + userDataToDestroy = userData; + userData->mMappedFlows = reinterpret_cast(userData + 1); + } + userData->mMappedFlowCount = mMappedFlows.Length(); + userData->mLastFlowIndex = 0; + + PRUint32 currentTransformedTextOffset = 0; + + PRUint32 nextBreakIndex = 0; + nsTextFrame* nextBreakBeforeFrame = GetNextBreakBeforeFrame(&nextBreakIndex); + PRBool enabledJustification = mLineContainer && + mLineContainer->GetStyleText()->mTextAlign == NS_STYLE_TEXT_ALIGN_JUSTIFY; + + PRUint32 i; + const nsStyleText* textStyle = nsnull; + const nsStyleFont* fontStyle = nsnull; + nsStyleContext* lastStyleContext = nsnull; + for (i = 0; i < mMappedFlows.Length(); ++i) { + MappedFlow* mappedFlow = &mMappedFlows[i]; + nsTextFrame* f = mappedFlow->mStartFrame; + + lastStyleContext = f->GetStyleContext(); + // Detect use of text-transform or font-variant anywhere in the run + textStyle = f->GetStyleText(); + if (NS_STYLE_TEXT_TRANSFORM_NONE != textStyle->mTextTransform) { + anyTextTransformStyle = PR_TRUE; + } + textFlags |= GetSpacingFlags(StyleToCoord(textStyle->mLetterSpacing)); + textFlags |= GetSpacingFlags(textStyle->mWordSpacing); + nsTextFrameUtils::CompressionMode compression = + CSSWhitespaceToCompressionMode[textStyle->mWhiteSpace]; + if (enabledJustification && !textStyle->WhiteSpaceIsSignificant()) { + textFlags |= gfxTextRunFactory::TEXT_ENABLE_SPACING; + } + fontStyle = f->GetStyleFont(); + if (NS_STYLE_FONT_VARIANT_SMALL_CAPS == fontStyle->mFont.variant) { + anySmallcapsStyle = PR_TRUE; + } + + // Figure out what content is included in this flow. + nsIContent* content = f->GetContent(); + const nsTextFragment* frag = f->GetFragment(); + PRInt32 contentStart = mappedFlow->mStartFrame->GetContentOffset(); + PRInt32 contentEnd = mappedFlow->GetContentEnd(); + PRInt32 contentLength = contentEnd - contentStart; + + TextRunMappedFlow* newFlow = &userData->mMappedFlows[i]; + newFlow->mStartFrame = mappedFlow->mStartFrame; + newFlow->mDOMOffsetToBeforeTransformOffset = builder.GetCharCount() - + mappedFlow->mStartFrame->GetContentOffset(); + newFlow->mContentLength = contentLength; + + while (nextBreakBeforeFrame && nextBreakBeforeFrame->GetContent() == content) { + textBreakPoints.AppendElement( + nextBreakBeforeFrame->GetContentOffset() + newFlow->mDOMOffsetToBeforeTransformOffset); + nextBreakBeforeFrame = GetNextBreakBeforeFrame(&nextBreakIndex); + } + + PRUint32 analysisFlags; + if (frag->Is2b()) { + NS_ASSERTION(mDoubleByteText, "Wrong buffer char size!"); + PRUnichar* bufStart = static_cast(aTextBuffer); + PRUnichar* bufEnd = nsTextFrameUtils::TransformText( + frag->Get2b() + contentStart, contentLength, bufStart, + compression, &mNextRunContextInfo, &builder, &analysisFlags); + aTextBuffer = bufEnd; + } else { + if (mDoubleByteText) { + // Need to expand the text. First transform it into a temporary buffer, + // then expand. + nsAutoTArray tempBuf; + if (!tempBuf.AppendElements(contentLength)) { + DestroyUserData(userDataToDestroy); + return nsnull; + } + PRUint8* bufStart = tempBuf.Elements(); + PRUint8* end = nsTextFrameUtils::TransformText( + reinterpret_cast(frag->Get1b()) + contentStart, contentLength, + bufStart, compression, &mNextRunContextInfo, &builder, &analysisFlags); + aTextBuffer = ExpandBuffer(static_cast(aTextBuffer), + tempBuf.Elements(), end - tempBuf.Elements()); + } else { + PRUint8* bufStart = static_cast(aTextBuffer); + PRUint8* end = nsTextFrameUtils::TransformText( + reinterpret_cast(frag->Get1b()) + contentStart, contentLength, + bufStart, compression, &mNextRunContextInfo, &builder, &analysisFlags); + aTextBuffer = end; + } + } + textFlags |= analysisFlags; + + currentTransformedTextOffset = + (static_cast(aTextBuffer) - static_cast(textPtr)) >> mDoubleByteText; + + endOfLastContent = contentEnd; + } + + // Check for out-of-memory in gfxSkipCharsBuilder + if (!builder.IsOK()) { + DestroyUserData(userDataToDestroy); + return nsnull; + } + + void* finalUserData; + if (userData == &dummyData) { + textFlags |= nsTextFrameUtils::TEXT_IS_SIMPLE_FLOW; + userData = nsnull; + finalUserData = mMappedFlows[0].mStartFrame; + } else { + finalUserData = userData; + } + + PRUint32 transformedLength = currentTransformedTextOffset; + + // Now build the textrun + nsTextFrame* firstFrame = mMappedFlows[0].mStartFrame; + gfxFontGroup* fontGroup = GetFontGroupForFrame(firstFrame); + if (!fontGroup) { + DestroyUserData(userDataToDestroy); + return nsnull; + } + + if (textFlags & nsTextFrameUtils::TEXT_HAS_TAB) { + textFlags |= gfxTextRunFactory::TEXT_ENABLE_SPACING; + } + if (textFlags & nsTextFrameUtils::TEXT_HAS_SHY) { + textFlags |= gfxTextRunFactory::TEXT_ENABLE_HYPHEN_BREAKS; + } + if (mBidiEnabled && (NS_GET_EMBEDDING_LEVEL(firstFrame) & 1)) { + textFlags |= gfxTextRunFactory::TEXT_IS_RTL; + } + if (mNextRunContextInfo & nsTextFrameUtils::INCOMING_WHITESPACE) { + textFlags |= nsTextFrameUtils::TEXT_TRAILING_WHITESPACE; + } + if (mNextRunContextInfo & nsTextFrameUtils::INCOMING_ARABICCHAR) { + textFlags |= gfxTextRunWordCache::TEXT_TRAILING_ARABICCHAR; + } + // ContinueTextRunAcrossFrames guarantees that it doesn't matter which + // frame's style is used, so use the last frame's + textFlags |= nsLayoutUtils::GetTextRunFlagsForStyle(lastStyleContext, + textStyle, fontStyle); + // XXX this is a bit of a hack. For performance reasons, if we're favouring + // performance over quality, don't try to get accurate glyph extents. + if (!(textFlags & gfxTextRunFactory::TEXT_OPTIMIZE_SPEED)) { + textFlags |= gfxTextRunFactory::TEXT_NEED_BOUNDING_BOX; + } + + gfxSkipChars skipChars; + skipChars.TakeFrom(&builder); + // Convert linebreak coordinates to transformed string offsets + NS_ASSERTION(nextBreakIndex == mLineBreakBeforeFrames.Length(), + "Didn't find all the frames to break-before..."); + gfxSkipCharsIterator iter(skipChars); + nsAutoTArray textBreakPointsAfterTransform; + for (i = 0; i < textBreakPoints.Length(); ++i) { + nsTextFrameUtils::AppendLineBreakOffset(&textBreakPointsAfterTransform, + iter.ConvertOriginalToSkipped(textBreakPoints[i])); + } + if (mStartOfLine) { + nsTextFrameUtils::AppendLineBreakOffset(&textBreakPointsAfterTransform, + transformedLength); + } + + // Setup factory chain + nsAutoPtr transformingFactory; + if (anySmallcapsStyle) { + transformingFactory = new nsFontVariantTextRunFactory(); + } + if (anyTextTransformStyle) { + transformingFactory = + new nsCaseTransformTextRunFactory(transformingFactory.forget()); + } + nsTArray styles; + if (transformingFactory) { + iter.SetOriginalOffset(0); + for (i = 0; i < mMappedFlows.Length(); ++i) { + MappedFlow* mappedFlow = &mMappedFlows[i]; + nsTextFrame* f; + for (f = mappedFlow->mStartFrame; f != mappedFlow->mEndFrame; + f = static_cast(f->GetNextContinuation())) { + PRUint32 offset = iter.GetSkippedOffset(); + iter.AdvanceOriginal(f->GetContentLength()); + PRUint32 end = iter.GetSkippedOffset(); + nsStyleContext* sc = f->GetStyleContext(); + PRUint32 j; + for (j = offset; j < end; ++j) { + styles.AppendElement(sc); + } + } + } + textFlags |= nsTextFrameUtils::TEXT_IS_TRANSFORMED; + NS_ASSERTION(iter.GetSkippedOffset() == transformedLength, + "We didn't cover all the characters in the text run!"); + } + + gfxTextRun* textRun; + gfxTextRunFactory::Parameters params = + { mContext, finalUserData, &skipChars, + textBreakPointsAfterTransform.Elements(), textBreakPointsAfterTransform.Length(), + firstFrame->PresContext()->AppUnitsPerDevPixel() }; + + if (mDoubleByteText) { + const PRUnichar* text = static_cast(textPtr); + if (transformingFactory) { + textRun = transformingFactory->MakeTextRun(text, transformedLength, ¶ms, + fontGroup, textFlags, styles.Elements()); + if (textRun) { + // ownership of the factory has passed to the textrun + transformingFactory.forget(); + } + } else { + textRun = MakeTextRun(text, transformedLength, fontGroup, ¶ms, textFlags); + } + } else { + const PRUint8* text = static_cast(textPtr); + textFlags |= gfxFontGroup::TEXT_IS_8BIT; + if (transformingFactory) { + textRun = transformingFactory->MakeTextRun(text, transformedLength, ¶ms, + fontGroup, textFlags, styles.Elements()); + if (textRun) { + // ownership of the factory has passed to the textrun + transformingFactory.forget(); + } + } else { + textRun = MakeTextRun(text, transformedLength, fontGroup, ¶ms, textFlags); + } + } + if (!textRun) { + DestroyUserData(userDataToDestroy); + return nsnull; + } + + // We have to set these up after we've created the textrun, because + // the breaks may be stored in the textrun during this very call. + // This is a bit annoying because it requires another loop over the frames + // making up the textrun, but I don't see a way to avoid this. + SetupBreakSinksForTextRun(textRun, PR_FALSE, mSkipIncompleteTextRuns); + + if (mSkipIncompleteTextRuns) { + mSkipIncompleteTextRuns = !TextContainsLineBreakerWhiteSpace(textPtr, + transformedLength, mDoubleByteText); + // Arrange for this textrun to be deleted the next time the linebreaker + // is flushed out + mTextRunsToDelete.AppendElement(textRun); + // Since we're doing to destroy the user data now, avoid a dangling + // pointer. Strictly speaking we don't need to do this since it should + // not be used (since this textrun will not be used and will be + // itself deleted soon), but it's always better to not have dangling + // pointers around. + textRun->SetUserData(nsnull); + DestroyUserData(userDataToDestroy); + return nsnull; + } + + // Actually wipe out the textruns associated with the mapped frames and associate + // those frames with this text run. + AssignTextRun(textRun); + return textRun; +} + +static PRBool +HasCompressedLeadingWhitespace(nsTextFrame* aFrame, const nsStyleText* aStyleText, + PRInt32 aContentEndOffset, + const gfxSkipCharsIterator& aIterator) +{ + if (!aIterator.IsOriginalCharSkipped()) + return PR_FALSE; + + gfxSkipCharsIterator iter = aIterator; + PRInt32 frameContentOffset = aFrame->GetContentOffset(); + const nsTextFragment* frag = aFrame->GetFragment(); + while (frameContentOffset < aContentEndOffset && iter.IsOriginalCharSkipped()) { + if (IsTrimmableSpace(frag, frameContentOffset, aStyleText)) + return PR_TRUE; + ++frameContentOffset; + iter.AdvanceOriginal(1); + } + return PR_FALSE; +} + +void +BuildTextRunsScanner::SetupBreakSinksForTextRun(gfxTextRun* aTextRun, + PRBool aIsExistingTextRun, + PRBool aSuppressSink) +{ + // textruns have uniform language + nsIAtom* lang = mMappedFlows[0].mStartFrame->GetStyleVisibility()->mLangGroup; + // We keep this pointed at the skip-chars data for the current mappedFlow. + // This lets us cheaply check whether the flow has compressed initial + // whitespace... + gfxSkipCharsIterator iter(aTextRun->GetSkipChars()); + + PRUint32 i; + for (i = 0; i < mMappedFlows.Length(); ++i) { + MappedFlow* mappedFlow = &mMappedFlows[i]; + PRUint32 offset = iter.GetSkippedOffset(); + gfxSkipCharsIterator iterNext = iter; + iterNext.AdvanceOriginal(mappedFlow->GetContentEnd() - + mappedFlow->mStartFrame->GetContentOffset()); + + nsAutoPtr* breakSink = mBreakSinks.AppendElement( + new BreakSink(aTextRun, mContext, offset, aIsExistingTextRun)); + if (!breakSink || !*breakSink) + return; + + PRUint32 length = iterNext.GetSkippedOffset() - offset; + PRUint32 flags = 0; + nsIFrame* initialBreakController = mappedFlow->mAncestorControllingInitialBreak; + if (!initialBreakController) { + initialBreakController = mLineContainer; + } + if (!initialBreakController->GetStyleText()->WhiteSpaceCanWrap()) { + flags |= nsLineBreaker::BREAK_SUPPRESS_INITIAL; + } + nsTextFrame* startFrame = mappedFlow->mStartFrame; + const nsStyleText* textStyle = startFrame->GetStyleText(); + if (!textStyle->WhiteSpaceCanWrap()) { + flags |= nsLineBreaker::BREAK_SUPPRESS_INSIDE; + } + if (aTextRun->GetFlags() & nsTextFrameUtils::TEXT_NO_BREAKS) { + flags |= nsLineBreaker::BREAK_SKIP_SETTING_NO_BREAKS; + } + if (textStyle->mTextTransform == NS_STYLE_TEXT_TRANSFORM_CAPITALIZE) { + flags |= nsLineBreaker::BREAK_NEED_CAPITALIZATION; + } + + if (HasCompressedLeadingWhitespace(startFrame, textStyle, + mappedFlow->GetContentEnd(), iter)) { + mLineBreaker.AppendInvisibleWhitespace(flags); + } + + if (length > 0) { + BreakSink* sink = aSuppressSink ? nsnull : (*breakSink).get(); + if (aTextRun->GetFlags() & gfxFontGroup::TEXT_IS_8BIT) { + mLineBreaker.AppendText(lang, aTextRun->GetText8Bit() + offset, + length, flags, sink); + } else { + mLineBreaker.AppendText(lang, aTextRun->GetTextUnicode() + offset, + length, flags, sink); + } + } + + iter = iterNext; + } +} + +void +BuildTextRunsScanner::AssignTextRun(gfxTextRun* aTextRun) +{ + PRUint32 i; + for (i = 0; i < mMappedFlows.Length(); ++i) { + MappedFlow* mappedFlow = &mMappedFlows[i]; + nsTextFrame* startFrame = mappedFlow->mStartFrame; + nsTextFrame* endFrame = mappedFlow->mEndFrame; + nsTextFrame* f; + for (f = startFrame; f != endFrame; + f = static_cast(f->GetNextContinuation())) { +#ifdef DEBUG_roc + if (f->GetTextRun()) { + gfxTextRun* textRun = f->GetTextRun(); + if (textRun->GetFlags() & nsTextFrameUtils::TEXT_IS_SIMPLE_FLOW) { + if (mMappedFlows[0].mStartFrame != static_cast(textRun->GetUserData())) { + NS_WARNING("REASSIGNING SIMPLE FLOW TEXT RUN!"); + } + } else { + TextRunUserData* userData = + static_cast(textRun->GetUserData()); + + if (PRUint32(userData->mMappedFlowCount) >= mMappedFlows.Length() || + userData->mMappedFlows[userData->mMappedFlowCount - 1].mStartFrame != + mMappedFlows[userData->mMappedFlowCount - 1].mStartFrame) { + NS_WARNING("REASSIGNING MULTIFLOW TEXT RUN (not append)!"); + } + } + } +#endif + f->ClearTextRun(); + f->SetTextRun(aTextRun); + } + // Set this bit now; we can't set it any earlier because + // f->ClearTextRun() might clear it out. + startFrame->AddStateBits(TEXT_IN_TEXTRUN_USER_DATA); + } +} + +gfxSkipCharsIterator +nsTextFrame::EnsureTextRun(gfxContext* aReferenceContext, nsIFrame* aLineContainer, + const nsLineList::iterator* aLine, + PRUint32* aFlowEndInTextRun) +{ + if (mTextRun && (!aLine || !(*aLine)->GetInvalidateTextRuns())) { + if (mTextRun->GetExpirationState()->IsTracked()) { + gTextRuns->MarkUsed(mTextRun); + } + } else { + nsRefPtr ctx = aReferenceContext; + if (!ctx) { + ctx = GetReferenceRenderingContext(this, nsnull); + } + if (ctx) { + BuildTextRuns(ctx, this, aLineContainer, aLine); + } + if (!mTextRun) { + // A text run was not constructed for this frame. This is bad. The caller + // will check mTextRun. + static const gfxSkipChars emptySkipChars; + return gfxSkipCharsIterator(emptySkipChars, 0); + } + } + + if (mTextRun->GetFlags() & nsTextFrameUtils::TEXT_IS_SIMPLE_FLOW) { + if (aFlowEndInTextRun) { + *aFlowEndInTextRun = mTextRun->GetLength(); + } + return gfxSkipCharsIterator(mTextRun->GetSkipChars(), 0, mContentOffset); + } + + TextRunUserData* userData = static_cast(mTextRun->GetUserData()); + // Find the flow that contains us + PRInt32 direction; + PRInt32 startAt = userData->mLastFlowIndex; + // Search first forward and then backward from the current position + for (direction = 1; direction >= -1; direction -= 2) { + PRInt32 i; + for (i = startAt; 0 <= i && i < userData->mMappedFlowCount; i += direction) { + TextRunMappedFlow* flow = &userData->mMappedFlows[i]; + if (flow->mStartFrame->GetContent() == mContent) { + // Since textruns can only contain one flow for a given content element, + // this must be our flow. + userData->mLastFlowIndex = i; + gfxSkipCharsIterator iter(mTextRun->GetSkipChars(), + flow->mDOMOffsetToBeforeTransformOffset, mContentOffset); + if (aFlowEndInTextRun) { + if (i + 1 < userData->mMappedFlowCount) { + gfxSkipCharsIterator end(mTextRun->GetSkipChars()); + *aFlowEndInTextRun = end.ConvertOriginalToSkipped( + flow[1].mStartFrame->GetContentOffset() + flow[1].mDOMOffsetToBeforeTransformOffset); + } else { + *aFlowEndInTextRun = mTextRun->GetLength(); + } + } + return iter; + } + ++flow; + } + startAt = userData->mLastFlowIndex - 1; + } + NS_ERROR("Can't find flow containing this frame???"); + static const gfxSkipChars emptySkipChars; + return gfxSkipCharsIterator(emptySkipChars, 0); +} + +static PRUint32 +GetEndOfTrimmedText(const nsTextFragment* aFrag, const nsStyleText* aStyleText, + PRUint32 aStart, PRUint32 aEnd, + gfxSkipCharsIterator* aIterator) +{ + aIterator->SetSkippedOffset(aEnd); + while (aIterator->GetSkippedOffset() > aStart) { + aIterator->AdvanceSkipped(-1); + if (!IsTrimmableSpace(aFrag, aIterator->GetOriginalOffset(), aStyleText)) + return aIterator->GetSkippedOffset() + 1; + } + return aStart; +} + +nsTextFrame::TrimmedOffsets +nsTextFrame::GetTrimmedOffsets(const nsTextFragment* aFrag, + PRBool aTrimAfter) +{ + NS_ASSERTION(mTextRun, "Need textrun here"); + // This should not be used during reflow. We need our TEXT_REFLOW_FLAGS + // to be set correctly. + NS_ASSERTION(!(GetStateBits() & NS_FRAME_FIRST_REFLOW), + "Can only call this on frames that have been reflowed"); + NS_ASSERTION(!(GetStateBits() & NS_FRAME_IN_REFLOW), + "Can only call this on frames that are not being reflowed"); + + TrimmedOffsets offsets = { GetContentOffset(), GetContentLength() }; + const nsStyleText* textStyle = GetStyleText(); + // Note that pre-line newlines should still allow us to trim spaces + // for display + if (textStyle->WhiteSpaceIsSignificant()) + return offsets; + + if (GetStateBits() & TEXT_START_OF_LINE) { + PRInt32 whitespaceCount = + GetTrimmableWhitespaceCount(aFrag, + offsets.mStart, offsets.mLength, 1); + offsets.mStart += whitespaceCount; + offsets.mLength -= whitespaceCount; + } + + if (aTrimAfter && (GetStateBits() & TEXT_END_OF_LINE)) { + // This treats a trailing 'pre-line' newline as trimmable. That's fine, + // it's actually what we want since we want whitespace before it to + // be trimmed. + PRInt32 whitespaceCount = + GetTrimmableWhitespaceCount(aFrag, + offsets.GetEnd() - 1, offsets.mLength, -1); + offsets.mLength -= whitespaceCount; + } + return offsets; +} + +/* + * Currently only Unicode characters below 0x10000 have their spacing modified + * by justification. If characters above 0x10000 turn out to need + * justification spacing, that will require extra work. Currently, + * this function must not include 0xd800 to 0xdbff because these characters + * are surrogates. + */ +static PRBool IsJustifiableCharacter(const nsTextFragment* aFrag, PRInt32 aPos, + PRBool aLangIsCJ) +{ + PRUnichar ch = aFrag->CharAt(aPos); + if (ch == '\n' || ch == '\t') + return PR_TRUE; + if (ch == ' ') { + // Don't justify spaces that are combined with diacriticals + if (!aFrag->Is2b()) + return PR_TRUE; + return !nsTextFrameUtils::IsSpaceCombiningSequenceTail( + aFrag->Get2b() + aPos + 1, aFrag->GetLength() - (aPos + 1)); + } + if (ch < 0x2150u) + return PR_FALSE; + if (aLangIsCJ && ( + (0x2150u <= ch && ch <= 0x22ffu) || // Number Forms, Arrows, Mathematical Operators + (0x2460u <= ch && ch <= 0x24ffu) || // Enclosed Alphanumerics + (0x2580u <= ch && ch <= 0x27bfu) || // Block Elements, Geometric Shapes, Miscellaneous Symbols, Dingbats + (0x27f0u <= ch && ch <= 0x2bffu) || // Supplemental Arrows-A, Braille Patterns, Supplemental Arrows-B, + // Miscellaneous Mathematical Symbols-B, Supplemental Mathematical Operators, + // Miscellaneous Symbols and Arrows + (0x2e80u <= ch && ch <= 0x312fu) || // CJK Radicals Supplement, CJK Radicals Supplement, + // Ideographic Description Characters, CJK Symbols and Punctuation, + // Hiragana, Katakana, Bopomofo + (0x3190u <= ch && ch <= 0xabffu) || // Kanbun, Bopomofo Extended, Katakana Phonetic Extensions, + // Enclosed CJK Letters and Months, CJK Compatibility, + // CJK Unified Ideographs Extension A, Yijing Hexagram Symbols, + // CJK Unified Ideographs, Yi Syllables, Yi Radicals + (0xf900u <= ch && ch <= 0xfaffu) || // CJK Compatibility Ideographs + (0xff5eu <= ch && ch <= 0xff9fu) // Halfwidth and Fullwidth Forms(a part) + )) + return PR_TRUE; + return PR_FALSE; +} + +static void ClearMetrics(nsHTMLReflowMetrics& aMetrics) +{ + aMetrics.width = 0; + aMetrics.height = 0; + aMetrics.ascent = 0; +} + +static PRInt32 FindChar(const nsTextFragment* frag, + PRInt32 aOffset, PRInt32 aLength, PRUnichar ch) +{ + PRInt32 i = 0; + if (frag->Is2b()) { + const PRUnichar* str = frag->Get2b() + aOffset; + for (; i < aLength; ++i) { + if (*str == ch) + return i + aOffset; + ++str; + } + } else { + if (PRUint16(ch) <= 0xFF) { + const char* str = frag->Get1b() + aOffset; + const void* p = memchr(str, ch, aLength); + if (p) + return (static_cast(p) - str) + aOffset; + } + } + return -1; +} + +static PRBool IsChineseJapaneseLangGroup(nsIFrame* aFrame) +{ + nsIAtom* langGroup = aFrame->GetStyleVisibility()->mLangGroup; + return langGroup == nsGkAtoms::Japanese + || langGroup == nsGkAtoms::Chinese + || langGroup == nsGkAtoms::Taiwanese + || langGroup == nsGkAtoms::HongKongChinese; +} + +#ifdef DEBUG +static PRBool IsInBounds(const gfxSkipCharsIterator& aStart, PRInt32 aContentLength, + PRUint32 aOffset, PRUint32 aLength) { + if (aStart.GetSkippedOffset() > aOffset) + return PR_FALSE; + if (aContentLength == PR_INT32_MAX) + return PR_TRUE; + gfxSkipCharsIterator iter(aStart); + iter.AdvanceOriginal(aContentLength); + return iter.GetSkippedOffset() >= aOffset + aLength; +} +#endif + +class NS_STACK_CLASS PropertyProvider : public gfxTextRun::PropertyProvider { +public: + /** + * Use this constructor for reflow, when we don't know what text is + * really mapped by the frame and we have a lot of other data around. + * + * @param aLength can be PR_INT32_MAX to indicate we cover all the text + * associated with aFrame up to where its flow chain ends in the given + * textrun. If PR_INT32_MAX is passed, justification and hyphen-related methods + * cannot be called, nor can GetOriginalLength(). + */ + PropertyProvider(gfxTextRun* aTextRun, const nsStyleText* aTextStyle, + const nsTextFragment* aFrag, nsTextFrame* aFrame, + const gfxSkipCharsIterator& aStart, PRInt32 aLength, + nsIFrame* aLineContainer, + nscoord aOffsetFromBlockOriginForTabs) + : mTextRun(aTextRun), mFontGroup(nsnull), + mTextStyle(aTextStyle), mFrag(aFrag), + mLineContainer(aLineContainer), + mFrame(aFrame), mStart(aStart), mTempIterator(aStart), + mTabWidths(nsnull), mLength(aLength), + mWordSpacing(mTextStyle->mWordSpacing), + mLetterSpacing(StyleToCoord(mTextStyle->mLetterSpacing)), + mJustificationSpacing(0), + mHyphenWidth(-1), + mOffsetFromBlockOriginForTabs(aOffsetFromBlockOriginForTabs), + mReflowing(PR_TRUE) + { + NS_ASSERTION(mStart.IsInitialized(), "Start not initialized?"); + } + + /** + * Use this constructor after the frame has been reflowed and we don't + * have other data around. Gets everything from the frame. EnsureTextRun + * *must* be called before this!!! + */ + PropertyProvider(nsTextFrame* aFrame, const gfxSkipCharsIterator& aStart) + : mTextRun(aFrame->GetTextRun()), mFontGroup(nsnull), + mTextStyle(aFrame->GetStyleText()), + mFrag(aFrame->GetFragment()), + mLineContainer(nsnull), + mFrame(aFrame), mStart(aStart), mTempIterator(aStart), + mTabWidths(nsnull), + mLength(aFrame->GetContentLength()), + mWordSpacing(mTextStyle->mWordSpacing), + mLetterSpacing(StyleToCoord(mTextStyle->mLetterSpacing)), + mJustificationSpacing(0), + mHyphenWidth(-1), + mOffsetFromBlockOriginForTabs(0), + mReflowing(PR_FALSE) + { + NS_ASSERTION(mTextRun, "Textrun not initialized!"); + } + + // Call this after construction if you're not going to reflow the text + void InitializeForDisplay(PRBool aTrimAfter); + + virtual void GetSpacing(PRUint32 aStart, PRUint32 aLength, Spacing* aSpacing); + virtual gfxFloat GetHyphenWidth(); + virtual void GetHyphenationBreaks(PRUint32 aStart, PRUint32 aLength, + PRPackedBool* aBreakBefore); + + void GetSpacingInternal(PRUint32 aStart, PRUint32 aLength, Spacing* aSpacing, + PRBool aIgnoreTabs); + + /** + * Count the number of justifiable characters in the given DOM range + */ + PRUint32 ComputeJustifiableCharacters(PRInt32 aOffset, PRInt32 aLength); + /** + * Find the start and end of the justifiable characters. Does not depend on the + * position of aStart or aEnd, although it's most efficient if they are near the + * start and end of the text frame. + */ + void FindJustificationRange(gfxSkipCharsIterator* aStart, + gfxSkipCharsIterator* aEnd); + + const nsStyleText* GetStyleText() { return mTextStyle; } + nsTextFrame* GetFrame() { return mFrame; } + // This may not be equal to the frame offset/length in because we may have + // adjusted for whitespace trimming according to the state bits set in the frame + // (for the static provider) + const gfxSkipCharsIterator& GetStart() { return mStart; } + // May return PR_INT32_MAX if that was given to the constructor + PRUint32 GetOriginalLength() { + NS_ASSERTION(mLength != PR_INT32_MAX, "Length not known"); + return mLength; + } + const nsTextFragment* GetFragment() { return mFrag; } + + gfxFontGroup* GetFontGroup() { + if (!mFontGroup) + InitFontGroupAndFontMetrics(); + return mFontGroup; + } + + nsIFontMetrics* GetFontMetrics() { + if (!mFontMetrics) + InitFontGroupAndFontMetrics(); + return mFontMetrics; + } + + gfxFloat* GetTabWidths(PRUint32 aTransformedStart, PRUint32 aTransformedLength); + + const gfxSkipCharsIterator& GetEndHint() { return mTempIterator; } + +protected: + void SetupJustificationSpacing(); + + void InitFontGroupAndFontMetrics() { + mFontGroup = GetFontGroupForFrame(mFrame, getter_AddRefs(mFontMetrics)); + } + + gfxTextRun* mTextRun; + gfxFontGroup* mFontGroup; + nsCOMPtr mFontMetrics; + const nsStyleText* mTextStyle; + const nsTextFragment* mFrag; + nsIFrame* mLineContainer; + nsTextFrame* mFrame; + gfxSkipCharsIterator mStart; // Offset in original and transformed string + gfxSkipCharsIterator mTempIterator; + + // Widths for each transformed string character, 0 for non-tab characters. + // Either null, or pointing to the frame's tabWidthProperty. + nsTArray* mTabWidths; + + PRInt32 mLength; // DOM string length, may be PR_INT32_MAX + gfxFloat mWordSpacing; // space for each whitespace char + gfxFloat mLetterSpacing; // space for each letter + gfxFloat mJustificationSpacing; + gfxFloat mHyphenWidth; + gfxFloat mOffsetFromBlockOriginForTabs; + PRPackedBool mReflowing; +}; + +PRUint32 +PropertyProvider::ComputeJustifiableCharacters(PRInt32 aOffset, PRInt32 aLength) +{ + // Scan non-skipped characters and count justifiable chars. + nsSkipCharsRunIterator + run(mStart, nsSkipCharsRunIterator::LENGTH_INCLUDES_SKIPPED, aLength); + run.SetOriginalOffset(aOffset); + PRUint32 justifiableChars = 0; + PRBool isCJK = IsChineseJapaneseLangGroup(mFrame); + while (run.NextRun()) { + PRInt32 i; + for (i = 0; i < run.GetRunLength(); ++i) { + justifiableChars += + IsJustifiableCharacter(mFrag, run.GetOriginalOffset() + i, isCJK); + } + } + return justifiableChars; +} + +/** + * Finds the offset of the first character of the cluster containing aPos + */ +static void FindClusterStart(gfxTextRun* aTextRun, PRInt32 aOriginalStart, + gfxSkipCharsIterator* aPos) +{ + while (aPos->GetOriginalOffset() > aOriginalStart) { + if (aPos->IsOriginalCharSkipped() || + aTextRun->IsClusterStart(aPos->GetSkippedOffset())) { + break; + } + aPos->AdvanceOriginal(-1); + } +} + +/** + * Finds the offset of the last character of the cluster containing aPos + */ +static void FindClusterEnd(gfxTextRun* aTextRun, PRInt32 aOriginalEnd, + gfxSkipCharsIterator* aPos) +{ + NS_PRECONDITION(aPos->GetOriginalOffset() < aOriginalEnd, + "character outside string"); + aPos->AdvanceOriginal(1); + while (aPos->GetOriginalOffset() < aOriginalEnd) { + if (aPos->IsOriginalCharSkipped() || + aTextRun->IsClusterStart(aPos->GetSkippedOffset())) { + break; + } + aPos->AdvanceOriginal(1); + } + aPos->AdvanceOriginal(-1); +} + +// aStart, aLength in transformed string offsets +void +PropertyProvider::GetSpacing(PRUint32 aStart, PRUint32 aLength, + Spacing* aSpacing) +{ + GetSpacingInternal(aStart, aLength, aSpacing, + (mTextRun->GetFlags() & nsTextFrameUtils::TEXT_HAS_TAB) == 0); +} + +static PRBool +CanAddSpacingAfter(gfxTextRun* aTextRun, PRUint32 aOffset) +{ + if (aOffset + 1 >= aTextRun->GetLength()) + return PR_TRUE; + return aTextRun->IsClusterStart(aOffset + 1) && + aTextRun->IsLigatureGroupStart(aOffset + 1); +} + +void +PropertyProvider::GetSpacingInternal(PRUint32 aStart, PRUint32 aLength, + Spacing* aSpacing, PRBool aIgnoreTabs) +{ + NS_PRECONDITION(IsInBounds(mStart, mLength, aStart, aLength), "Range out of bounds"); + + PRUint32 index; + for (index = 0; index < aLength; ++index) { + aSpacing[index].mBefore = 0.0; + aSpacing[index].mAfter = 0.0; + } + + // Find our offset into the original+transformed string + gfxSkipCharsIterator start(mStart); + start.SetSkippedOffset(aStart); + + // First, compute the word and letter spacing + if (mWordSpacing || mLetterSpacing) { + // Iterate over non-skipped characters + nsSkipCharsRunIterator + run(start, nsSkipCharsRunIterator::LENGTH_UNSKIPPED_ONLY, aLength); + while (run.NextRun()) { + PRUint32 runOffsetInSubstring = run.GetSkippedOffset() - aStart; + PRInt32 i; + gfxSkipCharsIterator iter = run.GetPos(); + for (i = 0; i < run.GetRunLength(); ++i) { + if (CanAddSpacingAfter(mTextRun, run.GetSkippedOffset() + i)) { + // End of a cluster, not in a ligature: put letter-spacing after it + aSpacing[runOffsetInSubstring + i].mAfter += mLetterSpacing; + } + if (IsCSSWordSpacingSpace(mFrag, i + run.GetOriginalOffset(), + mTextStyle)) { + // It kinda sucks, but space characters can be part of clusters, + // and even still be whitespace (I think!) + iter.SetSkippedOffset(run.GetSkippedOffset() + i); + FindClusterEnd(mTextRun, run.GetOriginalOffset() + run.GetRunLength(), + &iter); + aSpacing[iter.GetSkippedOffset() - aStart].mAfter += mWordSpacing; + } + } + } + } + + // Now add tab spacing, if there is any + if (!aIgnoreTabs) { + gfxFloat* tabs = GetTabWidths(aStart, aLength); + if (tabs) { + for (index = 0; index < aLength; ++index) { + aSpacing[index].mAfter += tabs[index]; + } + } + } + + // Now add in justification spacing + if (mJustificationSpacing) { + gfxFloat halfJustificationSpace = mJustificationSpacing/2; + // Scan non-skipped characters and adjust justifiable chars, adding + // justification space on either side of the cluster + PRBool isCJK = IsChineseJapaneseLangGroup(mFrame); + gfxSkipCharsIterator justificationStart(mStart), justificationEnd(mStart); + FindJustificationRange(&justificationStart, &justificationEnd); + + nsSkipCharsRunIterator + run(start, nsSkipCharsRunIterator::LENGTH_UNSKIPPED_ONLY, aLength); + while (run.NextRun()) { + PRInt32 i; + gfxSkipCharsIterator iter = run.GetPos(); + PRInt32 runOriginalOffset = run.GetOriginalOffset(); + for (i = 0; i < run.GetRunLength(); ++i) { + PRInt32 iterOriginalOffset = runOriginalOffset + i; + if (IsJustifiableCharacter(mFrag, iterOriginalOffset, isCJK)) { + iter.SetOriginalOffset(iterOriginalOffset); + FindClusterStart(mTextRun, runOriginalOffset, &iter); + PRUint32 clusterFirstChar = iter.GetSkippedOffset(); + FindClusterEnd(mTextRun, runOriginalOffset + run.GetRunLength(), &iter); + PRUint32 clusterLastChar = iter.GetSkippedOffset(); + // Only apply justification to characters before justificationEnd + if (clusterFirstChar >= justificationStart.GetSkippedOffset() && + clusterLastChar < justificationEnd.GetSkippedOffset()) { + aSpacing[clusterFirstChar - aStart].mBefore += halfJustificationSpace; + aSpacing[clusterLastChar - aStart].mAfter += halfJustificationSpace; + } + } + } + } + } +} + +static void TabWidthDestructor(void* aObject, nsIAtom* aProp, void* aValue, + void* aData) +{ + delete static_cast*>(aValue); +} + +static gfxFloat +ComputeTabWidthAppUnits(nsIFrame* aLineContainer, gfxTextRun* aTextRun) +{ + // Round the space width when converting to appunits the same way + // textruns do + gfxFloat spaceWidthAppUnits = + NS_roundf(GetFirstFontMetrics( + GetFontGroupForFrame(aLineContainer)).spaceWidth * + aTextRun->GetAppUnitsPerDevUnit()); + return 8*spaceWidthAppUnits; +} + +// aX and the result are in whole appunits. +static gfxFloat +AdvanceToNextTab(gfxFloat aX, nsIFrame* aLineContainer, + gfxTextRun* aTextRun, gfxFloat* aCachedTabWidth) +{ + if (*aCachedTabWidth < 0) { + *aCachedTabWidth = ComputeTabWidthAppUnits(aLineContainer, aTextRun); + } + + // Advance aX to the next multiple of *aCachedTabWidth. We must advance + // by at least 1 appunit. + // XXX should we make this 1 CSS pixel? + return NS_ceil((aX + 1)/(*aCachedTabWidth))*(*aCachedTabWidth); +} + +gfxFloat* +PropertyProvider::GetTabWidths(PRUint32 aStart, PRUint32 aLength) +{ + if (!mTabWidths) { + if (!mReflowing) { + mTabWidths = static_cast*> + (mFrame->GetProperty(nsGkAtoms::tabWidthProperty)); + if (!mTabWidths) { + NS_WARNING("We need precomputed tab widths, but they're not here..."); + return nsnull; + } + } else { + if (!mLineContainer) { + // Intrinsic width computation does its own tab processing. We + // just don't do anything here. + return nsnull; + } + + nsAutoPtr > tabs(new nsTArray()); + if (!tabs) + return nsnull; + nsresult rv = mFrame->SetProperty(nsGkAtoms::tabWidthProperty, tabs, + TabWidthDestructor, nsnull); + if (NS_FAILED(rv)) + return nsnull; + mTabWidths = tabs.forget(); + } + } + + PRUint32 startOffset = mStart.GetSkippedOffset(); + PRUint32 tabsEnd = startOffset + mTabWidths->Length(); + if (tabsEnd < aStart + aLength) { + if (!mReflowing) { + NS_WARNING("We need precomputed tab widths, but we don't have enough..."); + return nsnull; + } + + if (!mTabWidths->AppendElements(aStart + aLength - tabsEnd)) + return nsnull; + + gfxFloat tabWidth = -1; + for (PRUint32 i = tabsEnd; i < aStart + aLength; ++i) { + Spacing spacing; + GetSpacingInternal(i, 1, &spacing, PR_TRUE); + mOffsetFromBlockOriginForTabs += spacing.mBefore; + + if (mTextRun->GetChar(i) != '\t') { + (*mTabWidths)[i - startOffset] = 0; + if (mTextRun->IsClusterStart(i)) { + PRUint32 clusterEnd = i + 1; + while (clusterEnd < mTextRun->GetLength() && + !mTextRun->IsClusterStart(clusterEnd)) { + ++clusterEnd; + } + mOffsetFromBlockOriginForTabs += + mTextRun->GetAdvanceWidth(i, clusterEnd - i, nsnull); + } + } else { + double nextTab = AdvanceToNextTab(mOffsetFromBlockOriginForTabs, + mLineContainer, mTextRun, &tabWidth); + (*mTabWidths)[i - startOffset] = nextTab - mOffsetFromBlockOriginForTabs; + mOffsetFromBlockOriginForTabs = nextTab; + } + + mOffsetFromBlockOriginForTabs += spacing.mAfter; + } + } + + return mTabWidths->Elements() + aStart - startOffset; +} + +gfxFloat +PropertyProvider::GetHyphenWidth() +{ + if (mHyphenWidth < 0) { + gfxTextRunCache::AutoTextRun hyphenTextRun(GetHyphenTextRun(mTextRun, nsnull, mFrame)); + mHyphenWidth = mLetterSpacing; + if (hyphenTextRun.get()) { + mHyphenWidth += hyphenTextRun->GetAdvanceWidth(0, hyphenTextRun->GetLength(), nsnull); + } + } + return mHyphenWidth; +} + +void +PropertyProvider::GetHyphenationBreaks(PRUint32 aStart, PRUint32 aLength, + PRPackedBool* aBreakBefore) +{ + NS_PRECONDITION(IsInBounds(mStart, mLength, aStart, aLength), "Range out of bounds"); + NS_PRECONDITION(mLength != PR_INT32_MAX, "Can't call this with undefined length"); + + if (!mTextStyle->WhiteSpaceCanWrap()) { + memset(aBreakBefore, PR_FALSE, aLength); + return; + } + + // Iterate through the original-string character runs + nsSkipCharsRunIterator + run(mStart, nsSkipCharsRunIterator::LENGTH_UNSKIPPED_ONLY, aLength); + run.SetSkippedOffset(aStart); + // We need to visit skipped characters so that we can detect SHY + run.SetVisitSkipped(); + + PRInt32 prevTrailingCharOffset = run.GetPos().GetOriginalOffset() - 1; + PRBool allowHyphenBreakBeforeNextChar = + prevTrailingCharOffset >= mStart.GetOriginalOffset() && + prevTrailingCharOffset < mStart.GetOriginalOffset() + mLength && + mFrag->CharAt(prevTrailingCharOffset) == CH_SHY; + + while (run.NextRun()) { + NS_ASSERTION(run.GetRunLength() > 0, "Shouldn't return zero-length runs"); + if (run.IsSkipped()) { + // Check if there's a soft hyphen which would let us hyphenate before + // the next non-skipped character. Don't look at soft hyphens followed + // by other skipped characters, we won't use them. + allowHyphenBreakBeforeNextChar = + mFrag->CharAt(run.GetOriginalOffset() + run.GetRunLength() - 1) == CH_SHY; + } else { + PRInt32 runOffsetInSubstring = run.GetSkippedOffset() - aStart; + memset(aBreakBefore + runOffsetInSubstring, 0, run.GetRunLength()); + // Don't allow hyphen breaks at the start of the line + aBreakBefore[runOffsetInSubstring] = allowHyphenBreakBeforeNextChar && + (!(mFrame->GetStateBits() & TEXT_START_OF_LINE) || + run.GetSkippedOffset() > mStart.GetSkippedOffset()); + allowHyphenBreakBeforeNextChar = PR_FALSE; + } + } +} + +void +PropertyProvider::InitializeForDisplay(PRBool aTrimAfter) +{ + nsTextFrame::TrimmedOffsets trimmed = + mFrame->GetTrimmedOffsets(mFrag, aTrimAfter); + mStart.SetOriginalOffset(trimmed.mStart); + mLength = trimmed.mLength; + SetupJustificationSpacing(); +} + +static PRUint32 GetSkippedDistance(const gfxSkipCharsIterator& aStart, + const gfxSkipCharsIterator& aEnd) +{ + return aEnd.GetSkippedOffset() - aStart.GetSkippedOffset(); +} + +void +PropertyProvider::FindJustificationRange(gfxSkipCharsIterator* aStart, + gfxSkipCharsIterator* aEnd) +{ + NS_PRECONDITION(mLength != PR_INT32_MAX, "Can't call this with undefined length"); + NS_ASSERTION(aStart && aEnd, "aStart or/and aEnd is null"); + + aStart->SetOriginalOffset(mStart.GetOriginalOffset()); + aEnd->SetOriginalOffset(mStart.GetOriginalOffset() + mLength); + + // Ignore first cluster at start of line for justification purposes + if (mFrame->GetStateBits() & TEXT_START_OF_LINE) { + while (aStart->GetOriginalOffset() < aEnd->GetOriginalOffset()) { + aStart->AdvanceOriginal(1); + if (!aStart->IsOriginalCharSkipped() && + mTextRun->IsClusterStart(aStart->GetSkippedOffset())) + break; + } + } + + // Ignore trailing cluster at end of line for justification purposes + if (mFrame->GetStateBits() & TEXT_END_OF_LINE) { + while (aEnd->GetOriginalOffset() > aStart->GetOriginalOffset()) { + aEnd->AdvanceOriginal(-1); + if (!aEnd->IsOriginalCharSkipped() && + mTextRun->IsClusterStart(aEnd->GetSkippedOffset())) + break; + } + } +} + +void +PropertyProvider::SetupJustificationSpacing() +{ + NS_PRECONDITION(mLength != PR_INT32_MAX, "Can't call this with undefined length"); + + if (!(mFrame->GetStateBits() & TEXT_JUSTIFICATION_ENABLED)) + return; + + gfxSkipCharsIterator start(mStart), end(mStart); + end.AdvanceOriginal(mLength); + gfxSkipCharsIterator realEnd(end); + FindJustificationRange(&start, &end); + + PRInt32 justifiableCharacters = + ComputeJustifiableCharacters(start.GetOriginalOffset(), + end.GetOriginalOffset() - start.GetOriginalOffset()); + if (justifiableCharacters == 0) { + // Nothing to do, nothing is justifiable and we shouldn't have any + // justification space assigned + return; + } + + gfxFloat naturalWidth = + mTextRun->GetAdvanceWidth(mStart.GetSkippedOffset(), + GetSkippedDistance(mStart, realEnd), this); + if (mFrame->GetStateBits() & TEXT_HYPHEN_BREAK) { + gfxTextRunCache::AutoTextRun hyphenTextRun(GetHyphenTextRun(mTextRun, nsnull, mFrame)); + if (hyphenTextRun.get()) { + naturalWidth += + hyphenTextRun->GetAdvanceWidth(0, hyphenTextRun->GetLength(), nsnull); + } + } + gfxFloat totalJustificationSpace = mFrame->GetSize().width - naturalWidth; + if (totalJustificationSpace <= 0) { + // No space available + return; + } + + mJustificationSpacing = totalJustificationSpace/justifiableCharacters; +} + +//---------------------------------------------------------------------- + +// Helper class for managing blinking text + +class nsBlinkTimer : public nsITimerCallback +{ +public: + nsBlinkTimer(); + virtual ~nsBlinkTimer(); + + NS_DECL_ISUPPORTS + + void AddFrame(nsPresContext* aPresContext, nsIFrame* aFrame); + + PRBool RemoveFrame(nsIFrame* aFrame); + + PRInt32 FrameCount(); + + void Start(); + + void Stop(); + + NS_DECL_NSITIMERCALLBACK + + static nsresult AddBlinkFrame(nsPresContext* aPresContext, nsIFrame* aFrame); + static nsresult RemoveBlinkFrame(nsIFrame* aFrame); + + static PRBool GetBlinkIsOff() { return sState == 3; } + +protected: + + struct FrameData { + nsPresContext* mPresContext; // pres context associated with the frame + nsIFrame* mFrame; + + + FrameData(nsPresContext* aPresContext, + nsIFrame* aFrame) + : mPresContext(aPresContext), mFrame(aFrame) {} + }; + + class FrameDataComparator { + public: + PRBool Equals(const FrameData& aTimer, nsIFrame* const& aFrame) const { + return aTimer.mFrame == aFrame; + } + }; + + nsCOMPtr mTimer; + nsTArray mFrames; + nsPresContext* mPresContext; + +protected: + + static nsBlinkTimer* sTextBlinker; + static PRUint32 sState; // 0-2 == on; 3 == off + +}; + +nsBlinkTimer* nsBlinkTimer::sTextBlinker = nsnull; +PRUint32 nsBlinkTimer::sState = 0; + +#ifdef NOISY_BLINK +static PRTime gLastTick; +#endif + +nsBlinkTimer::nsBlinkTimer() +{ +} + +nsBlinkTimer::~nsBlinkTimer() +{ + Stop(); + sTextBlinker = nsnull; +} + +void nsBlinkTimer::Start() +{ + nsresult rv; + mTimer = do_CreateInstance("@mozilla.org/timer;1", &rv); + if (NS_OK == rv) { + mTimer->InitWithCallback(this, 250, nsITimer::TYPE_REPEATING_PRECISE); + } +} + +void nsBlinkTimer::Stop() +{ + if (nsnull != mTimer) { + mTimer->Cancel(); + mTimer = nsnull; + } +} + +NS_IMPL_ISUPPORTS1(nsBlinkTimer, nsITimerCallback) + +void nsBlinkTimer::AddFrame(nsPresContext* aPresContext, nsIFrame* aFrame) { + mFrames.AppendElement(FrameData(aPresContext, aFrame)); + if (1 == mFrames.Length()) { + Start(); + } +} + +PRBool nsBlinkTimer::RemoveFrame(nsIFrame* aFrame) { + mFrames.RemoveElement(aFrame, FrameDataComparator()); + + if (mFrames.IsEmpty()) { + Stop(); + } + return PR_TRUE; +} + +PRInt32 nsBlinkTimer::FrameCount() { + return PRInt32(mFrames.Length()); +} + +NS_IMETHODIMP nsBlinkTimer::Notify(nsITimer *timer) +{ + // Toggle blink state bit so that text code knows whether or not to + // render. All text code shares the same flag so that they all blink + // in unison. + sState = (sState + 1) % 4; + if (sState == 1 || sState == 2) + // States 0, 1, and 2 are all the same. + return NS_OK; + +#ifdef NOISY_BLINK + PRTime now = PR_Now(); + char buf[50]; + PRTime delta; + LL_SUB(delta, now, gLastTick); + gLastTick = now; + PR_snprintf(buf, sizeof(buf), "%lldusec", delta); + printf("%s\n", buf); +#endif + + PRUint32 i, n = mFrames.Length(); + for (i = 0; i < n; i++) { + FrameData& frameData = mFrames.ElementAt(i); + + // Determine damaged area and tell view manager to redraw it + // blink doesn't blink outline ... I hope + nsRect bounds(nsPoint(0, 0), frameData.mFrame->GetSize()); + frameData.mFrame->Invalidate(bounds); + } + return NS_OK; +} + + +// static +nsresult nsBlinkTimer::AddBlinkFrame(nsPresContext* aPresContext, nsIFrame* aFrame) +{ + if (!sTextBlinker) + { + sTextBlinker = new nsBlinkTimer; + if (!sTextBlinker) return NS_ERROR_OUT_OF_MEMORY; + } + + NS_ADDREF(sTextBlinker); + + sTextBlinker->AddFrame(aPresContext, aFrame); + return NS_OK; +} + + +// static +nsresult nsBlinkTimer::RemoveBlinkFrame(nsIFrame* aFrame) +{ + NS_ASSERTION(sTextBlinker, "Should have blink timer here"); + + nsBlinkTimer* blinkTimer = sTextBlinker; // copy so we can call NS_RELEASE on it + if (!blinkTimer) return NS_OK; + + blinkTimer->RemoveFrame(aFrame); + NS_RELEASE(blinkTimer); + + return NS_OK; +} + +//---------------------------------------------------------------------- + +static nscolor +EnsureDifferentColors(nscolor colorA, nscolor colorB) +{ + if (colorA == colorB) { + nscolor res; + res = NS_RGB(NS_GET_R(colorA) ^ 0xff, + NS_GET_G(colorA) ^ 0xff, + NS_GET_B(colorA) ^ 0xff); + return res; + } + return colorA; +} + +//----------------------------------------------------------------------------- + +static nscolor +DarkenColor(nscolor aColor) +{ + PRUint16 hue, sat, value; + PRUint8 alpha; + + // convert the RBG to HSV so we can get the lightness (which is the v) + NS_RGB2HSV(aColor, hue, sat, value, alpha); + + // The goal here is to send white to black while letting colored + // stuff stay colored... So we adopt the following approach. + // Something with sat = 0 should end up with value = 0. Something + // with a high sat can end up with a high value and it's ok.... At + // the same time, we don't want to make things lighter. Do + // something simple, since it seems to work. + if (value > sat) { + value = sat; + // convert this color back into the RGB color space. + NS_HSV2RGB(aColor, hue, sat, value, alpha); + } + return aColor; +} + +// Check whether we should darken text colors. We need to do this if +// background images and colors are being suppressed, because that means +// light text will not be visible against the (presumed light-colored) background. +static PRBool +ShouldDarkenColors(nsPresContext* aPresContext) +{ + return !aPresContext->GetBackgroundColorDraw() && + !aPresContext->GetBackgroundImageDraw(); +} + +nsTextPaintStyle::nsTextPaintStyle(nsTextFrame* aFrame) + : mFrame(aFrame), + mPresContext(aFrame->PresContext()), + mInitCommonColors(PR_FALSE), + mInitSelectionColors(PR_FALSE) +{ + for (PRUint32 i = 0; i < NS_ARRAY_LENGTH(mSelectionStyle); i++) + mSelectionStyle[i].mInit = PR_FALSE; +} + +PRBool +nsTextPaintStyle::EnsureSufficientContrast(nscolor *aForeColor, nscolor *aBackColor) +{ + InitCommonColors(); + + // If the combination of selection background color and frame background color + // is sufficient contrast, don't exchange the selection colors. + PRInt32 backLuminosityDifference = + NS_LUMINOSITY_DIFFERENCE(*aBackColor, mFrameBackgroundColor); + if (backLuminosityDifference >= mSufficientContrast) + return PR_FALSE; + + // Otherwise, we should use the higher-contrast color for the selection + // background color. + PRInt32 foreLuminosityDifference = + NS_LUMINOSITY_DIFFERENCE(*aForeColor, mFrameBackgroundColor); + if (backLuminosityDifference < foreLuminosityDifference) { + nscolor tmpColor = *aForeColor; + *aForeColor = *aBackColor; + *aBackColor = tmpColor; + return PR_TRUE; + } + return PR_FALSE; +} + +nscolor +nsTextPaintStyle::GetTextColor() +{ + nscolor color = mFrame->GetStyleColor()->mColor; + if (ShouldDarkenColors(mPresContext)) { + color = DarkenColor(color); + } + return color; +} + +PRBool +nsTextPaintStyle::GetSelectionColors(nscolor* aForeColor, + nscolor* aBackColor) +{ + NS_ASSERTION(aForeColor, "aForeColor is null"); + NS_ASSERTION(aBackColor, "aBackColor is null"); + + if (!InitSelectionColors()) + return PR_FALSE; + + *aForeColor = mSelectionTextColor; + *aBackColor = mSelectionBGColor; + return PR_TRUE; +} + +void +nsTextPaintStyle::GetHighlightColors(nscolor* aForeColor, + nscolor* aBackColor) +{ + NS_ASSERTION(aForeColor, "aForeColor is null"); + NS_ASSERTION(aBackColor, "aBackColor is null"); + + nsILookAndFeel* look = mPresContext->LookAndFeel(); + nscolor foreColor, backColor; + look->GetColor(nsILookAndFeel::eColor_TextHighlightBackground, + backColor); + look->GetColor(nsILookAndFeel::eColor_TextHighlightForeground, + foreColor); + EnsureSufficientContrast(&foreColor, &backColor); + *aForeColor = foreColor; + *aBackColor = backColor; +} + +void +nsTextPaintStyle::GetIMESelectionColors(PRInt32 aIndex, + nscolor* aForeColor, + nscolor* aBackColor) +{ + NS_ASSERTION(aForeColor, "aForeColor is null"); + NS_ASSERTION(aBackColor, "aBackColor is null"); + NS_ASSERTION(aIndex >= 0 && aIndex < 5, "Index out of range"); + + nsSelectionStyle* selectionStyle = GetSelectionStyle(aIndex); + *aForeColor = selectionStyle->mTextColor; + *aBackColor = selectionStyle->mBGColor; +} + +PRBool +nsTextPaintStyle::GetSelectionUnderlineForPaint(PRInt32 aIndex, + nscolor* aLineColor, + float* aRelativeSize, + PRUint8* aStyle) +{ + NS_ASSERTION(aLineColor, "aLineColor is null"); + NS_ASSERTION(aRelativeSize, "aRelativeSize is null"); + NS_ASSERTION(aIndex >= 0 && aIndex < 5, "Index out of range"); + + nsSelectionStyle* selectionStyle = GetSelectionStyle(aIndex); + if (selectionStyle->mUnderlineStyle == NS_STYLE_BORDER_STYLE_NONE || + selectionStyle->mUnderlineColor == NS_TRANSPARENT || + selectionStyle->mUnderlineRelativeSize <= 0.0f) + return PR_FALSE; + + *aLineColor = selectionStyle->mUnderlineColor; + *aRelativeSize = selectionStyle->mUnderlineRelativeSize; + *aStyle = selectionStyle->mUnderlineStyle; + return PR_TRUE; +} + +void +nsTextPaintStyle::InitCommonColors() +{ + if (mInitCommonColors) + return; + + nsStyleContext* sc = mFrame->GetStyleContext(); + + nsStyleContext* bgContext = + nsCSSRendering::FindNonTransparentBackground(sc); + NS_ASSERTION(bgContext, "Cannot find NonTransparentBackground."); + const nsStyleBackground* bg = bgContext->GetStyleBackground(); + + nscolor defaultBgColor = mPresContext->DefaultBackgroundColor(); + mFrameBackgroundColor = NS_ComposeColors(defaultBgColor, + bg->mBackgroundColor); + + if (bgContext->GetStyleDisplay()->mAppearance) { + // Assume a native widget has sufficient contrast always + mSufficientContrast = 0; + mInitCommonColors = PR_TRUE; + return; + } + + NS_ASSERTION(NS_GET_A(defaultBgColor) == 255, + "default background color is not opaque"); + + nsILookAndFeel* look = mPresContext->LookAndFeel(); + nscolor defaultWindowBackgroundColor, selectionTextColor, selectionBGColor; + look->GetColor(nsILookAndFeel::eColor_TextSelectBackground, + selectionBGColor); + look->GetColor(nsILookAndFeel::eColor_TextSelectForeground, + selectionTextColor); + look->GetColor(nsILookAndFeel::eColor_WindowBackground, + defaultWindowBackgroundColor); + + mSufficientContrast = + PR_MIN(PR_MIN(NS_SUFFICIENT_LUMINOSITY_DIFFERENCE, + NS_LUMINOSITY_DIFFERENCE(selectionTextColor, + selectionBGColor)), + NS_LUMINOSITY_DIFFERENCE(defaultWindowBackgroundColor, + selectionBGColor)); + + mInitCommonColors = PR_TRUE; +} + +static nsIContent* +FindElementAncestor(nsINode* aNode) +{ + while (aNode && !aNode->IsNodeOfType(nsINode::eELEMENT)) { + aNode = aNode->GetParent(); + } + return static_cast(aNode); +} + +PRBool +nsTextPaintStyle::InitSelectionColors() +{ + if (mInitSelectionColors) + return PR_TRUE; + + PRInt16 selectionFlags; + PRInt16 selectionStatus = mFrame->GetSelectionStatus(&selectionFlags); + if (!(selectionFlags & nsISelectionDisplay::DISPLAY_TEXT) || + selectionStatus < nsISelectionController::SELECTION_ON) { + // Not displaying the normal selection. + // We're not caching this fact, so every call to GetSelectionColors + // will come through here. We could avoid this, but it's not really worth it. + return PR_FALSE; + } + + mInitSelectionColors = PR_TRUE; + + nsIFrame* nonGeneratedAncestor = nsLayoutUtils::GetNonGeneratedAncestor(mFrame); + nsIContent* selectionContent = FindElementAncestor(nonGeneratedAncestor->GetContent()); + + if (selectionContent && + selectionStatus == nsISelectionController::SELECTION_ON) { + nsRefPtr sc = nsnull; + sc = mPresContext->StyleSet()-> + ProbePseudoStyleFor(selectionContent, nsCSSPseudoElements::mozSelection, + mFrame->GetStyleContext()); + // Use -moz-selection pseudo class. + if (sc) { + const nsStyleBackground* bg = sc->GetStyleBackground(); + mSelectionBGColor = bg->mBackgroundColor; + mSelectionTextColor = sc->GetStyleColor()->mColor; + return PR_TRUE; + } + } + + nsILookAndFeel* look = mPresContext->LookAndFeel(); + + nscolor selectionBGColor; + look->GetColor(nsILookAndFeel::eColor_TextSelectBackground, + selectionBGColor); + + if (selectionStatus == nsISelectionController::SELECTION_ATTENTION) { + look->GetColor(nsILookAndFeel::eColor_TextSelectBackgroundAttention, + mSelectionBGColor); + mSelectionBGColor = EnsureDifferentColors(mSelectionBGColor, + selectionBGColor); + } else if (selectionStatus != nsISelectionController::SELECTION_ON) { + look->GetColor(nsILookAndFeel::eColor_TextSelectBackgroundDisabled, + mSelectionBGColor); + mSelectionBGColor = EnsureDifferentColors(mSelectionBGColor, + selectionBGColor); + } else { + mSelectionBGColor = selectionBGColor; + } + + look->GetColor(nsILookAndFeel::eColor_TextSelectForeground, + mSelectionTextColor); + + // On MacOS X, we don't exchange text color and BG color. + if (mSelectionTextColor == NS_DONT_CHANGE_COLOR) { + mSelectionTextColor = EnsureDifferentColors(mFrame->GetStyleColor()->mColor, + mSelectionBGColor); + } else { + EnsureSufficientContrast(&mSelectionTextColor, &mSelectionBGColor); + } + return PR_TRUE; +} + +nsTextPaintStyle::nsSelectionStyle* +nsTextPaintStyle::GetSelectionStyle(PRInt32 aIndex) +{ + InitSelectionStyle(aIndex); + return &mSelectionStyle[aIndex]; +} + +struct StyleIDs { + nsILookAndFeel::nsColorID mForeground, mBackground, mLine; + nsILookAndFeel::nsMetricID mLineStyle; + nsILookAndFeel::nsMetricFloatID mLineRelativeSize; +}; +static StyleIDs SelectionStyleIDs[] = { + { nsILookAndFeel::eColor_IMERawInputForeground, + nsILookAndFeel::eColor_IMERawInputBackground, + nsILookAndFeel::eColor_IMERawInputUnderline, + nsILookAndFeel::eMetric_IMERawInputUnderlineStyle, + nsILookAndFeel::eMetricFloat_IMEUnderlineRelativeSize }, + { nsILookAndFeel::eColor_IMESelectedRawTextForeground, + nsILookAndFeel::eColor_IMESelectedRawTextBackground, + nsILookAndFeel::eColor_IMESelectedRawTextUnderline, + nsILookAndFeel::eMetric_IMESelectedRawTextUnderlineStyle, + nsILookAndFeel::eMetricFloat_IMEUnderlineRelativeSize }, + { nsILookAndFeel::eColor_IMEConvertedTextForeground, + nsILookAndFeel::eColor_IMEConvertedTextBackground, + nsILookAndFeel::eColor_IMEConvertedTextUnderline, + nsILookAndFeel::eMetric_IMEConvertedTextUnderlineStyle, + nsILookAndFeel::eMetricFloat_IMEUnderlineRelativeSize }, + { nsILookAndFeel::eColor_IMESelectedConvertedTextForeground, + nsILookAndFeel::eColor_IMESelectedConvertedTextBackground, + nsILookAndFeel::eColor_IMESelectedConvertedTextUnderline, + nsILookAndFeel::eMetric_IMESelectedConvertedTextUnderline, + nsILookAndFeel::eMetricFloat_IMEUnderlineRelativeSize }, + { nsILookAndFeel::eColor_LAST_COLOR, + nsILookAndFeel::eColor_LAST_COLOR, + nsILookAndFeel::eColor_SpellCheckerUnderline, + nsILookAndFeel::eMetric_SpellCheckerUnderlineStyle, + nsILookAndFeel::eMetricFloat_SpellCheckerUnderlineRelativeSize } +}; + +static PRUint8 sUnderlineStyles[] = { + nsCSSRendering::DECORATION_STYLE_NONE, // NS_UNDERLINE_STYLE_NONE 0 + nsCSSRendering::DECORATION_STYLE_DOTTED, // NS_UNDERLINE_STYLE_DOTTED 1 + nsCSSRendering::DECORATION_STYLE_DASHED, // NS_UNDERLINE_STYLE_DASHED 2 + nsCSSRendering::DECORATION_STYLE_SOLID, // NS_UNDERLINE_STYLE_SOLID 3 + nsCSSRendering::DECORATION_STYLE_DOUBLE, // NS_UNDERLINE_STYLE_DOUBLE 4 + nsCSSRendering::DECORATION_STYLE_WAVY // NS_UNDERLINE_STYLE_WAVY 5 +}; + +void +nsTextPaintStyle::InitSelectionStyle(PRInt32 aIndex) +{ + NS_ASSERTION(aIndex >= 0 && aIndex < 5, "aIndex is invalid"); + nsSelectionStyle* selectionStyle = &mSelectionStyle[aIndex]; + if (selectionStyle->mInit) + return; + + StyleIDs* styleIDs = &SelectionStyleIDs[aIndex]; + + nsILookAndFeel* look = mPresContext->LookAndFeel(); + nscolor foreColor, backColor; + if (styleIDs->mForeground == nsILookAndFeel::eColor_LAST_COLOR) { + foreColor = NS_SAME_AS_FOREGROUND_COLOR; + } else { + look->GetColor(styleIDs->mForeground, foreColor); + } + if (styleIDs->mBackground == nsILookAndFeel::eColor_LAST_COLOR) { + backColor = NS_TRANSPARENT; + } else { + look->GetColor(styleIDs->mBackground, backColor); + } + + // Convert special color to actual color + NS_ASSERTION(foreColor != NS_TRANSPARENT, + "foreColor cannot be NS_TRANSPARENT"); + NS_ASSERTION(backColor != NS_SAME_AS_FOREGROUND_COLOR, + "backColor cannot be NS_SAME_AS_FOREGROUND_COLOR"); + NS_ASSERTION(backColor != NS_40PERCENT_FOREGROUND_COLOR, + "backColor cannot be NS_40PERCENT_FOREGROUND_COLOR"); + + foreColor = GetResolvedForeColor(foreColor, GetTextColor(), backColor); + + if (NS_GET_A(backColor) > 0) + EnsureSufficientContrast(&foreColor, &backColor); + + nscolor lineColor; + float relativeSize; + PRUint8 lineStyle; + GetSelectionUnderline(mPresContext, aIndex, + &lineColor, &relativeSize, &lineStyle); + lineColor = GetResolvedForeColor(lineColor, foreColor, backColor); + + selectionStyle->mTextColor = foreColor; + selectionStyle->mBGColor = backColor; + selectionStyle->mUnderlineColor = lineColor; + selectionStyle->mUnderlineStyle = lineStyle; + selectionStyle->mUnderlineRelativeSize = relativeSize; + selectionStyle->mInit = PR_TRUE; +} + +/* static */ PRBool +nsTextPaintStyle::GetSelectionUnderline(nsPresContext* aPresContext, + PRInt32 aIndex, + nscolor* aLineColor, + float* aRelativeSize, + PRUint8* aStyle) +{ + NS_ASSERTION(aPresContext, "aPresContext is null"); + NS_ASSERTION(aRelativeSize, "aRelativeSize is null"); + NS_ASSERTION(aStyle, "aStyle is null"); + NS_ASSERTION(aIndex >= 0 && aIndex < 5, "Index out of range"); + + nsILookAndFeel* look = aPresContext->LookAndFeel(); + + StyleIDs& styleID = SelectionStyleIDs[aIndex]; + nscolor color; + float size; + PRInt32 style; + + look->GetColor(styleID.mLine, color); + look->GetMetric(styleID.mLineStyle, style); + if (!NS_IS_VALID_UNDERLINE_STYLE(style)) { + NS_ERROR("Invalid underline style value is specified"); + style = NS_UNDERLINE_STYLE_SOLID; + } + look->GetMetric(styleID.mLineRelativeSize, size); + + NS_ASSERTION(size, "selection underline relative size must be larger than 0"); + + if (aLineColor) { + *aLineColor = color; + } + *aRelativeSize = size; + *aStyle = sUnderlineStyles[style]; + + return sUnderlineStyles[style] != nsCSSRendering::DECORATION_STYLE_NONE && + color != NS_TRANSPARENT && + size > 0.0f; +} + +inline nscolor Get40PercentColor(nscolor aForeColor, nscolor aBackColor) +{ + nscolor foreColor = NS_RGBA(NS_GET_R(aForeColor), + NS_GET_G(aForeColor), + NS_GET_B(aForeColor), + (PRUint8)(255 * 0.4f)); + // Don't use true alpha color for readability. + return NS_ComposeColors(aBackColor, foreColor); +} + +nscolor +nsTextPaintStyle::GetResolvedForeColor(nscolor aColor, + nscolor aDefaultForeColor, + nscolor aBackColor) +{ + if (aColor == NS_SAME_AS_FOREGROUND_COLOR) + return aDefaultForeColor; + + if (aColor != NS_40PERCENT_FOREGROUND_COLOR) + return aColor; + + // Get actual background color + nscolor actualBGColor = aBackColor; + if (actualBGColor == NS_TRANSPARENT) { + InitCommonColors(); + actualBGColor = mFrameBackgroundColor; + } + return Get40PercentColor(aDefaultForeColor, actualBGColor); +} + +//----------------------------------------------------------------------------- + +#ifdef ACCESSIBILITY +NS_IMETHODIMP nsTextFrame::GetAccessible(nsIAccessible** aAccessible) +{ + if (IsEmpty()) { + nsAutoString renderedWhitespace; + GetRenderedText(&renderedWhitespace, nsnull, nsnull, 0, 1); + if (renderedWhitespace.IsEmpty()) { + return NS_ERROR_FAILURE; + } + } + + nsCOMPtr accService = do_GetService("@mozilla.org/accessibilityService;1"); + + if (accService) { + return accService->CreateHTMLTextAccessible(static_cast(this), aAccessible); + } + return NS_ERROR_FAILURE; +} +#endif + + +//----------------------------------------------------------------------------- +NS_IMETHODIMP +nsTextFrame::Init(nsIContent* aContent, + nsIFrame* aParent, + nsIFrame* aPrevInFlow) +{ + NS_ASSERTION(!aPrevInFlow, "Can't be a continuation!"); + NS_PRECONDITION(aContent->IsNodeOfType(nsINode::eTEXT), + "Bogus content!"); + + if (!PresContext()->IsDynamic()) { + AddStateBits(TEXT_BLINK_ON_OR_PRINTING); + } + + // Since our content has a frame now, this flag is no longer needed. + aContent->UnsetFlags(NS_CREATE_FRAME_IF_NON_WHITESPACE); + // We're not a continuing frame. + // mContentOffset = 0; not necessary since we get zeroed out at init + return nsFrame::Init(aContent, aParent, aPrevInFlow); +} + +void +nsTextFrame::Destroy() +{ + // We might want to clear NS_CREATE_FRAME_IF_NON_WHITESPACE or + // NS_REFRAME_IF_WHITESPACE on mContent here, since our parent frame + // type might be changing. Not clear whether it's worth it. + ClearTextRun(); + if (mNextContinuation) { + mNextContinuation->SetPrevInFlow(nsnull); + } + // Let the base class destroy the frame + nsFrame::Destroy(); +} + +class nsContinuingTextFrame : public nsTextFrame { +public: + NS_DECL_FRAMEARENA_HELPERS + + friend nsIFrame* NS_NewContinuingTextFrame(nsIPresShell* aPresShell, nsStyleContext* aContext); + + NS_IMETHOD Init(nsIContent* aContent, + nsIFrame* aParent, + nsIFrame* aPrevInFlow); + + virtual void Destroy(); + + virtual nsIFrame* GetPrevContinuation() const { + return mPrevContinuation; + } + NS_IMETHOD SetPrevContinuation(nsIFrame* aPrevContinuation) { + NS_ASSERTION (!aPrevContinuation || GetType() == aPrevContinuation->GetType(), + "setting a prev continuation with incorrect type!"); + NS_ASSERTION (!nsSplittableFrame::IsInPrevContinuationChain(aPrevContinuation, this), + "creating a loop in continuation chain!"); + mPrevContinuation = aPrevContinuation; + RemoveStateBits(NS_FRAME_IS_FLUID_CONTINUATION); + return NS_OK; + } + virtual nsIFrame* GetPrevInFlowVirtual() const { return GetPrevInFlow(); } + nsIFrame* GetPrevInFlow() const { + return (GetStateBits() & NS_FRAME_IS_FLUID_CONTINUATION) ? mPrevContinuation : nsnull; + } + NS_IMETHOD SetPrevInFlow(nsIFrame* aPrevInFlow) { + NS_ASSERTION (!aPrevInFlow || GetType() == aPrevInFlow->GetType(), + "setting a prev in flow with incorrect type!"); + NS_ASSERTION (!nsSplittableFrame::IsInPrevContinuationChain(aPrevInFlow, this), + "creating a loop in continuation chain!"); + mPrevContinuation = aPrevInFlow; + AddStateBits(NS_FRAME_IS_FLUID_CONTINUATION); + return NS_OK; + } + virtual nsIFrame* GetFirstInFlow() const; + virtual nsIFrame* GetFirstContinuation() const; + + virtual void AddInlineMinWidth(nsIRenderingContext *aRenderingContext, + InlineMinWidthData *aData); + virtual void AddInlinePrefWidth(nsIRenderingContext *aRenderingContext, + InlinePrefWidthData *aData); + + virtual nsresult GetRenderedText(nsAString* aString = nsnull, + gfxSkipChars* aSkipChars = nsnull, + gfxSkipCharsIterator* aSkipIter = nsnull, + PRUint32 aSkippedStartOffset = 0, + PRUint32 aSkippedMaxLength = PR_UINT32_MAX) + { return NS_ERROR_NOT_IMPLEMENTED; } // Call on a primary text frame only + +protected: + nsContinuingTextFrame(nsStyleContext* aContext) : nsTextFrame(aContext) {} + nsIFrame* mPrevContinuation; +}; + +NS_IMETHODIMP +nsContinuingTextFrame::Init(nsIContent* aContent, + nsIFrame* aParent, + nsIFrame* aPrevInFlow) +{ + NS_ASSERTION(aPrevInFlow, "Must be a continuation!"); + + if (!PresContext()->IsDynamic()) { + AddStateBits(TEXT_BLINK_ON_OR_PRINTING); + } + + // NOTE: bypassing nsTextFrame::Init!!! + nsresult rv = nsFrame::Init(aContent, aParent, aPrevInFlow); + +#ifdef IBMBIDI + nsTextFrame* nextContinuation = + static_cast(aPrevInFlow->GetNextContinuation()); +#endif // IBMBIDI + // Hook the frame into the flow + SetPrevInFlow(aPrevInFlow); + aPrevInFlow->SetNextInFlow(this); + nsTextFrame* prev = static_cast(aPrevInFlow); + mContentOffset = prev->GetContentOffset() + prev->GetContentLengthHint(); + NS_ASSERTION(mContentOffset < PRInt32(GetFragment()->GetLength()), + "Creating ContinuingTextFrame, but there is no more content"); + if (prev->GetStyleContext() != GetStyleContext()) { + // We're taking part of prev's text, and its style may be different + // so clear its textrun which may no longer be valid (and don't set ours) + prev->ClearTextRun(); + } else { + mTextRun = prev->GetTextRun(); + } +#ifdef IBMBIDI + if (aPrevInFlow->GetStateBits() & NS_FRAME_IS_BIDI) { + nsPropertyTable *propTable = PresContext()->PropertyTable(); + propTable->SetProperty(this, nsGkAtoms::embeddingLevel, + propTable->GetProperty(aPrevInFlow, nsGkAtoms::embeddingLevel), + nsnull, nsnull); + propTable->SetProperty(this, nsGkAtoms::baseLevel, + propTable->GetProperty(aPrevInFlow, nsGkAtoms::baseLevel), + nsnull, nsnull); + propTable->SetProperty(this, nsGkAtoms::charType, + propTable->GetProperty(aPrevInFlow, nsGkAtoms::charType), + nsnull, nsnull); + if (nextContinuation) { + SetNextContinuation(nextContinuation); + nextContinuation->SetPrevContinuation(this); + // Adjust next-continuations' content offset as needed. + while (nextContinuation && + nextContinuation->GetContentOffset() < mContentOffset) { + NS_ASSERTION( + propTable->GetProperty(this, nsGkAtoms::embeddingLevel) == + propTable->GetProperty(nextContinuation, nsGkAtoms::embeddingLevel) && + propTable->GetProperty(this, nsGkAtoms::baseLevel) == + propTable->GetProperty(nextContinuation, nsGkAtoms::baseLevel) && + propTable->GetProperty(this, nsGkAtoms::charType) == + propTable->GetProperty(nextContinuation, nsGkAtoms::charType), + "stealing text from different type of BIDI continuation"); + nextContinuation->mContentOffset = mContentOffset; + nextContinuation = static_cast(nextContinuation->GetNextContinuation()); + } + } + mState |= NS_FRAME_IS_BIDI; + } // prev frame is bidi +#endif // IBMBIDI + + return rv; +} + +void +nsContinuingTextFrame::Destroy() +{ + // The text associated with this frame will become associated with our + // prev-continuation. If that means the text has changed style, then + // we need to wipe out the text run for the text. + // Note that mPrevContinuation can be null if we're destroying the whole + // frame chain from the start to the end. + // If this frame is mentioned in the userData for a textrun (say + // because there's a direction change at the start of this frame), then + // we have to clear the textrun because we're going away and the + // textrun had better not keep a dangling reference to us. + if ((GetStateBits() & TEXT_IN_TEXTRUN_USER_DATA) || + !mPrevContinuation || + mPrevContinuation->GetStyleContext() != GetStyleContext()) { + ClearTextRun(); + // Clear the previous continuation's text run also, so that it can rebuild + // the text run to include our text. + if (mPrevContinuation) { + (static_cast(mPrevContinuation))->ClearTextRun(); + } + } + nsSplittableFrame::RemoveFromFlow(this); + // Let the base class destroy the frame + nsFrame::Destroy(); +} + +nsIFrame* +nsContinuingTextFrame::GetFirstInFlow() const +{ + // Can't cast to |nsContinuingTextFrame*| because the first one isn't. + nsIFrame *firstInFlow, + *previous = const_cast + (static_cast(this)); + do { + firstInFlow = previous; + previous = firstInFlow->GetPrevInFlow(); + } while (previous); + return firstInFlow; +} + +nsIFrame* +nsContinuingTextFrame::GetFirstContinuation() const +{ + // Can't cast to |nsContinuingTextFrame*| because the first one isn't. + nsIFrame *firstContinuation, + *previous = const_cast + (static_cast(mPrevContinuation)); + + NS_ASSERTION(previous, "How can an nsContinuingTextFrame be the first continuation?"); + + do { + firstContinuation = previous; + previous = firstContinuation->GetPrevContinuation(); + } while (previous); + return firstContinuation; +} + +// XXX Do we want to do all the work for the first-in-flow or do the +// work for each part? (Be careful of first-letter / first-line, though, +// especially first-line!) Doing all the work on the first-in-flow has +// the advantage of avoiding the potential for incremental reflow bugs, +// but depends on our maintining the frame tree in reasonable ways even +// for edge cases (block-within-inline splits, nextBidi, etc.) + +// XXX We really need to make :first-letter happen during frame +// construction. + +// Needed for text frames in XUL. +/* virtual */ nscoord +nsTextFrame::GetMinWidth(nsIRenderingContext *aRenderingContext) +{ + return nsLayoutUtils::MinWidthFromInline(this, aRenderingContext); +} + +// Needed for text frames in XUL. +/* virtual */ nscoord +nsTextFrame::GetPrefWidth(nsIRenderingContext *aRenderingContext) +{ + return nsLayoutUtils::PrefWidthFromInline(this, aRenderingContext); +} + +/* virtual */ void +nsContinuingTextFrame::AddInlineMinWidth(nsIRenderingContext *aRenderingContext, + InlineMinWidthData *aData) +{ + // Do nothing, since the first-in-flow accounts for everything. + return; +} + +/* virtual */ void +nsContinuingTextFrame::AddInlinePrefWidth(nsIRenderingContext *aRenderingContext, + InlinePrefWidthData *aData) +{ + // Do nothing, since the first-in-flow accounts for everything. + return; +} + +static void +DestroySelectionDetails(SelectionDetails* aDetails) +{ + while (aDetails) { + SelectionDetails* next = aDetails->mNext; + delete aDetails; + aDetails = next; + } +} + +//---------------------------------------------------------------------- + +#if defined(DEBUG_rbs) || defined(DEBUG_bzbarsky) +static void +VerifyNotDirty(nsFrameState state) +{ + PRBool isZero = state & NS_FRAME_FIRST_REFLOW; + PRBool isDirty = state & NS_FRAME_IS_DIRTY; + if (!isZero && isDirty) + NS_WARNING("internal offsets may be out-of-sync"); +} +#define DEBUG_VERIFY_NOT_DIRTY(state) \ +VerifyNotDirty(state) +#else +#define DEBUG_VERIFY_NOT_DIRTY(state) +#endif + +nsIFrame* +NS_NewTextFrame(nsIPresShell* aPresShell, nsStyleContext* aContext) +{ + return new (aPresShell) nsTextFrame(aContext); +} + +NS_IMPL_FRAMEARENA_HELPERS(nsTextFrame) + +nsIFrame* +NS_NewContinuingTextFrame(nsIPresShell* aPresShell, nsStyleContext* aContext) +{ + return new (aPresShell) nsContinuingTextFrame(aContext); +} + +NS_IMPL_FRAMEARENA_HELPERS(nsContinuingTextFrame) + +nsTextFrame::~nsTextFrame() +{ + if (0 != (mState & TEXT_BLINK_ON_OR_PRINTING) && PresContext()->IsDynamic()) + { + nsBlinkTimer::RemoveBlinkFrame(this); + } +} + +NS_IMETHODIMP +nsTextFrame::GetCursor(const nsPoint& aPoint, + nsIFrame::Cursor& aCursor) +{ + FillCursorInformationFromStyle(GetStyleUserInterface(), aCursor); + if (NS_STYLE_CURSOR_AUTO == aCursor.mCursor) { + aCursor.mCursor = NS_STYLE_CURSOR_TEXT; + + // If tabindex >= 0, use default cursor to indicate it's not selectable + nsIFrame *ancestorFrame = this; + while ((ancestorFrame = ancestorFrame->GetParent()) != nsnull) { + nsIContent *ancestorContent = ancestorFrame->GetContent(); + if (ancestorContent && ancestorContent->HasAttr(kNameSpaceID_None, nsGkAtoms::tabindex)) { + nsAutoString tabIndexStr; + ancestorContent->GetAttr(kNameSpaceID_None, nsGkAtoms::tabindex, tabIndexStr); + if (!tabIndexStr.IsEmpty()) { + PRInt32 rv, tabIndexVal = tabIndexStr.ToInteger(&rv); + if (NS_SUCCEEDED(rv) && tabIndexVal >= 0) { + aCursor.mCursor = NS_STYLE_CURSOR_DEFAULT; + break; + } + } + } + } + } + + return NS_OK; +} + +nsIFrame* +nsTextFrame::GetLastInFlow() const +{ + nsTextFrame* lastInFlow = const_cast(this); + while (lastInFlow->GetNextInFlow()) { + lastInFlow = static_cast(lastInFlow->GetNextInFlow()); + } + NS_POSTCONDITION(lastInFlow, "illegal state in flow chain."); + return lastInFlow; +} +nsIFrame* +nsTextFrame::GetLastContinuation() const +{ + nsTextFrame* lastInFlow = const_cast(this); + while (lastInFlow->mNextContinuation) { + lastInFlow = static_cast(lastInFlow->mNextContinuation); + } + NS_POSTCONDITION(lastInFlow, "illegal state in continuation chain."); + return lastInFlow; +} + +void +nsTextFrame::ClearTextRun() +{ + // save textrun because ClearAllTextRunReferences will clear ours + gfxTextRun* textRun = mTextRun; + + if (!textRun) + return; + + UnhookTextRunFromFrames(textRun); + // see comments in BuildTextRunForFrames... +// if (textRun->GetFlags() & gfxFontGroup::TEXT_IS_PERSISTENT) { +// NS_ERROR("Shouldn't reach here for now..."); +// // the textrun's text may be referencing a DOM node that has changed, +// // so we'd better kill this textrun now. +// if (textRun->GetExpirationState()->IsTracked()) { +// gTextRuns->RemoveFromCache(textRun); +// } +// delete textRun; +// return; +// } + + if (!(textRun->GetFlags() & gfxTextRunWordCache::TEXT_IN_CACHE)) { + // Remove it now because it's not doing anything useful + gTextRuns->RemoveFromCache(textRun); + delete textRun; + } +} + +static void +ClearTextRunsInFlowChain(nsTextFrame* aFrame) +{ + nsTextFrame* f; + for (f = aFrame; f; f = static_cast(f->GetNextContinuation())) { + f->ClearTextRun(); + } +} + +NS_IMETHODIMP +nsTextFrame::CharacterDataChanged(CharacterDataChangeInfo* aInfo) +{ + ClearTextRunsInFlowChain(this); + + nsTextFrame* targetTextFrame; + PRInt32 nodeLength = mContent->GetText()->GetLength(); + + if (aInfo->mAppend) { + targetTextFrame = static_cast(GetLastContinuation()); + targetTextFrame->mState &= ~TEXT_WHITESPACE_FLAGS; + } else { + // Mark all the continuation frames as dirty, and fix up content offsets to + // be valid. + // Don't set NS_FRAME_IS_DIRTY on |this|, since we call FrameNeedsReflow + // below. + nsTextFrame* textFrame = this; + PRInt32 newLength = nodeLength; + do { + textFrame->mState &= ~TEXT_WHITESPACE_FLAGS; + // If the text node has shrunk, clip the frame contentlength as necessary + if (textFrame->mContentOffset > newLength) { + textFrame->mContentOffset = newLength; + } + textFrame = static_cast(textFrame->GetNextContinuation()); + if (!textFrame) { + break; + } + textFrame->mState |= NS_FRAME_IS_DIRTY; + } while (1); + targetTextFrame = this; + } + + // Ask the parent frame to reflow me. + PresContext()->GetPresShell()->FrameNeedsReflow(targetTextFrame, + nsIPresShell::eStyleChange, + NS_FRAME_IS_DIRTY); + + return NS_OK; +} + +/* virtual */ void +nsTextFrame::DidSetStyleContext(nsStyleContext* aOldStyleContext) +{ + nsFrame::DidSetStyleContext(aOldStyleContext); + ClearTextRun(); +} + +class nsDisplayText : public nsDisplayItem { +public: + nsDisplayText(nsTextFrame* aFrame) : nsDisplayItem(aFrame) { + MOZ_COUNT_CTOR(nsDisplayText); + } +#ifdef NS_BUILD_REFCNT_LOGGING + virtual ~nsDisplayText() { + MOZ_COUNT_DTOR(nsDisplayText); + } +#endif + + virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder) { + return mFrame->GetOverflowRect() + aBuilder->ToReferenceFrame(mFrame); + } + virtual void HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect, + HitTestState* aState, nsTArray *aOutFrames) { + if (nsRect(aBuilder->ToReferenceFrame(mFrame), mFrame->GetSize()).Intersects(aRect)) { + aOutFrames->AppendElement(mFrame); + } + } + virtual void Paint(nsDisplayListBuilder* aBuilder, + nsIRenderingContext* aCtx); + NS_DISPLAY_DECL_NAME("Text") +}; + +void +nsDisplayText::Paint(nsDisplayListBuilder* aBuilder, + nsIRenderingContext* aCtx) { + // Add 1 pixel of dirty area around mVisibleRect to allow us to paint + // antialiased pixels beyond the measured text extents. + // This is temporary until we do this in the actual calculation of text extents. + nsRect extraVisible = mVisibleRect; + nscoord appUnitsPerDevPixel = mFrame->PresContext()->AppUnitsPerDevPixel(); + extraVisible.Inflate(appUnitsPerDevPixel, appUnitsPerDevPixel); + static_cast(mFrame)-> + PaintText(aCtx, aBuilder->ToReferenceFrame(mFrame), extraVisible); +} + +NS_IMETHODIMP +nsTextFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder, + const nsRect& aDirtyRect, + const nsDisplayListSet& aLists) +{ + if (!IsVisibleForPainting(aBuilder)) + return NS_OK; + + DO_GLOBAL_REFLOW_COUNT_DSP("nsTextFrame"); + + if ((0 != (mState & TEXT_BLINK_ON_OR_PRINTING)) && nsBlinkTimer::GetBlinkIsOff() && + PresContext()->IsDynamic() && !aBuilder->IsForEventDelivery()) + return NS_OK; + + return aLists.Content()->AppendNewToTop(new (aBuilder) nsDisplayText(this)); +} + +static nsIFrame* +GetGeneratedContentOwner(nsIFrame* aFrame, PRBool* aIsBefore) +{ + *aIsBefore = PR_FALSE; + while (aFrame && (aFrame->GetStateBits() & NS_FRAME_GENERATED_CONTENT)) { + if (aFrame->GetStyleContext()->GetPseudoType() == nsCSSPseudoElements::before) { + *aIsBefore = PR_TRUE; + } + aFrame = aFrame->GetParent(); + } + return aFrame; +} + +SelectionDetails* +nsTextFrame::GetSelectionDetails() +{ + const nsFrameSelection* frameSelection = GetConstFrameSelection(); + if (!(GetStateBits() & NS_FRAME_GENERATED_CONTENT)) { + SelectionDetails* details = + frameSelection->LookUpSelection(mContent, GetContentOffset(), + GetContentLength(), PR_FALSE); + SelectionDetails* sd; + for (sd = details; sd; sd = sd->mNext) { + sd->mStart += mContentOffset; + sd->mEnd += mContentOffset; + } + return details; + } + + // Check if the beginning or end of the element is selected, depending on + // whether we're :before content or :after content. + PRBool isBefore; + nsIFrame* owner = GetGeneratedContentOwner(this, &isBefore); + if (!owner || !owner->GetContent()) + return nsnull; + + SelectionDetails* details = + frameSelection->LookUpSelection(owner->GetContent(), + isBefore ? 0 : owner->GetContent()->GetChildCount(), 0, PR_FALSE); + SelectionDetails* sd; + for (sd = details; sd; sd = sd->mNext) { + // The entire text is selected! + sd->mStart = GetContentOffset(); + sd->mEnd = GetContentEnd(); + } + return details; +} + +static void +FillClippedRect(gfxContext* aCtx, nsPresContext* aPresContext, + nscolor aColor, const gfxRect& aDirtyRect, const gfxRect& aRect) +{ + gfxRect r = aRect.Intersect(aDirtyRect); + // For now, we need to put this in pixel coordinates + PRInt32 app = aPresContext->AppUnitsPerDevPixel(); + aCtx->NewPath(); + // pixel-snap + aCtx->Rectangle(gfxRect(r.X() / app, r.Y() / app, + r.Width() / app, r.Height() / app), PR_TRUE); + aCtx->SetColor(gfxRGBA(aColor)); + aCtx->Fill(); +} + +nsTextFrame::TextDecorations +nsTextFrame::GetTextDecorations(nsPresContext* aPresContext) +{ + TextDecorations decorations; + + // Quirks mode text decoration are rendered by children; see bug 1777 + // In non-quirks mode, nsHTMLContainer::Paint and nsBlockFrame::Paint + // does the painting of text decorations. + if (eCompatibility_NavQuirks != aPresContext->CompatibilityMode()) + return decorations; + + PRBool useOverride = PR_FALSE; + nscolor overrideColor; + + // A mask of all possible decorations. + PRUint8 decorMask = NS_STYLE_TEXT_DECORATION_UNDERLINE | + NS_STYLE_TEXT_DECORATION_OVERLINE | + NS_STYLE_TEXT_DECORATION_LINE_THROUGH; + + for (nsStyleContext* context = GetStyleContext(); + decorMask && context && context->HasTextDecorations(); + context = context->GetParent()) { + const nsStyleTextReset* styleText = context->GetStyleTextReset(); + if (!useOverride && + (NS_STYLE_TEXT_DECORATION_OVERRIDE_ALL & styleText->mTextDecoration)) { + // This handles the La + // la la case. The link underline should be green. + useOverride = PR_TRUE; + overrideColor = context->GetStyleColor()->mColor; + } + + PRUint8 useDecorations = decorMask & styleText->mTextDecoration; + if (useDecorations) {// a decoration defined here + nscolor color = context->GetStyleColor()->mColor; + + if (NS_STYLE_TEXT_DECORATION_UNDERLINE & useDecorations) { + decorations.mUnderColor = useOverride ? overrideColor : color; + decorMask &= ~NS_STYLE_TEXT_DECORATION_UNDERLINE; + decorations.mDecorations |= NS_STYLE_TEXT_DECORATION_UNDERLINE; + } + if (NS_STYLE_TEXT_DECORATION_OVERLINE & useDecorations) { + decorations.mOverColor = useOverride ? overrideColor : color; + decorMask &= ~NS_STYLE_TEXT_DECORATION_OVERLINE; + decorations.mDecorations |= NS_STYLE_TEXT_DECORATION_OVERLINE; + } + if (NS_STYLE_TEXT_DECORATION_LINE_THROUGH & useDecorations) { + decorations.mStrikeColor = useOverride ? overrideColor : color; + decorMask &= ~NS_STYLE_TEXT_DECORATION_LINE_THROUGH; + decorations.mDecorations |= NS_STYLE_TEXT_DECORATION_LINE_THROUGH; + } + } + } + + return decorations; +} + +void +nsTextFrame::UnionTextDecorationOverflow(nsPresContext* aPresContext, + PropertyProvider& aProvider, + nsRect* aOverflowRect) +{ + // Text-shadow overflows + nsRect shadowRect = nsLayoutUtils::GetTextShadowRectsUnion(*aOverflowRect, this); + aOverflowRect->UnionRect(*aOverflowRect, shadowRect); + + if (IsFloatingFirstLetterChild()) { + // The underline/overline drawable area must be contained in the overflow + // rect when this is in floating first letter frame at *both* modes. + nscoord fontAscent, fontHeight; + nsIFontMetrics* fm = aProvider.GetFontMetrics(); + fm->GetMaxAscent(fontAscent); + fm->GetMaxHeight(fontHeight); + nsRect fontRect(0, mAscent - fontAscent, GetSize().width, fontHeight); + aOverflowRect->UnionRect(*aOverflowRect, fontRect); + } + + // When this frame is not selected, the text-decoration area must be in + // frame bounds. + nsRect decorationRect; + if (!(GetStateBits() & NS_FRAME_SELECTED_CONTENT) || + !CombineSelectionUnderlineRect(aPresContext, *aOverflowRect)) + return; + AddStateBits(TEXT_SELECTION_UNDERLINE_OVERFLOWED); +} + +void +nsTextFrame::PaintTextDecorations(gfxContext* aCtx, const gfxRect& aDirtyRect, + const gfxPoint& aFramePt, + const gfxPoint& aTextBaselinePt, + nsTextPaintStyle& aTextPaintStyle, + PropertyProvider& aProvider, + const nscolor* aOverrideColor) +{ + TextDecorations decorations = + GetTextDecorations(aTextPaintStyle.PresContext()); + if (!decorations.HasDecorationlines()) + return; + + gfxFont* firstFont = aProvider.GetFontGroup()->GetFontAt(0); + if (!firstFont) + return; // OOM + const gfxFont::Metrics& fontMetrics = firstFont->GetMetrics(); + gfxFloat app = aTextPaintStyle.PresContext()->AppUnitsPerDevPixel(); + + // XXX aFramePt is in AppUnits, shouldn't it be nsFloatPoint? + gfxPoint pt(aFramePt.x / app, (aTextBaselinePt.y - mAscent) / app); + gfxSize size(GetRect().width / app, 0); + gfxFloat ascent = gfxFloat(mAscent) / app; + + nscolor lineColor; + if (decorations.HasOverline()) { + lineColor = aOverrideColor ? *aOverrideColor : decorations.mOverColor; + size.height = fontMetrics.underlineSize; + nsCSSRendering::PaintDecorationLine( + aCtx, lineColor, pt, size, ascent, fontMetrics.maxAscent, + NS_STYLE_TEXT_DECORATION_OVERLINE, + nsCSSRendering::DECORATION_STYLE_SOLID); + } + if (decorations.HasUnderline()) { + lineColor = aOverrideColor ? *aOverrideColor : decorations.mUnderColor; + size.height = fontMetrics.underlineSize; + gfxFloat offset = aProvider.GetFontGroup()->GetUnderlineOffset(); + nsCSSRendering::PaintDecorationLine( + aCtx, lineColor, pt, size, ascent, offset, + NS_STYLE_TEXT_DECORATION_UNDERLINE, + nsCSSRendering::DECORATION_STYLE_SOLID); + } + if (decorations.HasStrikeout()) { + lineColor = aOverrideColor ? *aOverrideColor : decorations.mStrikeColor; + size.height = fontMetrics.strikeoutSize; + gfxFloat offset = fontMetrics.strikeoutOffset; + nsCSSRendering::PaintDecorationLine( + aCtx, lineColor, pt, size, ascent, offset, + NS_STYLE_TEXT_DECORATION_LINE_THROUGH, + nsCSSRendering::DECORATION_STYLE_SOLID); + } +} + +static gfxFloat +ComputeDescentLimitForSelectionUnderline(nsPresContext* aPresContext, + nsTextFrame* aFrame, + const gfxFont::Metrics& aFontMetrics) +{ + gfxFloat app = aPresContext->AppUnitsPerDevPixel(); + nscoord lineHeightApp = + nsHTMLReflowState::CalcLineHeight(aFrame->GetStyleContext(), NS_AUTOHEIGHT); + gfxFloat lineHeight = gfxFloat(lineHeightApp) / app; + if (lineHeight <= aFontMetrics.maxHeight) { + return aFontMetrics.maxDescent; + } + return aFontMetrics.maxDescent + (lineHeight - aFontMetrics.maxHeight) / 2; +} + + +// Make sure this stays in sync with DrawSelectionDecorations below +static const SelectionType SelectionTypesWithDecorations = + nsISelectionController::SELECTION_SPELLCHECK | + nsISelectionController::SELECTION_IME_RAWINPUT | + nsISelectionController::SELECTION_IME_SELECTEDRAWTEXT | + nsISelectionController::SELECTION_IME_CONVERTEDTEXT | + nsISelectionController::SELECTION_IME_SELECTEDCONVERTEDTEXT; + +static PRUint8 +GetTextDecorationStyle(const nsTextRangeStyle &aRangeStyle) +{ + NS_PRECONDITION(aRangeStyle.IsLineStyleDefined(), + "aRangeStyle.mLineStyle have to be defined"); + switch (aRangeStyle.mLineStyle) { + case nsTextRangeStyle::LINESTYLE_NONE: + return nsCSSRendering::DECORATION_STYLE_NONE; + case nsTextRangeStyle::LINESTYLE_SOLID: + return nsCSSRendering::DECORATION_STYLE_SOLID; + case nsTextRangeStyle::LINESTYLE_DOTTED: + return nsCSSRendering::DECORATION_STYLE_DOTTED; + case nsTextRangeStyle::LINESTYLE_DASHED: + return nsCSSRendering::DECORATION_STYLE_DASHED; + case nsTextRangeStyle::LINESTYLE_DOUBLE: + return nsCSSRendering::DECORATION_STYLE_DOUBLE; + case nsTextRangeStyle::LINESTYLE_WAVY: + return nsCSSRendering::DECORATION_STYLE_WAVY; + default: + NS_WARNING("Requested underline style is not valid"); + return nsCSSRendering::DECORATION_STYLE_SOLID; + } +} + +static gfxFloat +ComputeSelectionUnderlineHeight(nsPresContext* aPresContext, + const gfxFont::Metrics& aFontMetrics, + SelectionType aSelectionType) +{ + switch (aSelectionType) { + case nsISelectionController::SELECTION_IME_RAWINPUT: + case nsISelectionController::SELECTION_IME_SELECTEDRAWTEXT: + case nsISelectionController::SELECTION_IME_CONVERTEDTEXT: + case nsISelectionController::SELECTION_IME_SELECTEDCONVERTEDTEXT: + return aFontMetrics.underlineSize; + case nsISelectionController::SELECTION_SPELLCHECK: { + // The thickness of the spellchecker underline shouldn't honor the font + // metrics. It should be constant pixels value which is decided from the + // default font size. Note that if the actual font size is smaller than + // the default font size, we should use the actual font size because the + // computed value from the default font size can be too thick for the + // current font size. + PRInt32 defaultFontSize = + aPresContext->AppUnitsToDevPixels(nsStyleFont(aPresContext).mFont.size); + gfxFloat fontSize = PR_MIN(gfxFloat(defaultFontSize), + aFontMetrics.emHeight); + fontSize = PR_MAX(fontSize, 1.0); + return NS_ceil(fontSize / 20); + } + default: + NS_WARNING("Requested underline style is not valid"); + return aFontMetrics.underlineSize; + } +} + +/** + * This, plus SelectionTypesWithDecorations, encapsulates all knowledge about + * drawing text decoration for selections. + */ +static void DrawSelectionDecorations(gfxContext* aContext, SelectionType aType, + nsTextFrame* aFrame, + nsTextPaintStyle& aTextPaintStyle, + const nsTextRangeStyle &aRangeStyle, + const gfxPoint& aPt, gfxFloat aWidth, + gfxFloat aAscent, const gfxFont::Metrics& aFontMetrics) +{ + gfxPoint pt(aPt); + gfxSize size(aWidth, + ComputeSelectionUnderlineHeight(aTextPaintStyle.PresContext(), + aFontMetrics, aType)); + gfxFloat descentLimit = + ComputeDescentLimitForSelectionUnderline(aTextPaintStyle.PresContext(), + aFrame, aFontMetrics); + + float relativeSize; + PRUint8 style; + nscolor color; + PRInt32 index = + nsTextPaintStyle::GetUnderlineStyleIndexForSelectionType(aType); + PRBool weDefineSelectionUnderline = + aTextPaintStyle.GetSelectionUnderlineForPaint(index, &color, + &relativeSize, &style); + + switch (aType) { + case nsISelectionController::SELECTION_IME_RAWINPUT: + case nsISelectionController::SELECTION_IME_SELECTEDRAWTEXT: + case nsISelectionController::SELECTION_IME_CONVERTEDTEXT: + case nsISelectionController::SELECTION_IME_SELECTEDCONVERTEDTEXT: { + // IME decoration lines should not be drawn on the both ends, i.e., we + // need to cut both edges of the decoration lines. Because same style + // IME selections can adjoin, but the users need to be able to know + // where are the boundaries of the selections. + // + // X: underline + // + // IME selection #1 IME selection #2 IME selection #3 + // | | | + // | XXXXXXXXXXXXXXXXXXX | XXXXXXXXXXXXXXXXXXXX | XXXXXXXXXXXXXXXXXXX + // +---------------------+----------------------+-------------------- + // ^ ^ ^ ^ ^ + // gap gap gap + pt.x += 1.0; + size.width -= 2.0; + if (aRangeStyle.IsDefined()) { + // If IME defines the style, that should override our definition. + if (aRangeStyle.IsLineStyleDefined()) { + if (aRangeStyle.mLineStyle == nsTextRangeStyle::LINESTYLE_NONE) { + return; + } + style = GetTextDecorationStyle(aRangeStyle); + relativeSize = aRangeStyle.mIsBoldLine ? 2.0f : 1.0f; + } else if (!weDefineSelectionUnderline) { + // There is no underline style definition. + return; + } + if (aRangeStyle.IsUnderlineColorDefined()) { + color = aRangeStyle.mUnderlineColor; + } else if (aRangeStyle.IsForegroundColorDefined()) { + color = aRangeStyle.mForegroundColor; + } else { + NS_ASSERTION(!aRangeStyle.IsBackgroundColorDefined(), + "Only the background color is defined"); + color = aTextPaintStyle.GetTextColor(); + } + } else if (!weDefineSelectionUnderline) { + // IME doesn't specify the selection style and we don't define selection + // underline. + return; + } + break; + } + case nsISelectionController::SELECTION_SPELLCHECK: + if (!weDefineSelectionUnderline) + return; + break; + default: + NS_WARNING("Requested selection decorations when there aren't any"); + return; + } + size.height *= relativeSize; + nsCSSRendering::PaintDecorationLine( + aContext, color, pt, size, aAscent, aFontMetrics.underlineOffset, + NS_STYLE_TEXT_DECORATION_UNDERLINE, style, descentLimit); +} + +/** + * This function encapsulates all knowledge of how selections affect foreground + * and background colors. + * @return true if the selection affects colors, false otherwise + * @param aForeground the foreground color to use + * @param aBackground the background color to use, or RGBA(0,0,0,0) if no + * background should be painted + */ +static PRBool GetSelectionTextColors(SelectionType aType, + nsTextPaintStyle& aTextPaintStyle, + const nsTextRangeStyle &aRangeStyle, + nscolor* aForeground, nscolor* aBackground) +{ + switch (aType) { + case nsISelectionController::SELECTION_NORMAL: + return aTextPaintStyle.GetSelectionColors(aForeground, aBackground); + case nsISelectionController::SELECTION_FIND: + aTextPaintStyle.GetHighlightColors(aForeground, aBackground); + return PR_TRUE; + case nsISelectionController::SELECTION_IME_RAWINPUT: + case nsISelectionController::SELECTION_IME_SELECTEDRAWTEXT: + case nsISelectionController::SELECTION_IME_CONVERTEDTEXT: + case nsISelectionController::SELECTION_IME_SELECTEDCONVERTEDTEXT: + if (aRangeStyle.IsDefined()) { + *aForeground = aTextPaintStyle.GetTextColor(); + *aBackground = NS_RGBA(0,0,0,0); + if (!aRangeStyle.IsForegroundColorDefined() && + !aRangeStyle.IsBackgroundColorDefined()) { + return PR_FALSE; + } + if (aRangeStyle.IsForegroundColorDefined()) { + *aForeground = aRangeStyle.mForegroundColor; + } + if (aRangeStyle.IsBackgroundColorDefined()) { + *aBackground = aRangeStyle.mBackgroundColor; + } + return PR_TRUE; + } + aTextPaintStyle.GetIMESelectionColors( + nsTextPaintStyle::GetUnderlineStyleIndexForSelectionType(aType), + aForeground, aBackground); + return PR_TRUE; + default: + *aForeground = aTextPaintStyle.GetTextColor(); + *aBackground = NS_RGBA(0,0,0,0); + return PR_FALSE; + } +} + +/** + * This class lets us iterate over chunks of text in a uniform selection state, + * observing cluster boundaries, in content order, maintaining the current + * x-offset as we go, and telling whether the text chunk has a hyphen after + * it or not. The caller is responsible for actually computing the advance + * width of each chunk. + */ +class SelectionIterator { +public: + /** + * aStart and aLength are in the original string. aSelectionDetails is + * according to the original string. + */ + SelectionIterator(SelectionDetails** aSelectionDetails, + PRInt32 aStart, PRInt32 aLength, + PropertyProvider& aProvider, gfxTextRun* aTextRun); + + /** + * Returns the next segment of uniformly selected (or not) text. + * @param aXOffset the offset from the origin of the frame to the start + * of the text (the left baseline origin for LTR, the right baseline origin + * for RTL) + * @param aOffset the transformed string offset of the text for this segment + * @param aLength the transformed string length of the text for this segment + * @param aHyphenWidth if a hyphen is to be rendered after the text, the + * width of the hyphen, otherwise zero + * @param aType the selection type for this segment + * @param aStyle the selection style for this segment + * @return false if there are no more segments + */ + PRBool GetNextSegment(gfxFloat* aXOffset, PRUint32* aOffset, PRUint32* aLength, + gfxFloat* aHyphenWidth, SelectionType* aType, + nsTextRangeStyle* aStyle); + void UpdateWithAdvance(gfxFloat aAdvance) { + mXOffset += aAdvance*mTextRun->GetDirection(); + } + +private: + SelectionDetails** mSelectionDetails; + PropertyProvider& mProvider; + gfxTextRun* mTextRun; + gfxSkipCharsIterator mIterator; + PRInt32 mOriginalStart; + PRInt32 mOriginalEnd; + gfxFloat mXOffset; +}; + +SelectionIterator::SelectionIterator(SelectionDetails** aSelectionDetails, + PRInt32 aStart, PRInt32 aLength, PropertyProvider& aProvider, + gfxTextRun* aTextRun) + : mSelectionDetails(aSelectionDetails), mProvider(aProvider), + mTextRun(aTextRun), mIterator(aProvider.GetStart()), + mOriginalStart(aStart), mOriginalEnd(aStart + aLength), + mXOffset(mTextRun->IsRightToLeft() ? aProvider.GetFrame()->GetSize().width : 0) +{ + mIterator.SetOriginalOffset(aStart); +} + +PRBool SelectionIterator::GetNextSegment(gfxFloat* aXOffset, + PRUint32* aOffset, PRUint32* aLength, gfxFloat* aHyphenWidth, + SelectionType* aType, nsTextRangeStyle* aStyle) +{ + if (mIterator.GetOriginalOffset() >= mOriginalEnd) + return PR_FALSE; + + // save offset into transformed string now + PRUint32 runOffset = mIterator.GetSkippedOffset(); + + PRInt32 index = mIterator.GetOriginalOffset() - mOriginalStart; + SelectionDetails* sdptr = mSelectionDetails[index]; + SelectionType type = + sdptr ? sdptr->mType : nsISelectionController::SELECTION_NONE; + nsTextRangeStyle style; + if (sdptr) { + style = sdptr->mTextRangeStyle; + } + for (++index; mOriginalStart + index < mOriginalEnd; ++index) { + if (sdptr != mSelectionDetails[index]) + break; + } + mIterator.SetOriginalOffset(index + mOriginalStart); + + // Advance to the next cluster boundary + while (mIterator.GetOriginalOffset() < mOriginalEnd && + !mIterator.IsOriginalCharSkipped() && + !mTextRun->IsClusterStart(mIterator.GetSkippedOffset())) { + mIterator.AdvanceOriginal(1); + } + + PRBool haveHyphenBreak = + (mProvider.GetFrame()->GetStateBits() & TEXT_HYPHEN_BREAK) != 0; + *aOffset = runOffset; + *aLength = mIterator.GetSkippedOffset() - runOffset; + *aXOffset = mXOffset; + *aHyphenWidth = 0; + if (mIterator.GetOriginalOffset() == mOriginalEnd && haveHyphenBreak) { + *aHyphenWidth = mProvider.GetHyphenWidth(); + } + *aType = type; + *aStyle = style; + return PR_TRUE; +} + +static void +AddHyphenToMetrics(nsTextFrame* aTextFrame, gfxTextRun* aBaseTextRun, + gfxTextRun::Metrics* aMetrics, + gfxFont::BoundingBoxType aBoundingBoxType, + gfxContext* aContext) +{ + // Fix up metrics to include hyphen + gfxTextRunCache::AutoTextRun hyphenTextRun( + GetHyphenTextRun(aBaseTextRun, aContext, aTextFrame)); + if (!hyphenTextRun.get()) + return; + + gfxTextRun::Metrics hyphenMetrics = + hyphenTextRun->MeasureText(0, hyphenTextRun->GetLength(), + aBoundingBoxType, aContext, nsnull); + aMetrics->CombineWith(hyphenMetrics, aBaseTextRun->IsRightToLeft()); +} + +void +nsTextFrame::PaintOneShadow(PRUint32 aOffset, PRUint32 aLength, + nsCSSShadowItem* aShadowDetails, + PropertyProvider* aProvider, const nsRect& aDirtyRect, + const gfxPoint& aFramePt, const gfxPoint& aTextBaselinePt, + gfxContext* aCtx, const nscolor& aForegroundColor) +{ + gfxPoint shadowOffset(aShadowDetails->mXOffset, aShadowDetails->mYOffset); + nscoord blurRadius = NS_MAX(aShadowDetails->mRadius, 0); + + gfxTextRun::Metrics shadowMetrics = + mTextRun->MeasureText(aOffset, aLength, gfxFont::LOOSE_INK_EXTENTS, + nsnull, aProvider); + if (GetStateBits() & TEXT_HYPHEN_BREAK) { + AddHyphenToMetrics(this, mTextRun, &shadowMetrics, gfxFont::LOOSE_INK_EXTENTS, aCtx); + } + + // This rect is the box which is equivalent to where the shadow will be painted. + // The origin of mBoundingBox is the text baseline left, so we must translate it by + // that much in order to make the origin the top-left corner of the text bounding box. + gfxRect shadowGfxRect = shadowMetrics.mBoundingBox + + gfxPoint(aFramePt.x, aTextBaselinePt.y) + shadowOffset; + nsRect shadowRect(shadowGfxRect.X(), shadowGfxRect.Y(), + shadowGfxRect.Width(), shadowGfxRect.Height());; + + nsContextBoxBlur contextBoxBlur; + gfxContext* shadowContext = contextBoxBlur.Init(shadowRect, blurRadius, + PresContext()->AppUnitsPerDevPixel(), + aCtx, aDirtyRect); + if (!shadowContext) + return; + + nscolor shadowColor; + if (aShadowDetails->mHasColor) + shadowColor = aShadowDetails->mColor; + else + shadowColor = aForegroundColor; + + aCtx->Save(); + aCtx->NewPath(); + aCtx->SetColor(gfxRGBA(shadowColor)); + + // Draw the text onto our alpha-only surface to capture the alpha values. + // Remember that the box blur context has a device offset on it, so we don't need to + // translate any coordinates to fit on the surface. + gfxRect dirtyGfxRect(aDirtyRect.x, aDirtyRect.y, aDirtyRect.width, aDirtyRect.height); + gfxFloat advanceWidth; + DrawText(shadowContext, + aTextBaselinePt + shadowOffset, + aOffset, aLength, &dirtyGfxRect, aProvider, advanceWidth, + (GetStateBits() & TEXT_HYPHEN_BREAK) != 0); + + // This will only have an effect in quirks mode. Standards mode text-decoration shadow painting + // is handled in nsHTMLContainerFrame.cpp, so you must remember to consider that if you change + // any code behaviour here. + nsTextPaintStyle textPaintStyle(this); + PaintTextDecorations(shadowContext, dirtyGfxRect, aFramePt + shadowOffset, + aTextBaselinePt + shadowOffset, + textPaintStyle, *aProvider, &shadowColor); + + contextBoxBlur.DoPaint(); + aCtx->Restore(); +} + +// Paints selection backgrounds and text in the correct colors. Also computes +// aAllTypes, the union of all selection types that are applying to this text. +void +nsTextFrame::PaintTextWithSelectionColors(gfxContext* aCtx, + const gfxPoint& aFramePt, + const gfxPoint& aTextBaselinePt, const gfxRect& aDirtyRect, + PropertyProvider& aProvider, nsTextPaintStyle& aTextPaintStyle, + SelectionDetails* aDetails, SelectionType* aAllTypes) +{ + PRInt32 contentOffset = aProvider.GetStart().GetOriginalOffset(); + PRInt32 contentLength = aProvider.GetOriginalLength(); + + // Figure out which selections control the colors to use for each character. + nsAutoTArray prevailingSelectionsBuffer; + if (!prevailingSelectionsBuffer.AppendElements(contentLength)) + return; + SelectionDetails** prevailingSelections = prevailingSelectionsBuffer.Elements(); + + PRInt32 i; + SelectionType allTypes = 0; + for (i = 0; i < contentLength; ++i) { + prevailingSelections[i] = nsnull; + } + + SelectionDetails *sdptr = aDetails; + PRBool anyBackgrounds = PR_FALSE; + while (sdptr) { + PRInt32 start = PR_MAX(0, sdptr->mStart - contentOffset); + PRInt32 end = PR_MIN(contentLength, sdptr->mEnd - contentOffset); + SelectionType type = sdptr->mType; + if (start < end) { + allTypes |= type; + // Ignore selections that don't set colors + nscolor foreground, background; + if (GetSelectionTextColors(type, aTextPaintStyle, sdptr->mTextRangeStyle, + &foreground, &background)) { + if (NS_GET_A(background) > 0) { + anyBackgrounds = PR_TRUE; + } + for (i = start; i < end; ++i) { + // Favour normal selection over IME selections + if (!prevailingSelections[i] || + type < prevailingSelections[i]->mType) { + prevailingSelections[i] = sdptr; + } + } + } + } + sdptr = sdptr->mNext; + } + *aAllTypes = allTypes; + + gfxFloat xOffset, hyphenWidth; + PRUint32 offset, length; // in transformed string + SelectionType type; + nsTextRangeStyle rangeStyle; + // Draw background colors + if (anyBackgrounds) { + SelectionIterator iterator(prevailingSelections, contentOffset, contentLength, + aProvider, mTextRun); + while (iterator.GetNextSegment(&xOffset, &offset, &length, &hyphenWidth, + &type, &rangeStyle)) { + nscolor foreground, background; + GetSelectionTextColors(type, aTextPaintStyle, rangeStyle, + &foreground, &background); + // Draw background color + gfxFloat advance = hyphenWidth + + mTextRun->GetAdvanceWidth(offset, length, &aProvider); + if (NS_GET_A(background) > 0) { + gfxFloat x = xOffset - (mTextRun->IsRightToLeft() ? advance : 0); + FillClippedRect(aCtx, aTextPaintStyle.PresContext(), + background, aDirtyRect, + gfxRect(aFramePt.x + x, aFramePt.y, advance, GetSize().height)); + } + iterator.UpdateWithAdvance(advance); + } + } + + // Draw text + SelectionIterator iterator(prevailingSelections, contentOffset, contentLength, + aProvider, mTextRun); + while (iterator.GetNextSegment(&xOffset, &offset, &length, &hyphenWidth, + &type, &rangeStyle)) { + nscolor foreground, background; + GetSelectionTextColors(type, aTextPaintStyle, rangeStyle, + &foreground, &background); + // Draw text segment + aCtx->SetColor(gfxRGBA(foreground)); + gfxFloat advance; + + DrawText(aCtx, gfxPoint(aFramePt.x + xOffset, aTextBaselinePt.y), + offset, length, &aDirtyRect, &aProvider, + advance, hyphenWidth > 0); + if (hyphenWidth) { + advance += hyphenWidth; + } + iterator.UpdateWithAdvance(advance); + } +} + +void +nsTextFrame::PaintTextSelectionDecorations(gfxContext* aCtx, + const gfxPoint& aFramePt, + const gfxPoint& aTextBaselinePt, const gfxRect& aDirtyRect, + PropertyProvider& aProvider, nsTextPaintStyle& aTextPaintStyle, + SelectionDetails* aDetails, SelectionType aSelectionType) +{ + PRInt32 contentOffset = aProvider.GetStart().GetOriginalOffset(); + PRInt32 contentLength = aProvider.GetOriginalLength(); + + // Figure out which characters will be decorated for this selection. + nsAutoTArray selectedCharsBuffer; + if (!selectedCharsBuffer.AppendElements(contentLength)) + return; + SelectionDetails** selectedChars = selectedCharsBuffer.Elements(); + PRInt32 i; + for (i = 0; i < contentLength; ++i) { + selectedChars[i] = nsnull; + } + + SelectionDetails *sdptr = aDetails; + while (sdptr) { + if (sdptr->mType == aSelectionType) { + PRInt32 start = PR_MAX(0, sdptr->mStart - contentOffset); + PRInt32 end = PR_MIN(contentLength, sdptr->mEnd - contentOffset); + for (i = start; i < end; ++i) { + selectedChars[i] = sdptr; + } + } + sdptr = sdptr->mNext; + } + + gfxFont* firstFont = aProvider.GetFontGroup()->GetFontAt(0); + if (!firstFont) + return; // OOM + gfxFont::Metrics decorationMetrics(firstFont->GetMetrics()); + decorationMetrics.underlineOffset = + aProvider.GetFontGroup()->GetUnderlineOffset(); + + SelectionIterator iterator(selectedChars, contentOffset, contentLength, + aProvider, mTextRun); + gfxFloat xOffset, hyphenWidth; + PRUint32 offset, length; + PRInt32 app = aTextPaintStyle.PresContext()->AppUnitsPerDevPixel(); + // XXX aTextBaselinePt is in AppUnits, shouldn't it be nsFloatPoint? + gfxPoint pt(0.0, (aTextBaselinePt.y - mAscent) / app); + SelectionType type; + nsTextRangeStyle selectedStyle; + while (iterator.GetNextSegment(&xOffset, &offset, &length, &hyphenWidth, + &type, &selectedStyle)) { + gfxFloat advance = hyphenWidth + + mTextRun->GetAdvanceWidth(offset, length, &aProvider); + if (type == aSelectionType) { + pt.x = (aFramePt.x + xOffset - + (mTextRun->IsRightToLeft() ? advance : 0)) / app; + gfxFloat width = PR_ABS(advance) / app; + DrawSelectionDecorations(aCtx, aSelectionType, this, aTextPaintStyle, + selectedStyle, + pt, width, mAscent / app, decorationMetrics); + } + iterator.UpdateWithAdvance(advance); + } +} + +PRBool +nsTextFrame::PaintTextWithSelection(gfxContext* aCtx, + const gfxPoint& aFramePt, + const gfxPoint& aTextBaselinePt, const gfxRect& aDirtyRect, + PropertyProvider& aProvider, nsTextPaintStyle& aTextPaintStyle) +{ + SelectionDetails* details = GetSelectionDetails(); + if (!details) + return PR_FALSE; + + SelectionType allTypes; + PaintTextWithSelectionColors(aCtx, aFramePt, aTextBaselinePt, aDirtyRect, + aProvider, aTextPaintStyle, details, &allTypes); + PaintTextDecorations(aCtx, aDirtyRect, aFramePt, aTextBaselinePt, + aTextPaintStyle, aProvider); + PRInt32 i; + // Iterate through just the selection types that paint decorations and + // paint decorations for any that actually occur in this frame. Paint + // higher-numbered selection types below lower-numered ones on the + // general principal that lower-numbered selections are higher priority. + allTypes &= SelectionTypesWithDecorations; + for (i = nsISelectionController::NUM_SELECTIONTYPES - 1; i >= 1; --i) { + SelectionType type = 1 << (i - 1); + if (allTypes & type) { + // There is some selection of this type. Try to paint its decorations + // (there might not be any for this type but that's OK, + // PaintTextSelectionDecorations will exit early). + PaintTextSelectionDecorations(aCtx, aFramePt, aTextBaselinePt, aDirtyRect, + aProvider, aTextPaintStyle, details, type); + } + } + + DestroySelectionDetails(details); + return PR_TRUE; +} + +static PRUint32 +ComputeTransformedLength(PropertyProvider& aProvider) +{ + gfxSkipCharsIterator iter(aProvider.GetStart()); + PRUint32 start = iter.GetSkippedOffset(); + iter.AdvanceOriginal(aProvider.GetOriginalLength()); + return iter.GetSkippedOffset() - start; +} + +gfxFloat +nsTextFrame::GetSnappedBaselineY(gfxContext* aContext, gfxFloat aY) +{ + gfxFloat appUnitsPerDevUnit = mTextRun->GetAppUnitsPerDevUnit(); + gfxFloat baseline = aY + mAscent; + gfxRect putativeRect(0, baseline/appUnitsPerDevUnit, 1, 1); + if (!aContext->UserToDevicePixelSnapped(putativeRect)) + return baseline; + return aContext->DeviceToUser(putativeRect.pos).y*appUnitsPerDevUnit; +} + +void +nsTextFrame::PaintText(nsIRenderingContext* aRenderingContext, nsPoint aPt, + const nsRect& aDirtyRect) +{ + // Don't pass in aRenderingContext here, because we need a *reference* + // context and aRenderingContext might have some transform in it + // XXX get the block and line passed to us somehow! This is slow! + gfxSkipCharsIterator iter = EnsureTextRun(); + if (!mTextRun) + return; + + nsTextPaintStyle textPaintStyle(this); + PropertyProvider provider(this, iter); + // Trim trailing whitespace + provider.InitializeForDisplay(PR_TRUE); + + gfxContext* ctx = aRenderingContext->ThebesContext(); + + gfxPoint framePt(aPt.x, aPt.y); + gfxPoint textBaselinePt( + mTextRun->IsRightToLeft() ? gfxFloat(aPt.x + GetSize().width) : framePt.x, + GetSnappedBaselineY(ctx, aPt.y)); + + gfxRect dirtyRect(aDirtyRect.x, aDirtyRect.y, + aDirtyRect.width, aDirtyRect.height); + + gfxFloat advanceWidth; + gfxRGBA foregroundColor = gfxRGBA(textPaintStyle.GetTextColor()); + + // Paint the text shadow before doing any foreground stuff + const nsStyleText* textStyle = GetStyleText(); + if (textStyle->mTextShadow) { + // Text shadow happens with the last value being painted at the back, + // ie. it is painted first. + for (PRUint32 i = textStyle->mTextShadow->Length(); i > 0; --i) { + PaintOneShadow(provider.GetStart().GetSkippedOffset(), + ComputeTransformedLength(provider), + textStyle->mTextShadow->ShadowAt(i - 1), &provider, + aDirtyRect, framePt, textBaselinePt, ctx, + textPaintStyle.GetTextColor()); + } + } + + // Fork off to the (slower) paint-with-selection path if necessary. + if (nsLayoutUtils::GetNonGeneratedAncestor(this)->GetStateBits() & NS_FRAME_SELECTED_CONTENT) { + if (PaintTextWithSelection(ctx, framePt, textBaselinePt, + dirtyRect, provider, textPaintStyle)) + return; + } + + ctx->SetColor(foregroundColor); + + DrawText(ctx, textBaselinePt, provider.GetStart().GetSkippedOffset(), + ComputeTransformedLength(provider), &dirtyRect, + &provider, advanceWidth, + (GetStateBits() & TEXT_HYPHEN_BREAK) != 0); + PaintTextDecorations(ctx, dirtyRect, framePt, textBaselinePt, + textPaintStyle, provider); +} + +void +nsTextFrame::DrawText(gfxContext* aCtx, const gfxPoint& aTextBaselinePt, + PRUint32 aOffset, PRUint32 aLength, + const gfxRect* aDirtyRect, PropertyProvider* aProvider, + gfxFloat& aAdvanceWidth, PRBool aDrawSoftHyphen) +{ + // Paint the text and soft-hyphen (if any) onto the given graphics context + mTextRun->Draw(aCtx, aTextBaselinePt, aOffset, aLength, + aDirtyRect, aProvider, &aAdvanceWidth); + + if (aDrawSoftHyphen) { + // Don't use ctx as the context, because we need a reference context here, + // ctx may be transformed. + gfxTextRunCache::AutoTextRun hyphenTextRun(GetHyphenTextRun(mTextRun, nsnull, this)); + if (hyphenTextRun.get()) { + // For right-to-left text runs, the soft-hyphen is positioned at the left + // of the text, minus its own width + gfxFloat hyphenBaselineX = aTextBaselinePt.x + mTextRun->GetDirection() * aAdvanceWidth - + (mTextRun->IsRightToLeft() ? hyphenTextRun->GetAdvanceWidth(0, hyphenTextRun->GetLength(), nsnull) : 0); + hyphenTextRun->Draw(aCtx, gfxPoint(hyphenBaselineX, aTextBaselinePt.y), + 0, hyphenTextRun->GetLength(), aDirtyRect, nsnull, nsnull); + } + } +} + +PRInt16 +nsTextFrame::GetSelectionStatus(PRInt16* aSelectionFlags) +{ + // get the selection controller + nsCOMPtr selectionController; + nsresult rv = GetSelectionController(PresContext(), + getter_AddRefs(selectionController)); + if (NS_FAILED(rv) || !selectionController) + return nsISelectionController::SELECTION_OFF; + + selectionController->GetSelectionFlags(aSelectionFlags); + + PRInt16 selectionValue; + selectionController->GetDisplaySelection(&selectionValue); + + return selectionValue; +} + +PRBool +nsTextFrame::IsVisibleInSelection(nsISelection* aSelection) +{ + // Check the quick way first + PRBool isSelected = (mState & NS_FRAME_SELECTED_CONTENT) == NS_FRAME_SELECTED_CONTENT; + if (!isSelected) + return PR_FALSE; + + SelectionDetails* details = GetSelectionDetails(); + PRBool found = PR_FALSE; + + // where are the selection points "really" + SelectionDetails *sdptr = details; + while (sdptr) { + if (sdptr->mEnd > GetContentOffset() && + sdptr->mStart < GetContentEnd() && + sdptr->mType == nsISelectionController::SELECTION_NORMAL) { + found = PR_TRUE; + break; + } + sdptr = sdptr->mNext; + } + DestroySelectionDetails(details); + + return found; +} + +/** + * Compute the longest prefix of text whose width is <= aWidth. Return + * the length of the prefix. Also returns the width of the prefix in aFitWidth. + */ +static PRUint32 +CountCharsFit(gfxTextRun* aTextRun, PRUint32 aStart, PRUint32 aLength, + gfxFloat aWidth, PropertyProvider* aProvider, + gfxFloat* aFitWidth) +{ + PRUint32 last = 0; + gfxFloat width = 0; + PRUint32 i; + for (i = 1; i <= aLength; ++i) { + if (i == aLength || aTextRun->IsClusterStart(aStart + i)) { + gfxFloat nextWidth = width + + aTextRun->GetAdvanceWidth(aStart + last, i - last, aProvider); + if (nextWidth > aWidth) + break; + last = i; + width = nextWidth; + } + } + *aFitWidth = width; + return last; +} + +nsIFrame::ContentOffsets +nsTextFrame::CalcContentOffsetsFromFramePoint(nsPoint aPoint) +{ + return GetCharacterOffsetAtFramePointInternal(aPoint, PR_TRUE); +} + +nsIFrame::ContentOffsets +nsTextFrame::GetCharacterOffsetAtFramePoint(const nsPoint &aPoint) +{ + return GetCharacterOffsetAtFramePointInternal(aPoint, PR_FALSE); +} + +nsIFrame::ContentOffsets +nsTextFrame::GetCharacterOffsetAtFramePointInternal(const nsPoint &aPoint, + PRBool aForInsertionPoint) +{ + ContentOffsets offsets; + + gfxSkipCharsIterator iter = EnsureTextRun(); + if (!mTextRun) + return offsets; + + PropertyProvider provider(this, iter); + // Trim leading but not trailing whitespace if possible + provider.InitializeForDisplay(PR_FALSE); + gfxFloat width = mTextRun->IsRightToLeft() ? mRect.width - aPoint.x : aPoint.x; + gfxFloat fitWidth; + PRUint32 skippedLength = ComputeTransformedLength(provider); + + PRUint32 charsFit = CountCharsFit(mTextRun, + provider.GetStart().GetSkippedOffset(), skippedLength, width, &provider, &fitWidth); + + PRInt32 selectedOffset; + if (charsFit < skippedLength) { + // charsFit characters fitted, but no more could fit. See if we're + // more than halfway through the cluster.. If we are, choose the next + // cluster. + gfxSkipCharsIterator extraCluster(provider.GetStart()); + extraCluster.AdvanceSkipped(charsFit); + gfxSkipCharsIterator extraClusterLastChar(extraCluster); + FindClusterEnd(mTextRun, + provider.GetStart().GetOriginalOffset() + provider.GetOriginalLength(), + &extraClusterLastChar); + gfxFloat charWidth = + mTextRun->GetAdvanceWidth(extraCluster.GetSkippedOffset(), + GetSkippedDistance(extraCluster, extraClusterLastChar) + 1, + &provider); + selectedOffset = !aForInsertionPoint || width <= fitWidth + charWidth/2 + ? extraCluster.GetOriginalOffset() + : extraClusterLastChar.GetOriginalOffset() + 1; + } else { + // All characters fitted, we're at (or beyond) the end of the text. + // XXX This could be some pathological situation where negative spacing + // caused characters to move backwards. We can't really handle that + // in the current frame system because frames can't have negative + // intrinsic widths. + selectedOffset = + provider.GetStart().GetOriginalOffset() + provider.GetOriginalLength(); + } + + offsets.content = GetContent(); + offsets.offset = offsets.secondaryOffset = selectedOffset; + offsets.associateWithNext = mContentOffset == offsets.offset; + return offsets; +} + +PRBool +nsTextFrame::CombineSelectionUnderlineRect(nsPresContext* aPresContext, + nsRect& aRect) +{ + if (aRect.IsEmpty()) + return PR_FALSE; + + nsRect givenRect = aRect; + + nsCOMPtr fm; + nsLayoutUtils::GetFontMetricsForFrame(this, getter_AddRefs(fm)); + nsIThebesFontMetrics* tfm = static_cast(fm.get()); + gfxFontGroup* fontGroup = tfm->GetThebesFontGroup(); + gfxFont* firstFont = fontGroup->GetFontAt(0); + if (!firstFont) + return PR_FALSE; // OOM + const gfxFont::Metrics& metrics = firstFont->GetMetrics(); + gfxFloat underlineOffset = fontGroup->GetUnderlineOffset(); + gfxFloat ascent = aPresContext->AppUnitsToGfxUnits(mAscent); + gfxFloat descentLimit = + ComputeDescentLimitForSelectionUnderline(aPresContext, this, metrics); + + SelectionDetails *details = GetSelectionDetails(); + for (SelectionDetails *sd = details; sd; sd = sd->mNext) { + if (sd->mStart == sd->mEnd || !(sd->mType & SelectionTypesWithDecorations)) + continue; + + PRUint8 style; + float relativeSize; + PRInt32 index = + nsTextPaintStyle::GetUnderlineStyleIndexForSelectionType(sd->mType); + if (sd->mType == nsISelectionController::SELECTION_SPELLCHECK) { + if (!nsTextPaintStyle::GetSelectionUnderline(aPresContext, index, nsnull, + &relativeSize, &style)) { + continue; + } + } else { + // IME selections + nsTextRangeStyle& rangeStyle = sd->mTextRangeStyle; + if (rangeStyle.IsDefined()) { + if (!rangeStyle.IsLineStyleDefined() || + rangeStyle.mLineStyle == nsTextRangeStyle::LINESTYLE_NONE) { + continue; + } + style = GetTextDecorationStyle(rangeStyle); + relativeSize = rangeStyle.mIsBoldLine ? 2.0f : 1.0f; + } else if (!nsTextPaintStyle::GetSelectionUnderline(aPresContext, index, + nsnull, &relativeSize, + &style)) { + continue; + } + } + nsRect decorationArea; + gfxSize size(aPresContext->AppUnitsToGfxUnits(aRect.width), + ComputeSelectionUnderlineHeight(aPresContext, + metrics, sd->mType)); + relativeSize = PR_MAX(relativeSize, 1.0f); + size.height *= relativeSize; + decorationArea = + nsCSSRendering::GetTextDecorationRect(aPresContext, size, + ascent, underlineOffset, + NS_STYLE_TEXT_DECORATION_UNDERLINE, + style, descentLimit); + aRect.UnionRect(aRect, decorationArea); + } + DestroySelectionDetails(details); + + return !aRect.IsEmpty() && !givenRect.Contains(aRect); +} + +void +nsTextFrame::SetSelected(PRBool aSelected, + SelectionType aType) +{ + SetSelectedRange(0, mContent->GetText()->GetLength(), aSelected, aType); +} + +void +nsTextFrame::SetSelectedRange(PRUint32 aStart, + PRUint32 aEnd, + PRBool aSelected, + SelectionType aType) +{ + NS_ASSERTION(!GetPrevContinuation(), "Should only be called for primary frame"); + DEBUG_VERIFY_NOT_DIRTY(mState); + + // Selection is collapsed, which can't affect text frame rendering + if (aStart == aEnd) + return; + + if (aType == nsISelectionController::SELECTION_NORMAL) { + // check whether style allows selection + PRBool selectable; + IsSelectable(&selectable, nsnull); + if (!selectable) + return; + } + + PRBool anySelected = PR_FALSE; + + nsTextFrame* f = this; + while (f && f->GetContentEnd() <= PRInt32(aStart)) { + if (f->GetStateBits() & NS_FRAME_SELECTED_CONTENT) { + anySelected = PR_TRUE; + } + f = static_cast(f->GetNextContinuation()); + } + + nsPresContext* presContext = PresContext(); + while (f && f->GetContentOffset() < PRInt32(aEnd)) { + if (aSelected) { + f->AddStateBits(NS_FRAME_SELECTED_CONTENT); + anySelected = PR_TRUE; + } else { // we need to see if any other selection is available. + SelectionDetails *details = f->GetSelectionDetails(); + if (details) { + anySelected = PR_TRUE; + DestroySelectionDetails(details); + } else { + f->RemoveStateBits(NS_FRAME_SELECTED_CONTENT); + } + } + + // We may need to reflow to recompute the overflow area for + // spellchecking or IME underline if their underline is thicker than + // the normal decoration line. + PRBool didHaveOverflowingSelection = + (f->GetStateBits() & TEXT_SELECTION_UNDERLINE_OVERFLOWED) != 0; + nsRect r(nsPoint(0, 0), GetSize()); + PRBool willHaveOverflowingSelection = + aSelected && f->CombineSelectionUnderlineRect(presContext, r); + if (didHaveOverflowingSelection || willHaveOverflowingSelection) { + presContext->PresShell()->FrameNeedsReflow(f, + nsIPresShell::eStyleChange, + NS_FRAME_IS_DIRTY); + } + // Selection might change anything. Invalidate the overflow area. + f->InvalidateOverflowRect(); + + f = static_cast(f->GetNextContinuation()); + } + + // Scan remaining continuations to see if any are selected + while (f && !anySelected) { + if (f->GetStateBits() & NS_FRAME_SELECTED_CONTENT) { + anySelected = PR_TRUE; + } + f = static_cast(f->GetNextContinuation()); + } + + if (anySelected) { + mContent->SetFlags(NS_TEXT_IN_SELECTION); + } else { + // This is only legal because there is only one presentation for the + // content with a selection + mContent->UnsetFlags(NS_TEXT_IN_SELECTION); + } +} + +NS_IMETHODIMP +nsTextFrame::GetPointFromOffset(PRInt32 inOffset, + nsPoint* outPoint) +{ + if (!outPoint) + return NS_ERROR_NULL_POINTER; + + outPoint->x = 0; + outPoint->y = 0; + + DEBUG_VERIFY_NOT_DIRTY(mState); + if (mState & NS_FRAME_IS_DIRTY) + return NS_ERROR_UNEXPECTED; + + if (GetContentLength() <= 0) { + return NS_OK; + } + + gfxSkipCharsIterator iter = EnsureTextRun(); + if (!mTextRun) + return NS_ERROR_FAILURE; + + PropertyProvider properties(this, iter); + // Don't trim trailing whitespace, we want the caret to appear in the right + // place if it's positioned there + properties.InitializeForDisplay(PR_FALSE); + + if (inOffset < GetContentOffset()){ + NS_WARNING("offset before this frame's content"); + inOffset = GetContentOffset(); + } else if (inOffset > GetContentEnd()) { + NS_WARNING("offset after this frame's content"); + inOffset = GetContentEnd(); + } + PRInt32 trimmedOffset = properties.GetStart().GetOriginalOffset(); + PRInt32 trimmedEnd = trimmedOffset + properties.GetOriginalLength(); + inOffset = PR_MAX(inOffset, trimmedOffset); + inOffset = PR_MIN(inOffset, trimmedEnd); + + iter.SetOriginalOffset(inOffset); + + if (inOffset < trimmedEnd && + !iter.IsOriginalCharSkipped() && + !mTextRun->IsClusterStart(iter.GetSkippedOffset())) { + NS_WARNING("GetPointFromOffset called for non-cluster boundary"); + FindClusterStart(mTextRun, trimmedOffset, &iter); + } + + gfxFloat advanceWidth = + mTextRun->GetAdvanceWidth(properties.GetStart().GetSkippedOffset(), + GetSkippedDistance(properties.GetStart(), iter), + &properties); + nscoord width = NSToCoordCeilClamped(advanceWidth); + + if (mTextRun->IsRightToLeft()) { + outPoint->x = mRect.width - width; + } else { + outPoint->x = width; + } + outPoint->y = 0; + + return NS_OK; +} + +NS_IMETHODIMP +nsTextFrame::GetChildFrameContainingOffset(PRInt32 aContentOffset, + PRBool aHint, + PRInt32* aOutOffset, + nsIFrame**aOutFrame) +{ + DEBUG_VERIFY_NOT_DIRTY(mState); +#if 0 //XXXrbs disable due to bug 310227 + if (mState & NS_FRAME_IS_DIRTY) + return NS_ERROR_UNEXPECTED; +#endif + + NS_ASSERTION(aOutOffset && aOutFrame, "Bad out parameters"); + NS_ASSERTION(aContentOffset >= 0, "Negative content offset, existing code was very broken!"); + + nsTextFrame* f = this; + if (aContentOffset >= mContentOffset) { + while (PR_TRUE) { + nsTextFrame* next = static_cast(f->GetNextContinuation()); + if (!next || aContentOffset < next->GetContentOffset()) + break; + if (aContentOffset == next->GetContentOffset()) { + if (aHint) { + f = next; + } + break; + } + f = next; + } + } else { + while (PR_TRUE) { + nsTextFrame* prev = static_cast(f->GetPrevContinuation()); + if (!prev || aContentOffset > f->GetContentOffset()) + break; + if (aContentOffset == f->GetContentOffset()) { + if (!aHint) { + f = prev; + } + break; + } + f = prev; + } + } + + *aOutOffset = aContentOffset - f->GetContentOffset(); + *aOutFrame = f; + return NS_OK; +} + +PRBool +nsTextFrame::PeekOffsetNoAmount(PRBool aForward, PRInt32* aOffset) +{ + NS_ASSERTION(aOffset && *aOffset <= GetContentLength(), "aOffset out of range"); + + gfxSkipCharsIterator iter = EnsureTextRun(); + if (!mTextRun) + return PR_FALSE; + + TrimmedOffsets trimmed = GetTrimmedOffsets(GetFragment(), PR_TRUE); + // Check whether there are nonskipped characters in the trimmmed range + return iter.ConvertOriginalToSkipped(trimmed.GetEnd()) > + iter.ConvertOriginalToSkipped(trimmed.mStart); +} + +/** + * This class iterates through the clusters before or after the given + * aPosition (which is a content offset). You can test each cluster + * to see if it's whitespace (as far as selection/caret movement is concerned), + * or punctuation, or if there is a word break before the cluster. ("Before" + * is interpreted according to aDirection, so if aDirection is -1, "before" + * means actually *after* the cluster content.) + */ +class NS_STACK_CLASS ClusterIterator { +public: + ClusterIterator(nsTextFrame* aTextFrame, PRInt32 aPosition, PRInt32 aDirection, + nsString& aContext); + + PRBool NextCluster(); + PRBool IsWhitespace(); + PRBool IsPunctuation(); + PRBool HaveWordBreakBefore() { return mHaveWordBreak; } + PRInt32 GetAfterOffset(); + PRInt32 GetBeforeOffset(); + +private: + nsCOMPtr mCategories; + gfxSkipCharsIterator mIterator; + const nsTextFragment* mFrag; + nsTextFrame* mTextFrame; + PRInt32 mDirection; + PRInt32 mCharIndex; + nsTextFrame::TrimmedOffsets mTrimmed; + nsTArray mWordBreaks; + PRPackedBool mHaveWordBreak; +}; + +static PRBool +IsAcceptableCaretPosition(const gfxSkipCharsIterator& aIter, gfxTextRun* aTextRun, + nsIFrame* aFrame) +{ + if (aIter.IsOriginalCharSkipped()) + return PR_FALSE; + PRUint32 index = aIter.GetSkippedOffset(); + if (!aTextRun->IsClusterStart(index)) + return PR_FALSE; + return !(aFrame->GetStyleText()->NewlineIsSignificant() && + aTextRun->GetChar(index) == '\n'); +} + +PRBool +nsTextFrame::PeekOffsetCharacter(PRBool aForward, PRInt32* aOffset) +{ + PRInt32 contentLength = GetContentLength(); + NS_ASSERTION(aOffset && *aOffset <= contentLength, "aOffset out of range"); + + PRBool selectable; + PRUint8 selectStyle; + IsSelectable(&selectable, &selectStyle); + if (selectStyle == NS_STYLE_USER_SELECT_ALL) + return PR_FALSE; + + gfxSkipCharsIterator iter = EnsureTextRun(); + if (!mTextRun) + return PR_FALSE; + + TrimmedOffsets trimmed = GetTrimmedOffsets(GetFragment(), PR_FALSE); + + // A negative offset means "end of frame". + PRInt32 startOffset = GetContentOffset() + (*aOffset < 0 ? contentLength : *aOffset); + + if (!aForward) { + PRInt32 i; + for (i = PR_MIN(trimmed.GetEnd(), startOffset) - 1; + i >= trimmed.mStart; --i) { + iter.SetOriginalOffset(i); + if (IsAcceptableCaretPosition(iter, mTextRun, this)) { + *aOffset = i - mContentOffset; + return PR_TRUE; + } + } + *aOffset = 0; + } else { + PRInt32 i; + for (i = startOffset + 1; i <= trimmed.GetEnd(); ++i) { + iter.SetOriginalOffset(i); + // XXX we can't necessarily stop at the end of this frame, + // but we really have no choice right now. We need to do a deeper + // fix/restructuring of PeekOffsetCharacter + if (i == trimmed.GetEnd() || + IsAcceptableCaretPosition(iter, mTextRun, this)) { + *aOffset = i - mContentOffset; + return PR_TRUE; + } + } + *aOffset = contentLength; + } + + return PR_FALSE; +} + +PRBool +ClusterIterator::IsWhitespace() +{ + NS_ASSERTION(mCharIndex >= 0, "No cluster selected"); + return IsSelectionSpace(mFrag, mCharIndex); +} + +PRBool +ClusterIterator::IsPunctuation() +{ + NS_ASSERTION(mCharIndex >= 0, "No cluster selected"); + if (!mCategories) + return PR_FALSE; + nsIUGenCategory::nsUGenCategory c = mCategories->Get(mFrag->CharAt(mCharIndex)); + return c == nsIUGenCategory::kPunctuation || c == nsIUGenCategory::kSymbol; +} + +PRInt32 +ClusterIterator::GetBeforeOffset() +{ + NS_ASSERTION(mCharIndex >= 0, "No cluster selected"); + return mCharIndex + (mDirection > 0 ? 0 : 1); +} + +PRInt32 +ClusterIterator::GetAfterOffset() +{ + NS_ASSERTION(mCharIndex >= 0, "No cluster selected"); + return mCharIndex + (mDirection > 0 ? 1 : 0); +} + +PRBool +ClusterIterator::NextCluster() +{ + if (!mDirection) + return PR_FALSE; + gfxTextRun* textRun = mTextFrame->GetTextRun(); + + mHaveWordBreak = PR_FALSE; + while (PR_TRUE) { + PRBool keepGoing = PR_FALSE; + if (mDirection > 0) { + if (mIterator.GetOriginalOffset() >= mTrimmed.GetEnd()) + return PR_FALSE; + keepGoing = mIterator.IsOriginalCharSkipped() || + mIterator.GetOriginalOffset() < mTrimmed.mStart || + !textRun->IsClusterStart(mIterator.GetSkippedOffset()); + mCharIndex = mIterator.GetOriginalOffset(); + mIterator.AdvanceOriginal(1); + } else { + if (mIterator.GetOriginalOffset() <= mTrimmed.mStart) + return PR_FALSE; + mIterator.AdvanceOriginal(-1); + keepGoing = mIterator.IsOriginalCharSkipped() || + mIterator.GetOriginalOffset() >= mTrimmed.GetEnd() || + !textRun->IsClusterStart(mIterator.GetSkippedOffset()); + mCharIndex = mIterator.GetOriginalOffset(); + } + + if (mWordBreaks[GetBeforeOffset() - mTextFrame->GetContentOffset()]) { + mHaveWordBreak = PR_TRUE; + } + if (!keepGoing) + return PR_TRUE; + } +} + +ClusterIterator::ClusterIterator(nsTextFrame* aTextFrame, PRInt32 aPosition, + PRInt32 aDirection, nsString& aContext) + : mTextFrame(aTextFrame), mDirection(aDirection), mCharIndex(-1) +{ + mIterator = aTextFrame->EnsureTextRun(); + if (!aTextFrame->GetTextRun()) { + mDirection = 0; // signal failure + return; + } + mIterator.SetOriginalOffset(aPosition); + + mCategories = do_GetService(NS_UNICHARCATEGORY_CONTRACTID); + + mFrag = aTextFrame->GetFragment(); + mTrimmed = aTextFrame->GetTrimmedOffsets(mFrag, PR_TRUE); + + PRInt32 textOffset = aTextFrame->GetContentOffset(); + PRInt32 textLen = aTextFrame->GetContentLength(); + if (!mWordBreaks.AppendElements(textLen + 1)) { + mDirection = 0; // signal failure + return; + } + memset(mWordBreaks.Elements(), PR_FALSE, textLen + 1); + PRInt32 textStart; + if (aDirection > 0) { + if (aContext.IsEmpty()) { + // No previous context, so it must be the start of a line or text run + mWordBreaks[0] = PR_TRUE; + } + textStart = aContext.Length(); + mFrag->AppendTo(aContext, textOffset, textLen); + } else { + if (aContext.IsEmpty()) { + // No following context, so it must be the end of a line or text run + mWordBreaks[textLen] = PR_TRUE; + } + textStart = 0; + nsAutoString str; + mFrag->AppendTo(str, textOffset, textLen); + aContext.Insert(str, 0); + } + nsIWordBreaker* wordBreaker = nsContentUtils::WordBreaker(); + PRInt32 i; + for (i = 0; i <= textLen; ++i) { + PRInt32 indexInText = i + textStart; + mWordBreaks[i] |= + wordBreaker->BreakInBetween(aContext.get(), indexInText, + aContext.get() + indexInText, + aContext.Length() - indexInText); + } +} + +PRBool +nsTextFrame::PeekOffsetWord(PRBool aForward, PRBool aWordSelectEatSpace, PRBool aIsKeyboardSelect, + PRInt32* aOffset, PeekWordState* aState) +{ + PRInt32 contentLength = GetContentLength(); + NS_ASSERTION (aOffset && *aOffset <= contentLength, "aOffset out of range"); + + PRBool selectable; + PRUint8 selectStyle; + IsSelectable(&selectable, &selectStyle); + if (selectStyle == NS_STYLE_USER_SELECT_ALL) + return PR_FALSE; + + PRInt32 offset = GetContentOffset() + (*aOffset < 0 ? contentLength : *aOffset); + ClusterIterator cIter(this, offset, aForward ? 1 : -1, aState->mContext); + + if (!cIter.NextCluster()) + return PR_FALSE; + + do { + PRBool isPunctuation = cIter.IsPunctuation(); + PRBool isWhitespace = cIter.IsWhitespace(); + PRBool isWordBreakBefore = cIter.HaveWordBreakBefore(); + if (aWordSelectEatSpace == isWhitespace && !aState->mSawBeforeType) { + aState->SetSawBeforeType(); + aState->Update(isPunctuation, isWhitespace); + continue; + } + // See if we can break before the current cluster + if (!aState->mAtStart) { + PRBool canBreak; + if (isPunctuation != aState->mLastCharWasPunctuation) { + canBreak = BreakWordBetweenPunctuation(aState, aForward, + isPunctuation, isWhitespace, aIsKeyboardSelect); + } else if (!aState->mLastCharWasWhitespace && + !isWhitespace && !isPunctuation && isWordBreakBefore) { + // if both the previous and the current character are not white + // space but this can be word break before, we don't need to eat + // a white space in this case. This case happens in some languages + // that their words are not separated by white spaces. E.g., + // Japanese and Chinese. + canBreak = PR_TRUE; + } else { + canBreak = isWordBreakBefore && aState->mSawBeforeType; + } + if (canBreak) { + *aOffset = cIter.GetBeforeOffset() - mContentOffset; + return PR_TRUE; + } + } + aState->Update(isPunctuation, isWhitespace); + } while (cIter.NextCluster()); + + *aOffset = cIter.GetAfterOffset() - mContentOffset; + return PR_FALSE; +} + + // TODO this needs to be deCOMtaminated with the interface fixed in +// nsIFrame.h, but we won't do that until the old textframe is gone. +NS_IMETHODIMP +nsTextFrame::CheckVisibility(nsPresContext* aContext, PRInt32 aStartIndex, + PRInt32 aEndIndex, PRBool aRecurse, PRBool *aFinished, PRBool *aRetval) +{ + if (!aRetval) + return NS_ERROR_NULL_POINTER; + + // Text in the range is visible if there is at least one character in the range + // that is not skipped and is mapped by this frame (which is the primary frame) + // or one of its continuations. + for (nsTextFrame* f = this; f; + f = static_cast(GetNextContinuation())) { + if (f->PeekOffsetNoAmount(PR_TRUE, nsnull)) { + *aRetval = PR_TRUE; + return NS_OK; + } + } + + *aRetval = PR_FALSE; + return NS_OK; +} + +NS_IMETHODIMP +nsTextFrame::GetOffsets(PRInt32 &start, PRInt32 &end) const +{ + start = GetContentOffset(); + end = GetContentEnd(); + return NS_OK; +} + +static PRInt32 +FindEndOfPunctuationRun(const nsTextFragment* aFrag, + gfxTextRun* aTextRun, + gfxSkipCharsIterator* aIter, + PRInt32 aOffset, + PRInt32 aStart, + PRInt32 aEnd) +{ + PRInt32 i; + + for (i = aStart; i < aEnd - aOffset; ++i) { + if (nsContentUtils::IsPunctuationMarkAt(aFrag, aOffset + i)) { + aIter->SetOriginalOffset(aOffset + i); + FindClusterEnd(aTextRun, aEnd, aIter); + i = aIter->GetOriginalOffset() - aOffset; + } else { + break; + } + } + return i; +} + +/** + * Returns PR_TRUE if this text frame completes the first-letter, PR_FALSE + * if it does not contain a true "letter". + * If returns PR_TRUE, then it also updates aLength to cover just the first-letter + * text. + * + * XXX :first-letter should be handled during frame construction + * (and it has a good bit in common with nextBidi) + * + * @param aLength an in/out parameter: on entry contains the maximum length to + * return, on exit returns length of the first-letter fragment (which may + * include leading and trailing punctuation, for example) + */ +static PRBool +FindFirstLetterRange(const nsTextFragment* aFrag, + gfxTextRun* aTextRun, + PRInt32 aOffset, const gfxSkipCharsIterator& aIter, + PRInt32* aLength) +{ + PRInt32 i; + PRInt32 length = *aLength; + PRInt32 endOffset = aOffset + length; + gfxSkipCharsIterator iter(aIter); + + // skip leading whitespace, then consume clusters that start with punctuation + i = FindEndOfPunctuationRun(aFrag, aTextRun, &iter, aOffset, + GetTrimmableWhitespaceCount(aFrag, aOffset, length, 1), + endOffset); + if (i == length) + return PR_FALSE; + + // If the next character is not a letter or number, there is no first-letter. + // Return PR_TRUE so that we don't go on looking, but set aLength to 0. + if (!nsContentUtils::IsAlphanumericAt(aFrag, aOffset + i)) { + *aLength = 0; + return PR_TRUE; + } + + // consume another cluster (the actual first letter) + iter.SetOriginalOffset(aOffset + i); + FindClusterEnd(aTextRun, endOffset, &iter); + i = iter.GetOriginalOffset() - aOffset; + if (i + 1 == length) + return PR_TRUE; + + // consume clusters that start with punctuation + i = FindEndOfPunctuationRun(aFrag, aTextRun, &iter, aOffset, i + 1, endOffset); + if (i < length) + *aLength = i; + return PR_TRUE; +} + +static PRUint32 +FindStartAfterSkippingWhitespace(PropertyProvider* aProvider, + nsIFrame::InlineIntrinsicWidthData* aData, + const nsStyleText* aTextStyle, + gfxSkipCharsIterator* aIterator, + PRUint32 aFlowEndInTextRun) +{ + if (aData->skipWhitespace) { + while (aIterator->GetSkippedOffset() < aFlowEndInTextRun && + IsTrimmableSpace(aProvider->GetFragment(), aIterator->GetOriginalOffset(), aTextStyle)) { + aIterator->AdvanceOriginal(1); + } + } + return aIterator->GetSkippedOffset(); +} + +/* virtual */ +void nsTextFrame::MarkIntrinsicWidthsDirty() +{ + ClearTextRun(); + nsFrame::MarkIntrinsicWidthsDirty(); +} + +// XXX this doesn't handle characters shaped by line endings. We need to +// temporarily override the "current line ending" settings. +void +nsTextFrame::AddInlineMinWidthForFlow(nsIRenderingContext *aRenderingContext, + nsIFrame::InlineMinWidthData *aData) +{ + PRUint32 flowEndInTextRun; + gfxContext* ctx = aRenderingContext->ThebesContext(); + gfxSkipCharsIterator iter = + EnsureTextRun(ctx, aData->lineContainer, aData->line, &flowEndInTextRun); + if (!mTextRun) + return; + + // Pass null for the line container. This will disable tab spacing, but that's + // OK since we can't really handle tabs for intrinsic sizing anyway. + const nsStyleText* textStyle = GetStyleText(); + const nsTextFragment* frag = GetFragment(); + PropertyProvider provider(mTextRun, textStyle, frag, this, + iter, PR_INT32_MAX, nsnull, 0); + + PRBool collapseWhitespace = !textStyle->WhiteSpaceIsSignificant(); + PRBool preformatNewlines = textStyle->NewlineIsSignificant(); + PRBool preformatTabs = textStyle->WhiteSpaceIsSignificant(); + gfxFloat tabWidth = -1; + PRUint32 start = + FindStartAfterSkippingWhitespace(&provider, aData, textStyle, &iter, flowEndInTextRun); + + // XXX Should we consider hyphenation here? + for (PRUint32 i = start, wordStart = start; i <= flowEndInTextRun; ++i) { + PRBool preformattedNewline = PR_FALSE; + PRBool preformattedTab = PR_FALSE; + if (i < flowEndInTextRun) { + // XXXldb Shouldn't we be including the newline as part of the + // segment that it ends rather than part of the segment that it + // starts? + preformattedNewline = preformatNewlines && mTextRun->GetChar(i) == '\n'; + preformattedTab = preformatTabs && mTextRun->GetChar(i) == '\t'; + if (!mTextRun->CanBreakLineBefore(i) && !preformattedNewline && + !preformattedTab) { + // we can't break here (and it's not the end of the flow) + continue; + } + } + + if (i > wordStart) { + nscoord width = + NSToCoordCeilClamped(mTextRun->GetAdvanceWidth(wordStart, i - wordStart, &provider)); + aData->currentLine = NSCoordSaturatingAdd(aData->currentLine, width); + aData->atStartOfLine = PR_FALSE; + + if (collapseWhitespace) { + PRUint32 trimStart = GetEndOfTrimmedText(frag, textStyle, wordStart, i, &iter); + if (trimStart == start) { + // This is *all* trimmable whitespace, so whatever trailingWhitespace + // we saw previously is still trailing... + aData->trailingWhitespace += width; + } else { + // Some non-whitespace so the old trailingWhitespace is no longer trailing + aData->trailingWhitespace = + NSToCoordCeilClamped(mTextRun->GetAdvanceWidth(trimStart, i - trimStart, &provider)); + } + } else { + aData->trailingWhitespace = 0; + } + } + + if (preformattedTab) { + PropertyProvider::Spacing spacing; + provider.GetSpacing(i, 1, &spacing); + aData->currentLine += nscoord(spacing.mBefore); + gfxFloat afterTab = + AdvanceToNextTab(aData->currentLine, FindLineContainer(this), + mTextRun, &tabWidth); + aData->currentLine = nscoord(afterTab + spacing.mAfter); + wordStart = i + 1; + } else if (i < flowEndInTextRun || + (i == mTextRun->GetLength() && + (mTextRun->GetFlags() & nsTextFrameUtils::TEXT_HAS_TRAILING_BREAK))) { + if (preformattedNewline) { + aData->ForceBreak(aRenderingContext); + } else { + aData->OptionallyBreak(aRenderingContext); + } + wordStart = i; + } + } + + if (start < flowEndInTextRun) { + // Check if we have collapsible whitespace at the end + aData->skipWhitespace = + IsTrimmableSpace(provider.GetFragment(), + iter.ConvertSkippedToOriginal(flowEndInTextRun - 1), + textStyle); + } +} + +// XXX Need to do something here to avoid incremental reflow bugs due to +// first-line and first-letter changing min-width +/* virtual */ void +nsTextFrame::AddInlineMinWidth(nsIRenderingContext *aRenderingContext, + nsIFrame::InlineMinWidthData *aData) +{ + nsTextFrame* f; + gfxTextRun* lastTextRun = nsnull; + // nsContinuingTextFrame does nothing for AddInlineMinWidth; all text frames + // in the flow are handled right here. + for (f = this; f; f = static_cast(f->GetNextContinuation())) { + // f->mTextRun could be null if we haven't set up textruns yet for f. + // Except in OOM situations, lastTextRun will only be null for the first + // text frame. + if (f == this || f->mTextRun != lastTextRun) { + nsIFrame* lc; + if (aData->lineContainer && + aData->lineContainer != (lc = FindLineContainer(f))) { + NS_ASSERTION(f != this, "wrong InlineMinWidthData container" + " for first continuation"); + aData->line = nsnull; + aData->lineContainer = lc; + } + + // This will process all the text frames that share the same textrun as f. + f->AddInlineMinWidthForFlow(aRenderingContext, aData); + lastTextRun = f->mTextRun; + } + } +} + +// XXX this doesn't handle characters shaped by line endings. We need to +// temporarily override the "current line ending" settings. +void +nsTextFrame::AddInlinePrefWidthForFlow(nsIRenderingContext *aRenderingContext, + nsIFrame::InlinePrefWidthData *aData) +{ + PRUint32 flowEndInTextRun; + gfxContext* ctx = aRenderingContext->ThebesContext(); + gfxSkipCharsIterator iter = + EnsureTextRun(ctx, aData->lineContainer, aData->line, &flowEndInTextRun); + if (!mTextRun) + return; + + // Pass null for the line container. This will disable tab spacing, but that's + // OK since we can't really handle tabs for intrinsic sizing anyway. + + const nsStyleText* textStyle = GetStyleText(); + const nsTextFragment* frag = GetFragment(); + PropertyProvider provider(mTextRun, textStyle, frag, this, + iter, PR_INT32_MAX, nsnull, 0); + + PRBool collapseWhitespace = !textStyle->WhiteSpaceIsSignificant(); + PRBool preformatNewlines = textStyle->NewlineIsSignificant(); + PRBool preformatTabs = textStyle->WhiteSpaceIsSignificant(); + gfxFloat tabWidth = -1; + PRUint32 start = + FindStartAfterSkippingWhitespace(&provider, aData, textStyle, &iter, flowEndInTextRun); + + // XXX Should we consider hyphenation here? + // If newlines and tabs aren't preformatted, nothing to do inside + // the loop so make i skip to the end + PRUint32 loopStart = (preformatNewlines || preformatTabs) ? start : flowEndInTextRun; + for (PRUint32 i = loopStart, lineStart = start; i <= flowEndInTextRun; ++i) { + PRBool preformattedNewline = PR_FALSE; + PRBool preformattedTab = PR_FALSE; + if (i < flowEndInTextRun) { + // XXXldb Shouldn't we be including the newline as part of the + // segment that it ends rather than part of the segment that it + // starts? + NS_ASSERTION(preformatNewlines, "We can't be here unless newlines are hard breaks"); + preformattedNewline = preformatNewlines && mTextRun->GetChar(i) == '\n'; + preformattedTab = preformatTabs && mTextRun->GetChar(i) == '\t'; + if (!preformattedNewline && !preformattedTab) { + // we needn't break here (and it's not the end of the flow) + continue; + } + } + + if (i > lineStart) { + nscoord width = + NSToCoordCeilClamped(mTextRun->GetAdvanceWidth(lineStart, i - lineStart, &provider)); + aData->currentLine = NSCoordSaturatingAdd(aData->currentLine, width); + + if (collapseWhitespace) { + PRUint32 trimStart = GetEndOfTrimmedText(frag, textStyle, lineStart, i, &iter); + if (trimStart == start) { + // This is *all* trimmable whitespace, so whatever trailingWhitespace + // we saw previously is still trailing... + aData->trailingWhitespace += width; + } else { + // Some non-whitespace so the old trailingWhitespace is no longer trailing + aData->trailingWhitespace = + NSToCoordCeilClamped(mTextRun->GetAdvanceWidth(trimStart, i - trimStart, &provider)); + } + } else { + aData->trailingWhitespace = 0; + } + } + + if (preformattedTab) { + PropertyProvider::Spacing spacing; + provider.GetSpacing(i, 1, &spacing); + aData->currentLine += nscoord(spacing.mBefore); + gfxFloat afterTab = + AdvanceToNextTab(aData->currentLine, FindLineContainer(this), + mTextRun, &tabWidth); + aData->currentLine = nscoord(afterTab + spacing.mAfter); + lineStart = i + 1; + } else if (preformattedNewline) { + aData->ForceBreak(aRenderingContext); + lineStart = i; + } + } + + // Check if we have collapsible whitespace at the end + if (start < flowEndInTextRun) { + aData->skipWhitespace = + IsTrimmableSpace(provider.GetFragment(), + iter.ConvertSkippedToOriginal(flowEndInTextRun - 1), + textStyle); + } +} + +// XXX Need to do something here to avoid incremental reflow bugs due to +// first-line and first-letter changing pref-width +/* virtual */ void +nsTextFrame::AddInlinePrefWidth(nsIRenderingContext *aRenderingContext, + nsIFrame::InlinePrefWidthData *aData) +{ + nsTextFrame* f; + gfxTextRun* lastTextRun = nsnull; + // nsContinuingTextFrame does nothing for AddInlineMinWidth; all text frames + // in the flow are handled right here. + for (f = this; f; f = static_cast(f->GetNextContinuation())) { + // f->mTextRun could be null if we haven't set up textruns yet for f. + // Except in OOM situations, lastTextRun will only be null for the first + // text frame. + if (f == this || f->mTextRun != lastTextRun) { + nsIFrame* lc; + if (aData->lineContainer && + aData->lineContainer != (lc = FindLineContainer(f))) { + NS_ASSERTION(f != this, "wrong InlinePrefWidthData container" + " for first continuation"); + aData->line = nsnull; + aData->lineContainer = lc; + } + + // This will process all the text frames that share the same textrun as f. + f->AddInlinePrefWidthForFlow(aRenderingContext, aData); + lastTextRun = f->mTextRun; + } + } +} + +/* virtual */ nsSize +nsTextFrame::ComputeSize(nsIRenderingContext *aRenderingContext, + nsSize aCBSize, nscoord aAvailableWidth, + nsSize aMargin, nsSize aBorder, nsSize aPadding, + PRBool aShrinkWrap) +{ + // Inlines and text don't compute size before reflow. + return nsSize(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE); +} + +static nsRect +RoundOut(const gfxRect& aRect) +{ + nsRect r; + r.x = NSToCoordFloor(aRect.X()); + r.y = NSToCoordFloor(aRect.Y()); + r.width = NSToCoordCeil(aRect.XMost()) - r.x; + r.height = NSToCoordCeil(aRect.YMost()) - r.y; + return r; +} + +nsRect +nsTextFrame::ComputeTightBounds(gfxContext* aContext) const +{ + if ((GetStyleContext()->HasTextDecorations() && + eCompatibility_NavQuirks == PresContext()->CompatibilityMode()) || + (GetStateBits() & TEXT_HYPHEN_BREAK)) { + // This is conservative, but OK. + return GetOverflowRect(); + } + + gfxSkipCharsIterator iter = const_cast(this)->EnsureTextRun(); + if (!mTextRun) + return nsRect(0, 0, 0, 0); + + PropertyProvider provider(const_cast(this), iter); + // Trim trailing whitespace + provider.InitializeForDisplay(PR_TRUE); + + gfxTextRun::Metrics metrics = + mTextRun->MeasureText(provider.GetStart().GetSkippedOffset(), + ComputeTransformedLength(provider), + gfxFont::TIGHT_HINTED_OUTLINE_EXTENTS, + aContext, &provider); + // mAscent should be the same as metrics.mAscent, but it's what we use to + // paint so that's the one we'll use. + return RoundOut(metrics.mBoundingBox) + nsPoint(0, mAscent); +} + +static PRBool +HasSoftHyphenBefore(const nsTextFragment* aFrag, gfxTextRun* aTextRun, + PRInt32 aStartOffset, const gfxSkipCharsIterator& aIter) +{ + if (!(aTextRun->GetFlags() & nsTextFrameUtils::TEXT_HAS_SHY)) + return PR_FALSE; + gfxSkipCharsIterator iter = aIter; + while (iter.GetOriginalOffset() > aStartOffset) { + iter.AdvanceOriginal(-1); + if (!iter.IsOriginalCharSkipped()) + break; + if (aFrag->CharAt(iter.GetOriginalOffset()) == CH_SHY) + return PR_TRUE; + } + return PR_FALSE; +} + +void +nsTextFrame::SetLength(PRInt32 aLength) +{ + mContentLengthHint = aLength; + PRInt32 end = GetContentOffset() + aLength; + nsTextFrame* f = static_cast(GetNextInFlow()); + if (!f) + return; + if (end < f->mContentOffset) { + // Our frame is shrinking. Give the text to our next in flow. + f->mContentOffset = end; + if (f->GetTextRun() != mTextRun) { + ClearTextRun(); + f->ClearTextRun(); + } + return; + } + while (f && f->mContentOffset < end) { + // Our frame is growing. Take text from our in-flow. + f->mContentOffset = end; + if (f->GetTextRun() != mTextRun) { + ClearTextRun(); + f->ClearTextRun(); + } + f = static_cast(f->GetNextInFlow()); + } +#ifdef DEBUG + f = this; + PRInt32 iterations = 0; + while (f && iterations < 10) { + f->GetContentLength(); // Assert if negative length + f = static_cast(f->GetNextContinuation()); + ++iterations; + } + f = this; + iterations = 0; + while (f && iterations < 10) { + f->GetContentLength(); // Assert if negative length + f = static_cast(f->GetPrevContinuation()); + ++iterations; + } +#endif +} + +PRBool +nsTextFrame::IsFloatingFirstLetterChild() +{ + if (!GetStateBits() & TEXT_FIRST_LETTER) + return PR_FALSE; + nsIFrame* frame = GetParent(); + if (!frame || frame->GetType() != nsGkAtoms::letterFrame) + return PR_FALSE; + return frame->GetStyleDisplay()->IsFloating(); +} + +NS_IMETHODIMP +nsTextFrame::Reflow(nsPresContext* aPresContext, + nsHTMLReflowMetrics& aMetrics, + const nsHTMLReflowState& aReflowState, + nsReflowStatus& aStatus) +{ + DO_GLOBAL_REFLOW_COUNT("nsTextFrame"); + DISPLAY_REFLOW(aPresContext, this, aReflowState, aMetrics, aStatus); +#ifdef NOISY_REFLOW + ListTag(stdout); + printf(": BeginReflow: availableSize=%d,%d\n", + aReflowState.availableWidth, aReflowState.availableHeight); +#endif + + ///////////////////////////////////////////////////////////////////// + // Set up flags and clear out state + ///////////////////////////////////////////////////////////////////// + + // Clear out the reflow state flags in mState (without destroying + // the TEXT_BLINK_ON_OR_PRINTING bit). We also clear the whitespace flags + // because this can change whether the frame maps whitespace-only text or not. + RemoveStateBits(TEXT_REFLOW_FLAGS | TEXT_WHITESPACE_FLAGS); + + // Temporarily map all possible content while we construct our new textrun. + // so that when doing reflow our styles prevail over any part of the + // textrun we look at. Note that next-in-flows may be mapping the same + // content; gfxTextRun construction logic will ensure that we take priority. + PRInt32 maxContentLength = GetInFlowContentLength(); + + // XXX If there's no line layout, we shouldn't even have created this + // frame. This may happen if, for example, this is text inside a table + // but not inside a cell. For now, just don't reflow. We also don't need to + // reflow if there is no content. + if (!aReflowState.mLineLayout || !maxContentLength) { + ClearMetrics(aMetrics); + aStatus = NS_FRAME_COMPLETE; + return NS_OK; + } + + nsLineLayout& lineLayout = *aReflowState.mLineLayout; + + if (aReflowState.mFlags.mBlinks) { + if (0 == (mState & TEXT_BLINK_ON_OR_PRINTING) && PresContext()->IsDynamic()) { + mState |= TEXT_BLINK_ON_OR_PRINTING; + nsBlinkTimer::AddBlinkFrame(aPresContext, this); + } + } + else { + if (0 != (mState & TEXT_BLINK_ON_OR_PRINTING) && PresContext()->IsDynamic()) { + mState &= ~TEXT_BLINK_ON_OR_PRINTING; + nsBlinkTimer::RemoveBlinkFrame(this); + } + } + + const nsStyleText* textStyle = GetStyleText(); + + PRBool atStartOfLine = lineLayout.LineIsEmpty(); + if (atStartOfLine) { + AddStateBits(TEXT_START_OF_LINE); + } + + PRUint32 flowEndInTextRun; + nsIFrame* lineContainer = lineLayout.GetLineContainerFrame(); + gfxContext* ctx = aReflowState.rendContext->ThebesContext(); + const nsTextFragment* frag = GetFragment(); + + // DOM offsets of the text range we need to measure, after trimming + // whitespace, restricting to first-letter, and restricting preformatted text + // to nearest newline + PRInt32 length = maxContentLength; + PRInt32 offset = GetContentOffset(); + + // Restrict preformatted text to the nearest newline + PRInt32 newLineOffset = -1; // this will be -1 or a content offset + if (textStyle->NewlineIsSignificant()) { + newLineOffset = FindChar(frag, offset, length, '\n'); + if (newLineOffset >= 0) { + length = newLineOffset + 1 - offset; + } + } + if (atStartOfLine && !textStyle->WhiteSpaceIsSignificant()) { + // Skip leading whitespace. Make sure we don't skip a 'pre-line' + // newline if there is one. + PRInt32 skipLength = newLineOffset >= 0 ? length - 1 : length; + PRInt32 whitespaceCount = + GetTrimmableWhitespaceCount(frag, offset, skipLength, 1); + offset += whitespaceCount; + length -= whitespaceCount; + } + + PRBool completedFirstLetter = PR_FALSE; + // Layout dependent styles are a problem because we need to reconstruct + // the gfxTextRun based on our layout. + if (lineLayout.GetInFirstLetter() || lineLayout.GetInFirstLine()) { + SetLength(maxContentLength); + + if (lineLayout.GetInFirstLetter()) { + // floating first-letter boundaries are significant in textrun + // construction, so clear the textrun out every time we hit a first-letter + // and have changed our length (which controls the first-letter boundary) + ClearTextRun(); + // Find the length of the first-letter. We need a textrun for this. + gfxSkipCharsIterator iter = + EnsureTextRun(ctx, lineContainer, lineLayout.GetLine(), &flowEndInTextRun); + + if (mTextRun) { + PRInt32 firstLetterLength = length; + if (lineLayout.GetFirstLetterStyleOK()) { + completedFirstLetter = + FindFirstLetterRange(frag, mTextRun, offset, iter, &firstLetterLength); + if (newLineOffset >= 0) { + // Don't allow a preformatted newline to be part of a first-letter. + firstLetterLength = PR_MIN(firstLetterLength, length - 1); + if (length == 1) { + // There is no text to be consumed by the first-letter before the + // preformatted newline. Note that the first letter is therefore + // complete (FindFirstLetterRange will have returned false). + completedFirstLetter = PR_TRUE; + } + } + } else { + // We're in a first-letter frame's first in flow, so if there + // was a first-letter, we'd be it. However, for one reason + // or another (e.g., preformatted line break before this text), + // we're not actually supposed to have first-letter style. So + // just make a zero-length first-letter. + firstLetterLength = 0; + completedFirstLetter = PR_TRUE; + } + length = firstLetterLength; + if (length) { + AddStateBits(TEXT_FIRST_LETTER); + } + // Change this frame's length to the first-letter length right now + // so that when we rebuild the textrun it will be built with the + // right first-letter boundary + SetLength(offset + length - GetContentOffset()); + // Ensure that the textrun will be rebuilt + ClearTextRun(); + } + } + } + + gfxSkipCharsIterator iter = + EnsureTextRun(ctx, lineContainer, lineLayout.GetLine(), &flowEndInTextRun); + + if (mTextRun && iter.GetOriginalEnd() < offset + length) { + // The textrun does not map enough text for this frame. This can happen + // when the textrun was ended in the middle of a text node because a + // preformatted newline was encountered, and prev-in-flow frames have + // consumed all the text of the textrun. We need a new textrun. + ClearTextRun(); + iter = EnsureTextRun(ctx, lineContainer, + lineLayout.GetLine(), &flowEndInTextRun); + } + + if (!mTextRun) { + ClearMetrics(aMetrics); + aStatus = NS_FRAME_COMPLETE; + return NS_OK; + } + + NS_ASSERTION(gfxSkipCharsIterator(iter).ConvertOriginalToSkipped(offset + length) + <= mTextRun->GetLength(), + "Text run does not map enough text for our reflow"); + + ///////////////////////////////////////////////////////////////////// + // See how much text should belong to this text frame, and measure it + ///////////////////////////////////////////////////////////////////// + + iter.SetOriginalOffset(offset); + nscoord xOffsetForTabs = (mTextRun->GetFlags() & nsTextFrameUtils::TEXT_HAS_TAB) ? + (lineLayout.GetCurrentFrameXDistanceFromBlock() - + lineContainer->GetUsedBorderAndPadding().left) + : -1; + PropertyProvider provider(mTextRun, textStyle, frag, this, iter, length, + lineContainer, xOffsetForTabs); + + PRUint32 transformedOffset = provider.GetStart().GetSkippedOffset(); + + // The metrics for the text go in here + gfxTextRun::Metrics textMetrics; + gfxFont::BoundingBoxType boundingBoxType = IsFloatingFirstLetterChild() ? + gfxFont::TIGHT_HINTED_OUTLINE_EXTENTS : + gfxFont::LOOSE_INK_EXTENTS; +#ifdef MOZ_MATHML + NS_ASSERTION(!(NS_REFLOW_CALC_BOUNDING_METRICS & aMetrics.mFlags), + "We shouldn't be passed NS_REFLOW_CALC_BOUNDING_METRICS anymore"); +#endif + + PRInt32 limitLength = length; + PRInt32 forceBreak = lineLayout.GetForcedBreakPosition(mContent); + PRBool forceBreakAfter = PR_FALSE; + if (forceBreak >= offset + length) { + forceBreakAfter = forceBreak == offset + length; + // The break is not within the text considered for this textframe. + forceBreak = -1; + } + if (forceBreak >= 0) { + limitLength = forceBreak - offset; + NS_ASSERTION(limitLength >= 0, "Weird break found!"); + } + // This is the heart of text reflow right here! We don't know where + // to break, so we need to see how much text fits in the available width. + PRUint32 transformedLength; + if (offset + limitLength >= PRInt32(frag->GetLength())) { + NS_ASSERTION(offset + limitLength == PRInt32(frag->GetLength()), + "Content offset/length out of bounds"); + NS_ASSERTION(flowEndInTextRun >= transformedOffset, + "Negative flow length?"); + transformedLength = flowEndInTextRun - transformedOffset; + } else { + // we're not looking at all the content, so we need to compute the + // length of the transformed substring we're looking at + gfxSkipCharsIterator iter(provider.GetStart()); + iter.SetOriginalOffset(offset + limitLength); + transformedLength = iter.GetSkippedOffset() - transformedOffset; + } + PRUint32 transformedLastBreak = 0; + PRBool usedHyphenation; + gfxFloat trimmedWidth = 0; + gfxFloat availWidth = aReflowState.availableWidth; + PRBool canTrimTrailingWhitespace = !textStyle->WhiteSpaceIsSignificant(); + PRInt32 unusedOffset; + gfxBreakPriority breakPriority; + lineLayout.GetLastOptionalBreakPosition(&unusedOffset, &breakPriority); + PRUint32 transformedCharsFit = + mTextRun->BreakAndMeasureText(transformedOffset, transformedLength, + (GetStateBits() & TEXT_START_OF_LINE) != 0, + availWidth, + &provider, !lineLayout.LineIsBreakable(), + canTrimTrailingWhitespace ? &trimmedWidth : nsnull, + &textMetrics, boundingBoxType, ctx, + &usedHyphenation, &transformedLastBreak, + textStyle->WordCanWrap(), &breakPriority); + // The "end" iterator points to the first character after the string mapped + // by this frame. Basically, its original-string offset is offset+charsFit + // after we've computed charsFit. + gfxSkipCharsIterator end(provider.GetEndHint()); + end.SetSkippedOffset(transformedOffset + transformedCharsFit); + PRInt32 charsFit = end.GetOriginalOffset() - offset; + if (offset + charsFit == newLineOffset) { + // We broke before a trailing preformatted '\n'. The newline should + // be assigned to this frame. Note that newLineOffset will be -1 if + // there was no preformatted newline, so we wouldn't get here in that + // case. + ++charsFit; + } + // That might have taken us beyond our assigned content range (because + // we might have advanced over some skipped chars that extend outside + // this frame), so get back in. + PRInt32 lastBreak = -1; + if (charsFit >= limitLength) { + charsFit = limitLength; + if (transformedLastBreak != PR_UINT32_MAX) { + // lastBreak is needed. + // This may set lastBreak greater than 'length', but that's OK + lastBreak = end.ConvertSkippedToOriginal(transformedOffset + transformedLastBreak); + } + end.SetOriginalOffset(offset + charsFit); + // If we were forced to fit, and the break position is after a soft hyphen, + // note that this is a hyphenation break. + if ((forceBreak >= 0 || forceBreakAfter) && + HasSoftHyphenBefore(frag, mTextRun, offset, end)) { + usedHyphenation = PR_TRUE; + } + } + if (usedHyphenation) { + // Fix up metrics to include hyphen + AddHyphenToMetrics(this, mTextRun, &textMetrics, boundingBoxType, ctx); + AddStateBits(TEXT_HYPHEN_BREAK | TEXT_HAS_NONCOLLAPSED_CHARACTERS); + } + + gfxFloat trimmableWidth = 0; + PRBool brokeText = forceBreak >= 0 || transformedCharsFit < transformedLength; + if (canTrimTrailingWhitespace) { + // Optimization: if we trimmed trailing whitespace, and we can be sure + // this frame will be at the end of the line, then leave it trimmed off. + // Otherwise we have to undo the trimming, in case we're not at the end of + // the line. (If we actually do end up at the end of the line, we'll have + // to trim it off again in TrimTrailingWhiteSpace, and we'd like to avoid + // having to re-do it.) + if (brokeText) { + // We're definitely going to break so our trailing whitespace should + // definitely be timmed. Record that we've already done it. + AddStateBits(TEXT_TRIMMED_TRAILING_WHITESPACE); + } else { + // We might not be at the end of the line. (Note that even if this frame + // ends in breakable whitespace, it might not be at the end of the line + // because it might be followed by breakable, but preformatted, whitespace.) + // Undo the trimming. + textMetrics.mAdvanceWidth += trimmedWidth; + trimmableWidth = trimmedWidth; + if (mTextRun->IsRightToLeft()) { + // Space comes before text, so the bounding box is moved to the + // right by trimmdWidth + textMetrics.mBoundingBox.MoveBy(gfxPoint(trimmedWidth, 0)); + } + } + } + + if (!brokeText && lastBreak >= 0) { + // Since everything fit and no break was forced, + // record the last break opportunity + NS_ASSERTION(textMetrics.mAdvanceWidth - trimmableWidth <= aReflowState.availableWidth, + "If the text doesn't fit, and we have a break opportunity, why didn't MeasureText use it?"); + lineLayout.NotifyOptionalBreakPosition(mContent, lastBreak, PR_TRUE, breakPriority); + } + + PRInt32 contentLength = offset + charsFit - GetContentOffset(); + + ///////////////////////////////////////////////////////////////////// + // Compute output metrics + ///////////////////////////////////////////////////////////////////// + + // first-letter frames should use the tight bounding box metrics for ascent/descent + // for good drop-cap effects + if (GetStateBits() & TEXT_FIRST_LETTER) { + textMetrics.mAscent = PR_MAX(0, -textMetrics.mBoundingBox.Y()); + textMetrics.mDescent = PR_MAX(0, textMetrics.mBoundingBox.YMost()); + } + + // Setup metrics for caller + // Disallow negative widths + aMetrics.width = NSToCoordCeil(PR_MAX(0, textMetrics.mAdvanceWidth)); + + if (transformedCharsFit == 0 && !usedHyphenation) { + aMetrics.ascent = 0; + aMetrics.height = 0; + } else if (boundingBoxType != gfxFont::LOOSE_INK_EXTENTS) { + // Use actual text metrics for floating first letter frame. + aMetrics.ascent = NSToCoordCeil(textMetrics.mAscent); + aMetrics.height = aMetrics.ascent + NSToCoordCeil(textMetrics.mDescent); + } else { + // Otherwise, ascent should contain the overline drawable area. + // And also descent should contain the underline drawable area. + // nsIFontMetrics::GetMaxAscent/GetMaxDescent contains them. + nscoord fontAscent, fontDescent; + nsIFontMetrics* fm = provider.GetFontMetrics(); + fm->GetMaxAscent(fontAscent); + fm->GetMaxDescent(fontDescent); + aMetrics.ascent = PR_MAX(NSToCoordCeil(textMetrics.mAscent), fontAscent); + nscoord descent = PR_MAX(NSToCoordCeil(textMetrics.mDescent), fontDescent); + aMetrics.height = aMetrics.ascent + descent; + } + + NS_ASSERTION(aMetrics.ascent >= 0, "Negative ascent???"); + NS_ASSERTION(aMetrics.height - aMetrics.ascent >= 0, "Negative descent???"); + + mAscent = aMetrics.ascent; + + // Handle text that runs outside its normal bounds. + nsRect boundingBox = RoundOut(textMetrics.mBoundingBox) + nsPoint(0, mAscent); + aMetrics.mOverflowArea.UnionRect(boundingBox, + nsRect(0, 0, aMetrics.width, aMetrics.height)); + + UnionTextDecorationOverflow(aPresContext, provider, &aMetrics.mOverflowArea); + + ///////////////////////////////////////////////////////////////////// + // Clean up, update state + ///////////////////////////////////////////////////////////////////// + + // If all our characters are discarded or collapsed, then trimmable width + // from the last textframe should be preserved. Otherwise the trimmable width + // from this textframe overrides. (Currently in CSS trimmable width can be + // at most one space so there's no way for trimmable width from a previous + // frame to accumulate with trimmable width from this frame.) + if (transformedCharsFit > 0) { + lineLayout.SetTrimmableWidth(NSToCoordFloor(trimmableWidth)); + AddStateBits(TEXT_HAS_NONCOLLAPSED_CHARACTERS); + } + if (charsFit > 0 && charsFit == length && + HasSoftHyphenBefore(frag, mTextRun, offset, end)) { + // Record a potential break after final soft hyphen + lineLayout.NotifyOptionalBreakPosition(mContent, offset + length, + textMetrics.mAdvanceWidth + provider.GetHyphenWidth() <= availWidth, + eNormalBreak); + } + PRBool breakAfter = forceBreakAfter; + // length == 0 means either the text is empty or it's all collapsed away + PRBool emptyTextAtStartOfLine = atStartOfLine && length == 0; + if (!breakAfter && charsFit == length && !emptyTextAtStartOfLine && + transformedOffset + transformedLength == mTextRun->GetLength() && + (mTextRun->GetFlags() & nsTextFrameUtils::TEXT_HAS_TRAILING_BREAK)) { + // We placed all the text in the textrun and we have a break opportunity at + // the end of the textrun. We need to record it because the following + // content may not care about nsLineBreaker. + + // Note that because we didn't break, we can be sure that (thanks to the + // code up above) textMetrics.mAdvanceWidth includes the width of any + // trailing whitespace. So we need to subtract trimmableWidth here + // because if we did break at this point, that much width would be trimmed. + if (textMetrics.mAdvanceWidth - trimmableWidth > availWidth) { + breakAfter = PR_TRUE; + } else { + lineLayout.NotifyOptionalBreakPosition(mContent, offset + length, PR_TRUE, + eNormalBreak); + } + } + + // Compute reflow status + aStatus = contentLength == maxContentLength + ? NS_FRAME_COMPLETE : NS_FRAME_NOT_COMPLETE; + + if (charsFit == 0 && length > 0) { + // Couldn't place any text + aStatus = NS_INLINE_LINE_BREAK_BEFORE(); + } else if (contentLength > 0 && mContentOffset + contentLength - 1 == newLineOffset) { + // Ends in \n + aStatus = NS_INLINE_LINE_BREAK_AFTER(aStatus); + lineLayout.SetLineEndsInBR(PR_TRUE); + } else if (breakAfter) { + aStatus = NS_INLINE_LINE_BREAK_AFTER(aStatus); + } + if (completedFirstLetter) { + lineLayout.SetFirstLetterStyleOK(PR_FALSE); + aStatus |= NS_INLINE_BREAK_FIRST_LETTER_COMPLETE; + } + + // Compute space and letter counts for justification, if required + if (!textStyle->WhiteSpaceIsSignificant() && + lineContainer->GetStyleText()->mTextAlign == NS_STYLE_TEXT_ALIGN_JUSTIFY) { + AddStateBits(TEXT_JUSTIFICATION_ENABLED); // This will include a space for trailing whitespace, if any is present. + // This is corrected for in nsLineLayout::TrimWhiteSpaceIn. + PRInt32 numJustifiableCharacters = + provider.ComputeJustifiableCharacters(offset, charsFit); + + NS_ASSERTION(numJustifiableCharacters <= charsFit, + "Bad justifiable character count"); + lineLayout.SetTextJustificationWeights(numJustifiableCharacters, + charsFit - numJustifiableCharacters); + } + + SetLength(contentLength); + + if (mContent->HasFlag(NS_TEXT_IN_SELECTION)) { + // XXXroc Watch out, this could be slow!!! Speed up GetSelectionDetails? + SelectionDetails* details = GetSelectionDetails(); + if (details) { + AddStateBits(NS_FRAME_SELECTED_CONTENT); + DestroySelectionDetails(details); + } else { + RemoveStateBits(NS_FRAME_SELECTED_CONTENT); + } + } + + Invalidate(aMetrics.mOverflowArea); + +#ifdef NOISY_REFLOW + ListTag(stdout); + printf(": desiredSize=%d,%d(b=%d) status=%x\n", + aMetrics.width, aMetrics.height, aMetrics.ascent, + aStatus); +#endif + NS_FRAME_SET_TRUNCATION(aStatus, aReflowState, aMetrics); + return NS_OK; +} + +/* virtual */ PRBool +nsTextFrame::CanContinueTextRun() const +{ + // We can continue a text run through a text frame + return PR_TRUE; +} + +nsTextFrame::TrimOutput +nsTextFrame::TrimTrailingWhiteSpace(nsIRenderingContext* aRC) +{ + TrimOutput result; + result.mChanged = PR_FALSE; + result.mLastCharIsJustifiable = PR_FALSE; + result.mDeltaWidth = 0; + + AddStateBits(TEXT_END_OF_LINE); + + PRInt32 contentLength = GetContentLength(); + if (!contentLength) + return result; + + gfxContext* ctx = aRC->ThebesContext(); + gfxSkipCharsIterator start = EnsureTextRun(ctx); + NS_ENSURE_TRUE(mTextRun, result); + + PRUint32 trimmedStart = start.GetSkippedOffset(); + + const nsTextFragment* frag = GetFragment(); + TrimmedOffsets trimmed = GetTrimmedOffsets(frag, PR_TRUE); + gfxSkipCharsIterator trimmedEndIter = start; + const nsStyleText* textStyle = GetStyleText(); + gfxFloat delta = 0; + PRUint32 trimmedEnd = trimmedEndIter.ConvertOriginalToSkipped(trimmed.GetEnd()); + + if (GetStateBits() & TEXT_TRIMMED_TRAILING_WHITESPACE) { + // We pre-trimmed this frame, so the last character is justifiable + result.mLastCharIsJustifiable = PR_TRUE; + } else if (trimmed.GetEnd() < GetContentEnd()) { + gfxSkipCharsIterator end = trimmedEndIter; + PRUint32 endOffset = end.ConvertOriginalToSkipped(GetContentOffset() + contentLength); + if (trimmedEnd < endOffset) { + // We can't be dealing with tabs here ... they wouldn't be trimmed. So it's + // OK to pass null for the line container. + PropertyProvider provider(mTextRun, textStyle, frag, this, start, contentLength, + nsnull, 0); + delta = mTextRun->GetAdvanceWidth(trimmedEnd, endOffset - trimmedEnd, &provider); + // non-compressed whitespace being skipped at end of line -> justifiable + // XXX should we actually *count* justifiable characters that should be + // removed from the overall count? I think so... + result.mLastCharIsJustifiable = PR_TRUE; + result.mChanged = PR_TRUE; + } + } + + if (!result.mLastCharIsJustifiable && + (GetStateBits() & TEXT_JUSTIFICATION_ENABLED)) { + // Check if any character in the last cluster is justifiable + PropertyProvider provider(mTextRun, textStyle, frag, this, start, contentLength, + nsnull, 0); + PRBool isCJK = IsChineseJapaneseLangGroup(this); + gfxSkipCharsIterator justificationStart(start), justificationEnd(trimmedEndIter); + provider.FindJustificationRange(&justificationStart, &justificationEnd); + + PRInt32 i; + for (i = justificationEnd.GetOriginalOffset(); i < trimmed.GetEnd(); ++i) { + if (IsJustifiableCharacter(frag, i, isCJK)) { + result.mLastCharIsJustifiable = PR_TRUE; + } + } + } + + gfxFloat advanceDelta; + mTextRun->SetLineBreaks(trimmedStart, trimmedEnd - trimmedStart, + (GetStateBits() & TEXT_START_OF_LINE) != 0, PR_TRUE, + &advanceDelta, ctx); + if (advanceDelta != 0) { + result.mChanged = PR_TRUE; + } + + // aDeltaWidth is *subtracted* from our width. + // If advanceDelta is positive then setting the line break made us longer, + // so aDeltaWidth could go negative. + result.mDeltaWidth = NSToCoordFloor(delta - advanceDelta); + // If aDeltaWidth goes negative, that means this frame might not actually fit + // anymore!!! We need higher level line layout to recover somehow. + // If it's because the frame has a soft hyphen that is now being displayed, + // this should actually be OK, because our reflow recorded the break + // opportunity that allowed the soft hyphen to be used, and we wouldn't + // have recorded the opportunity unless the hyphen fit (or was the first + // opportunity on the line). + // Otherwise this can/ really only happen when we have glyphs with special + // shapes at the end of lines, I think. Breaking inside a kerning pair won't + // do it because that would mean we broke inside this textrun, and + // BreakAndMeasureText should make sure the resulting shaped substring fits. + // Maybe if we passed a maxTextLength? But that only happens at direction + // changes (so we wouldn't kern across the boundary) or for first-letter + // (which always fits because it starts the line!). + NS_WARN_IF_FALSE(result.mDeltaWidth >= 0, + "Negative deltawidth, something odd is happening"); + +#ifdef NOISY_TRIM + ListTag(stdout); + printf(": trim => %d\n", result.mDeltaWidth); +#endif + return result; +} + +nsRect +nsTextFrame::RecomputeOverflowRect() +{ + gfxSkipCharsIterator iter = EnsureTextRun(); + if (!mTextRun) + return nsRect(nsPoint(0,0), GetSize()); + + PropertyProvider provider(this, iter); + provider.InitializeForDisplay(PR_TRUE); + + gfxTextRun::Metrics textMetrics = + mTextRun->MeasureText(provider.GetStart().GetSkippedOffset(), + ComputeTransformedLength(provider), + gfxFont::LOOSE_INK_EXTENTS, nsnull, + &provider); + + nsRect boundingBox = RoundOut(textMetrics.mBoundingBox) + nsPoint(0, mAscent); + boundingBox.UnionRect(boundingBox, + nsRect(nsPoint(0,0), GetSize())); + + UnionTextDecorationOverflow(PresContext(), provider, &boundingBox); + + return boundingBox; +} + +static PRUnichar TransformChar(const nsStyleText* aStyle, gfxTextRun* aTextRun, + PRUint32 aSkippedOffset, PRUnichar aChar) +{ + if (aChar == '\n') { + return aStyle->NewlineIsSignificant() ? aChar : ' '; + } + switch (aStyle->mTextTransform) { + case NS_STYLE_TEXT_TRANSFORM_LOWERCASE: + nsContentUtils::GetCaseConv()->ToLower(aChar, &aChar); + break; + case NS_STYLE_TEXT_TRANSFORM_UPPERCASE: + nsContentUtils::GetCaseConv()->ToUpper(aChar, &aChar); + break; + case NS_STYLE_TEXT_TRANSFORM_CAPITALIZE: + if (aTextRun->CanBreakLineBefore(aSkippedOffset)) { + nsContentUtils::GetCaseConv()->ToTitle(aChar, &aChar); + } + break; + } + + return aChar; +} + +nsresult nsTextFrame::GetRenderedText(nsAString* aAppendToString, + gfxSkipChars* aSkipChars, + gfxSkipCharsIterator* aSkipIter, + PRUint32 aSkippedStartOffset, + PRUint32 aSkippedMaxLength) +{ + // The handling of aSkippedStartOffset and aSkippedMaxLength could be more efficient... + gfxSkipCharsBuilder skipCharsBuilder; + nsTextFrame* textFrame; + const nsTextFragment* textFrag = GetFragment(); + PRUint32 keptCharsLength = 0; + PRUint32 validCharsLength = 0; + + // Build skipChars and copy text, for each text frame in this continuation block + for (textFrame = this; textFrame; + textFrame = static_cast(textFrame->GetNextContinuation())) { + // For each text frame continuation in this block ... + + // Ensure the text run and grab the gfxSkipCharsIterator for it + gfxSkipCharsIterator iter = textFrame->EnsureTextRun(); + if (!textFrame->mTextRun) + return NS_ERROR_FAILURE; + + // Skip to the start of the text run, past ignored chars at start of line + // XXX In the future we may decide to trim extra spaces before a hard line + // break, in which case we need to accurately detect those sitations and + // call GetTrimmedOffsets() with PR_TRUE to trim whitespace at the line's end + TrimmedOffsets trimmedContentOffsets = textFrame->GetTrimmedOffsets(textFrag, PR_FALSE); + PRInt32 startOfLineSkipChars = trimmedContentOffsets.mStart - textFrame->mContentOffset; + if (startOfLineSkipChars > 0) { + skipCharsBuilder.SkipChars(startOfLineSkipChars); + iter.SetOriginalOffset(trimmedContentOffsets.mStart); + } + + // Keep and copy the appropriate chars withing the caller's requested range + const nsStyleText* textStyle = textFrame->GetStyleText(); + while (iter.GetOriginalOffset() < trimmedContentOffsets.GetEnd() && + keptCharsLength < aSkippedMaxLength) { + // For each original char from content text + if (iter.IsOriginalCharSkipped() || ++validCharsLength <= aSkippedStartOffset) { + skipCharsBuilder.SkipChar(); + } else { + ++keptCharsLength; + skipCharsBuilder.KeepChar(); + if (aAppendToString) { + aAppendToString->Append( + TransformChar(textStyle, textFrame->mTextRun, iter.GetSkippedOffset(), + textFrag->CharAt(iter.GetOriginalOffset()))); + } + } + iter.AdvanceOriginal(1); + } + if (keptCharsLength >= aSkippedMaxLength) { + break; // Already past the end, don't build string or gfxSkipCharsIter anymore + } + } + + if (aSkipChars) { + aSkipChars->TakeFrom(&skipCharsBuilder); // Copy skipChars into aSkipChars + if (aSkipIter) { + // Caller must provide both pointers in order to retrieve a gfxSkipCharsIterator, + // because the gfxSkipCharsIterator holds a weak pointer to the gfxSkipCars. + *aSkipIter = gfxSkipCharsIterator(*aSkipChars, GetContentLength()); + } + } + + return NS_OK; +} + +#ifdef DEBUG +// Translate the mapped content into a string that's printable +void +nsTextFrame::ToCString(nsCString& aBuf, PRInt32* aTotalContentLength) const +{ + // Get the frames text content + const nsTextFragment* frag = GetFragment(); + if (!frag) { + return; + } + + // Compute the total length of the text content. + *aTotalContentLength = frag->GetLength(); + + PRInt32 contentLength = GetContentLength(); + // Set current fragment and current fragment offset + if (0 == contentLength) { + return; + } + PRInt32 fragOffset = GetContentOffset(); + PRInt32 n = fragOffset + contentLength; + while (fragOffset < n) { + PRUnichar ch = frag->CharAt(fragOffset++); + if (ch == '\r') { + aBuf.AppendLiteral("\\r"); + } else if (ch == '\n') { + aBuf.AppendLiteral("\\n"); + } else if (ch == '\t') { + aBuf.AppendLiteral("\\t"); + } else if ((ch < ' ') || (ch >= 127)) { + aBuf.Append(nsPrintfCString("\\u%04x", ch)); + } else { + aBuf.Append(ch); + } + } +} +#endif + +nsIAtom* +nsTextFrame::GetType() const +{ + return nsGkAtoms::textFrame; +} + +/* virtual */ PRBool +nsTextFrame::IsEmpty() +{ + NS_ASSERTION(!(mState & TEXT_IS_ONLY_WHITESPACE) || + !(mState & TEXT_ISNOT_ONLY_WHITESPACE), + "Invalid state"); + + // XXXldb Should this check compatibility mode as well??? + const nsStyleText* textStyle = GetStyleText(); + if (textStyle->WhiteSpaceIsSignificant()) { + // XXX shouldn't we return true if the length is zero? + return PR_FALSE; + } + + if (mState & TEXT_ISNOT_ONLY_WHITESPACE) { + return PR_FALSE; + } + + if (mState & TEXT_IS_ONLY_WHITESPACE) { + return PR_TRUE; + } + + PRBool isEmpty = IsAllWhitespace(GetFragment(), + textStyle->mWhiteSpace != NS_STYLE_WHITESPACE_PRE_LINE); + mState |= (isEmpty ? TEXT_IS_ONLY_WHITESPACE : TEXT_ISNOT_ONLY_WHITESPACE); + return isEmpty; +} + +#ifdef DEBUG +NS_IMETHODIMP +nsTextFrame::GetFrameName(nsAString& aResult) const +{ + return MakeFrameName(NS_LITERAL_STRING("Text"), aResult); +} + +NS_IMETHODIMP_(nsFrameState) +nsTextFrame::GetDebugStateBits() const +{ + // mask out our emptystate flags; those are just caches + return nsFrame::GetDebugStateBits() & + ~(TEXT_WHITESPACE_FLAGS | TEXT_REFLOW_FLAGS); +} + +NS_IMETHODIMP +nsTextFrame::List(FILE* out, PRInt32 aIndent) const +{ + // Output the tag + IndentBy(out, aIndent); + ListTag(out); +#ifdef DEBUG_waterson + fprintf(out, " [parent=%p]", mParent); +#endif + if (HasView()) { + fprintf(out, " [view=%p]", static_cast(GetView())); + } + + PRInt32 totalContentLength; + nsCAutoString tmp; + ToCString(tmp, &totalContentLength); + + // Output the first/last content offset and prev/next in flow info + PRBool isComplete = GetContentEnd() == totalContentLength; + fprintf(out, "[%d,%d,%c] ", + GetContentOffset(), GetContentLength(), + isComplete ? 'T':'F'); + + if (nsnull != mNextSibling) { + fprintf(out, " next=%p", static_cast(mNextSibling)); + } + nsIFrame* prevContinuation = GetPrevContinuation(); + if (nsnull != prevContinuation) { + fprintf(out, " prev-continuation=%p", static_cast(prevContinuation)); + } + if (nsnull != mNextContinuation) { + fprintf(out, " next-continuation=%p", static_cast(mNextContinuation)); + } + + // Output the rect and state + fprintf(out, " {%d,%d,%d,%d}", mRect.x, mRect.y, mRect.width, mRect.height); + if (0 != mState) { + if (mState & NS_FRAME_SELECTED_CONTENT) { + fprintf(out, " [state=%08x] SELECTED", mState); + } else { + fprintf(out, " [state=%08x]", mState); + } + } + fprintf(out, " [content=%p]", static_cast(mContent)); + if (HasOverflowRect()) { + nsRect overflowArea = GetOverflowRect(); + fprintf(out, " [overflow=%d,%d,%d,%d]", overflowArea.x, overflowArea.y, + overflowArea.width, overflowArea.height); + } + fprintf(out, " sc=%p", static_cast(mStyleContext)); + nsIAtom* pseudoTag = mStyleContext->GetPseudoType(); + if (pseudoTag) { + nsAutoString atomString; + pseudoTag->ToString(atomString); + fprintf(out, " pst=%s", + NS_LossyConvertUTF16toASCII(atomString).get()); + } + fputs("<\n", out); + + // Output the text + aIndent++; + + IndentBy(out, aIndent); + fputs("\"", out); + fputs(tmp.get(), out); + fputs("\"\n", out); + + aIndent--; + IndentBy(out, aIndent); + fputs(">\n", out); + + return NS_OK; +} +#endif + +void +nsTextFrame::AdjustOffsetsForBidi(PRInt32 aStart, PRInt32 aEnd) +{ + AddStateBits(NS_FRAME_IS_BIDI); + + /* + * After Bidi resolution we may need to reassign text runs. + * This is called during bidi resolution from the block container, so we + * shouldn't be holding a local reference to a textrun anywhere. + */ + ClearTextRun(); + + nsTextFrame* prev = static_cast(GetPrevContinuation()); + if (prev) { + // the bidi resolver can be very evil when columns/pages are involved. Don't + // let it violate our invariants. + PRInt32 prevOffset = prev->GetContentOffset(); + aStart = PR_MAX(aStart, prevOffset); + aEnd = PR_MAX(aEnd, prevOffset); + prev->ClearTextRun(); + } + + mContentOffset = aStart; + SetLength(aEnd - aStart); +} + +/** + * @return PR_TRUE if this text frame ends with a newline character. It should return + * PR_FALSE if it is not a text frame. + */ +PRBool +nsTextFrame::HasTerminalNewline() const +{ + return ::HasTerminalNewline(this); +} + +PRBool +nsTextFrame::IsAtEndOfLine() const +{ + return (GetStateBits() & TEXT_END_OF_LINE) != 0; +} + +const nsTextFragment* +nsTextFrame::GetFragmentInternal() const +{ + return PresContext()->IsDynamic() ? mContent->GetText() : + nsLayoutUtils::GetTextFragmentForPrinting(this); +} diff --git a/tests/cpp/nsThread.cpp b/tests/cpp/nsThread.cpp new file mode 100644 index 0000000..9539c56 --- /dev/null +++ b/tests/cpp/nsThread.cpp @@ -0,0 +1,702 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla code. + * + * The Initial Developer of the Original Code is Google Inc. + * Portions created by the Initial Developer are Copyright (C) 2006 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Darin Fisher + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#include "timelog.h" + +#include "nsThread.h" +#include "nsThreadManager.h" +#include "nsIClassInfoImpl.h" +#include "nsIProgrammingLanguage.h" +#include "nsAutoLock.h" +#include "nsAutoPtr.h" +#include "nsCOMPtr.h" +#include "prlog.h" +#include "nsThreadUtilsInternal.h" + +#ifdef PR_LOGGING +static PRLogModuleInfo *sLog = PR_NewLogModule("nsThread"); +#endif +#define LOG(args) PR_LOG(sLog, PR_LOG_DEBUG, args) + +NS_DECL_CI_INTERFACE_GETTER(nsThread) + +nsIThreadObserver* nsThread::sGlobalObserver; + +//----------------------------------------------------------------------------- +// Because we do not have our own nsIFactory, we have to implement nsIClassInfo +// somewhat manually. + +class nsThreadClassInfo : public nsIClassInfo { +public: + NS_DECL_ISUPPORTS_INHERITED // no mRefCnt + NS_DECL_NSICLASSINFO + + nsThreadClassInfo() {} +}; + +static nsThreadClassInfo sThreadClassInfo; + +NS_IMETHODIMP_(nsrefcnt) nsThreadClassInfo::AddRef() { return 2; } +NS_IMETHODIMP_(nsrefcnt) nsThreadClassInfo::Release() { return 1; } +NS_IMPL_QUERY_INTERFACE1(nsThreadClassInfo, nsIClassInfo) + +NS_IMETHODIMP +nsThreadClassInfo::GetInterfaces(PRUint32 *count, nsIID ***array) +{ + return NS_CI_INTERFACE_GETTER_NAME(nsThread)(count, array); +} + +NS_IMETHODIMP +nsThreadClassInfo::GetHelperForLanguage(PRUint32 lang, nsISupports **result) +{ + *result = nsnull; + return NS_OK; +} + +NS_IMETHODIMP +nsThreadClassInfo::GetContractID(char **result) +{ + *result = nsnull; + return NS_OK; +} + +NS_IMETHODIMP +nsThreadClassInfo::GetClassDescription(char **result) +{ + *result = nsnull; + return NS_OK; +} + +NS_IMETHODIMP +nsThreadClassInfo::GetClassID(nsCID **result) +{ + *result = nsnull; + return NS_OK; +} + +NS_IMETHODIMP +nsThreadClassInfo::GetImplementationLanguage(PRUint32 *result) +{ + *result = nsIProgrammingLanguage::CPLUSPLUS; + return NS_OK; +} + +NS_IMETHODIMP +nsThreadClassInfo::GetFlags(PRUint32 *result) +{ + *result = THREADSAFE; + return NS_OK; +} + +NS_IMETHODIMP +nsThreadClassInfo::GetClassIDNoAlloc(nsCID *result) +{ + return NS_ERROR_NOT_AVAILABLE; +} + +//----------------------------------------------------------------------------- + +NS_IMPL_THREADSAFE_ADDREF(nsThread) +NS_IMPL_THREADSAFE_RELEASE(nsThread) +NS_INTERFACE_MAP_BEGIN(nsThread) + NS_INTERFACE_MAP_ENTRY(nsIThread) + NS_INTERFACE_MAP_ENTRY(nsIThreadInternal) + NS_INTERFACE_MAP_ENTRY(nsIEventTarget) + NS_INTERFACE_MAP_ENTRY(nsISupportsPriority) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIThread) + if (aIID.Equals(NS_GET_IID(nsIClassInfo))) { + foundInterface = static_cast(&sThreadClassInfo); + } else +NS_INTERFACE_MAP_END +NS_IMPL_CI_INTERFACE_GETTER4(nsThread, nsIThread, nsIThreadInternal, + nsIEventTarget, nsISupportsPriority) + +//----------------------------------------------------------------------------- + +class nsThreadStartupEvent : public nsRunnable { +public: + // Create a new thread startup object. + static nsThreadStartupEvent *Create() { + nsThreadStartupEvent *startup = new nsThreadStartupEvent(); + if (startup && startup->mMon) + return startup; + // Allocation failure + delete startup; + return nsnull; + } + + // This method does not return until the thread startup object is in the + // completion state. + void Wait() { + if (mInitialized) // Maybe avoid locking... + return; + nsAutoMonitor mon(mMon); + while (!mInitialized) + mon.Wait(); + } + + // This method needs to be public to support older compilers (xlC_r on AIX). + // It should be called directly as this class type is reference counted. + virtual ~nsThreadStartupEvent() { + if (mMon) + nsAutoMonitor::DestroyMonitor(mMon); + } + +private: + NS_IMETHOD Run() { + nsAutoMonitor mon(mMon); + mInitialized = PR_TRUE; + mon.Notify(); + return NS_OK; + } + + nsThreadStartupEvent() + : mMon(nsAutoMonitor::NewMonitor("xpcom.threadstartup")) + , mInitialized(PR_FALSE) { + } + + PRMonitor *mMon; + PRBool mInitialized; +}; + +//----------------------------------------------------------------------------- + +// This event is responsible for notifying nsThread::Shutdown that it is time +// to call PR_JoinThread. +class nsThreadShutdownAckEvent : public nsRunnable { +public: + nsThreadShutdownAckEvent(nsThreadShutdownContext *ctx) + : mShutdownContext(ctx) { + } + NS_IMETHOD Run() { + mShutdownContext->shutdownAck = PR_TRUE; + return NS_OK; + } +private: + nsThreadShutdownContext *mShutdownContext; +}; + +// This event is responsible for setting mShutdownContext +class nsThreadShutdownEvent : public nsRunnable { +public: + nsThreadShutdownEvent(nsThread *thr, nsThreadShutdownContext *ctx) + : mThread(thr), mShutdownContext(ctx) { + } + NS_IMETHOD Run() { + mThread->mShutdownContext = mShutdownContext; + return NS_OK; + } +private: + nsRefPtr mThread; + fprintf(logfp, "%s.%09ld: New Thread (%p)\n", out.str, out.nsec, (void *)self); + + // Inform the ThreadManager + nsThreadManager::get()->RegisterCurrentThread(self); + + // Wait for and process startup event + nsCOMPtr event; + if (!self->GetEvent(PR_TRUE, getter_AddRefs(event))) { + NS_WARNING("failed waiting for thread startup event"); + return; + } + event->Run(); // unblocks nsThread::Init + event = nsnull; + + // Now, process incoming events... + while (!self->ShuttingDown()) + NS_ProcessNextEvent(self); + + // Do NS_ProcessPendingEvents but with special handling to set + // mEventsAreDoomed atomically with the removal of the last event. The key + // invariant here is that we will never permit PutEvent to succeed if the + // event would be left in the queue after our final call to + // NS_ProcessPendingEvents. + while (PR_TRUE) { + { + nsAutoLock lock(self->mLock); + if (!self->mEvents->HasPendingEvent()) { + // No events in the queue, so we will stop now. Don't let any more + // events be added, since they won't be processed. It is critical + // that no PutEvent can occur between testing that the event queue is + // empty and setting mEventsAreDoomed! + self->mEventsAreDoomed = PR_TRUE; + break; + } + } + NS_ProcessPendingEvents(self); + } + + // Inform the threadmanager that this thread is going away + nsThreadManager::get()->UnregisterCurrentThread(self); + + // Dispatch shutdown ACK + event = new nsThreadShutdownAckEvent(self->mShutdownContext); + self->mShutdownContext->joiningThread->Dispatch(event, NS_DISPATCH_NORMAL); + + // Release any observer of the thread here. + self->SetObserver(nsnull); + + NS_RELEASE(self); +} + +//----------------------------------------------------------------------------- + +nsThread::nsThread() + : mLock(PR_NewLock()) + , mEvents(&mEventsRoot) + , mPriority(PRIORITY_NORMAL) + , mThread(nsnull) + , mRunningEvent(0) + , mShutdownContext(nsnull) + , mShutdownRequired(PR_FALSE) + , mEventsAreDoomed(PR_FALSE) +{ +} + +nsThread::~nsThread() +{ + if (mLock) + PR_DestroyLock(mLock); +} + +nsresult +nsThread::Init() +{ + NS_ENSURE_TRUE(mLock, NS_ERROR_OUT_OF_MEMORY); + + struct logtime out; + get_log_time(&out); + fprintf(logfp, "%s.%09ld: Thread (%p) Init() start\n", out.str, out.nsec, (void *)this); + + // spawn thread and wait until it is fully setup + nsRefPtr startup = nsThreadStartupEvent::Create(); + NS_ENSURE_TRUE(startup, NS_ERROR_OUT_OF_MEMORY); + + NS_ADDREF_THIS(); + + mShutdownRequired = PR_TRUE; + + // ThreadFunc is responsible for setting mThread + PRThread *thr = PR_CreateThread(PR_USER_THREAD, ThreadFunc, this, + PR_PRIORITY_NORMAL, PR_GLOBAL_THREAD, + PR_JOINABLE_THREAD, 0); + if (!thr) { + NS_RELEASE_THIS(); + return NS_ERROR_OUT_OF_MEMORY; + } + + // ThreadFunc will wait for this event to be run before it tries to access + // mThread. By delaying insertion of this event into the queue, we ensure + // that mThread is set properly. + { + nsAutoLock lock(mLock); + mEvents->PutEvent(startup); + } + + // Wait for thread to call ThreadManager::SetupCurrentThread, which completes + // initialization of ThreadFunc. + startup->Wait(); + + get_log_time(&out); + fprintf(logfp, "%s.%09ld: Thread (%p) Init() end\n", out.str, out.nsec, (void *)this); + + return NS_OK; +} + +nsresult +nsThread::InitCurrentThread() +{ + NS_ENSURE_TRUE(mLock, NS_ERROR_OUT_OF_MEMORY); + + mThread = PR_GetCurrentThread(); + + nsThreadManager::get()->RegisterCurrentThread(this); + return NS_OK; +} + +nsresult +nsThread::PutEvent(nsIRunnable *event) +{ + { + nsAutoLock lock(mLock); + if (mEventsAreDoomed) { + NS_WARNING("An event was posted to a thread that will never run it (rejected)"); + return NS_ERROR_UNEXPECTED; + } + if (!mEvents->PutEvent(event)) + return NS_ERROR_OUT_OF_MEMORY; + } + + nsCOMPtr obs = GetObserver(); + if (obs) + obs->OnDispatchedEvent(this); + + return NS_OK; +} + +//----------------------------------------------------------------------------- +// nsIEventTarget + +NS_IMETHODIMP +nsThread::Dispatch(nsIRunnable *event, PRUint32 flags) +{ + LOG(("THRD(%p) Dispatch [%p %x]\n", this, event, flags)); + + NS_ENSURE_ARG_POINTER(event); + + if (flags & DISPATCH_SYNC) { + nsThread *thread = nsThreadManager::get()->GetCurrentThread(); + NS_ENSURE_STATE(thread); + + // XXX we should be able to do something better here... we should + // be able to monitor the slot occupied by this event and use + // that to tell us when the event has been processed. + + nsRefPtr wrapper = + new nsThreadSyncDispatch(thread, event); + if (!wrapper) + return NS_ERROR_OUT_OF_MEMORY; + nsresult rv = PutEvent(wrapper); + // Don't wait for the event to finish if we didn't dispatch it... + if (NS_FAILED(rv)) + return rv; + + while (wrapper->IsPending()) + NS_ProcessNextEvent(thread); + return rv; + } + + NS_ASSERTION(flags == NS_DISPATCH_NORMAL, "unexpected dispatch flags"); + return PutEvent(event); +} + +NS_IMETHODIMP +nsThread::IsOnCurrentThread(PRBool *result) +{ + *result = (PR_GetCurrentThread() == mThread); + return NS_OK; +} + +//----------------------------------------------------------------------------- +// nsIThread + +NS_IMETHODIMP +nsThread::GetPRThread(PRThread **result) +{ + *result = mThread; + return NS_OK; +} + +NS_IMETHODIMP +nsThread::Shutdown() +{ + LOG(("THRD(%p) shutdown\n", this)); + + // XXX If we make this warn, then we hit that warning at xpcom shutdown while + // shutting down a thread in a thread pool. That happens b/c the thread + // in the thread pool is already shutdown by the thread manager. + if (!mThread) + return NS_OK; + + NS_ENSURE_STATE(mThread != PR_GetCurrentThread()); + + // Prevent multiple calls to this method + { + nsAutoLock lock(mLock); + if (!mShutdownRequired) + return NS_ERROR_UNEXPECTED; + mShutdownRequired = PR_FALSE; + } + + nsThreadShutdownContext context; + context.joiningThread = nsThreadManager::get()->GetCurrentThread(); + context.shutdownAck = PR_FALSE; + + // Set mShutdownContext and wake up the thread in case it is waiting for + // events to process. + nsCOMPtr event = new nsThreadShutdownEvent(this, &context); + if (!event) + return NS_ERROR_OUT_OF_MEMORY; + // XXXroc What if posting the event fails due to OOM? + PutEvent(event); + + // We could still end up with other events being added after the shutdown + // task, but that's okay because we process pending events in ThreadFunc + // after setting mShutdownContext just before exiting. + + // Process events on the current thread until we receive a shutdown ACK. + while (!context.shutdownAck) + NS_ProcessNextEvent(context.joiningThread); + + // Now, it should be safe to join without fear of dead-locking. + + PR_JoinThread(mThread); + mThread = nsnull; + +#ifdef DEBUG + { + nsAutoLock lock(mLock); + NS_ASSERTION(!mObserver, "Should have been cleared at shutdown!"); + } +#endif + + return NS_OK; +} + +NS_IMETHODIMP +nsThread::HasPendingEvents(PRBool *result) +{ + NS_ENSURE_STATE(PR_GetCurrentThread() == mThread); + + *result = mEvents->GetEvent(PR_FALSE, nsnull); + return NS_OK; +} + +NS_IMETHODIMP +nsThread::ProcessNextEvent(PRBool mayWait, PRBool *result) +{ + struct logtime out; + get_log_time(&out); + fprintf(logfp, "%s.%09ld: Thread (%p) ProcessNextEvent [%u %u]\n", + out.str, + out.nsec, + (void *)this, + mayWait, + mRunningEvent); + + LOG(("THRD(%p) ProcessNextEvent [%u %u]\n", this, mayWait, mRunningEvent)); + + NS_ENSURE_STATE(PR_GetCurrentThread() == mThread); + + PRBool notifyGlobalObserver = (sGlobalObserver != nsnull); + if (notifyGlobalObserver) + sGlobalObserver->OnProcessNextEvent(this, mayWait && !ShuttingDown(), + mRunningEvent); + + nsCOMPtr obs = mObserver; + if (obs) + obs->OnProcessNextEvent(this, mayWait && !ShuttingDown(), mRunningEvent); + + ++mRunningEvent; + + nsresult rv = NS_OK; + + { + // Scope for |event| to make sure that its destructor fires while + // mRunningEvent has been incremented, since that destructor can + // also do work. + + // If we are shutting down, then do not wait for new events. + nsCOMPtr event; + mEvents->GetEvent(mayWait && !ShuttingDown(), getter_AddRefs(event)); + + *result = (event.get() != nsnull); + + if (event) { + get_log_time(&out); + fprintf(logfp, "%s.%09ld: Thread (%p) running [%p]\n", + out.str, + out.nsec, + (void *)this, + (void *)event.get()); + LOG(("THRD(%p) running [%p]\n", this, event.get())); + event->Run(); + } else if (mayWait) { + NS_ASSERTION(ShuttingDown(), + "This should only happen when shutting down"); + rv = NS_ERROR_UNEXPECTED; + } + get_log_time(&out); + fprintf(logfp, "%s.%09ld: Thread (%p) running finished [%p]\n", + out.str, + out.nsec, + (void *)this, + (void *)event.get()); + } + + --mRunningEvent; + if (obs) + obs->AfterProcessNextEvent(this, mRunningEvent); + + if (notifyGlobalObserver && sGlobalObserver) + sGlobalObserver->AfterProcessNextEvent(this, mRunningEvent); + + return rv; +} + +//----------------------------------------------------------------------------- +// nsISupportsPriority + +NS_IMETHODIMP +nsThread::GetPriority(PRInt32 *priority) +{ + *priority = mPriority; + return NS_OK; +} + +NS_IMETHODIMP +nsThread::SetPriority(PRInt32 priority) +{ + NS_ENSURE_STATE(mThread); + + // NSPR defines the following four thread priorities: + // PR_PRIORITY_LOW + // PR_PRIORITY_NORMAL + // PR_PRIORITY_HIGH + // PR_PRIORITY_URGENT + // We map the priority values defined on nsISupportsPriority to these values. + + mPriority = priority; + + PRThreadPriority pri; + if (mPriority <= PRIORITY_HIGHEST) { + pri = PR_PRIORITY_URGENT; + } else if (mPriority < PRIORITY_NORMAL) { + pri = PR_PRIORITY_HIGH; + } else if (mPriority > PRIORITY_NORMAL) { + pri = PR_PRIORITY_LOW; + } else { + pri = PR_PRIORITY_NORMAL; + } + PR_SetThreadPriority(mThread, pri); + + return NS_OK; +} + +NS_IMETHODIMP +nsThread::AdjustPriority(PRInt32 delta) +{ + return SetPriority(mPriority + delta); +} + +//----------------------------------------------------------------------------- +// nsIThreadInternal + +NS_IMETHODIMP +nsThread::GetObserver(nsIThreadObserver **obs) +{ + nsAutoLock lock(mLock); + NS_IF_ADDREF(*obs = mObserver); + return NS_OK; +} + +NS_IMETHODIMP +nsThread::SetObserver(nsIThreadObserver *obs) +{ + NS_ENSURE_STATE(PR_GetCurrentThread() == mThread); + + nsAutoLock lock(mLock); + mObserver = obs; + return NS_OK; +} + +NS_IMETHODIMP +nsThread::PushEventQueue(nsIThreadEventFilter *filter) +{ + nsChainedEventQueue *queue = new nsChainedEventQueue(filter); + if (!queue || !queue->IsInitialized()) { + delete queue; + return NS_ERROR_OUT_OF_MEMORY; + } + + nsAutoLock lock(mLock); + queue->mNext = mEvents; + mEvents = queue; + return NS_OK; +} + +NS_IMETHODIMP +nsThread::PopEventQueue() +{ + nsAutoLock lock(mLock); + + // Make sure we do not pop too many! + NS_ENSURE_STATE(mEvents != &mEventsRoot); + + nsChainedEventQueue *queue = mEvents; + mEvents = mEvents->mNext; + + nsCOMPtr event; + while (queue->GetEvent(PR_FALSE, getter_AddRefs(event))) + mEvents->PutEvent(event); + + delete queue; + + return NS_OK; +} + +PRBool +nsThread::nsChainedEventQueue::PutEvent(nsIRunnable *event) +{ + PRBool val; + if (!mFilter || mFilter->AcceptEvent(event)) { + val = mQueue.PutEvent(event); + } else { + val = mNext->PutEvent(event); + } + return val; +} + +//----------------------------------------------------------------------------- + +NS_IMETHODIMP +nsThreadSyncDispatch::Run() +{ + if (mSyncTask) { + mSyncTask->Run(); + mSyncTask = nsnull; + // unblock the origin thread + mOrigin->Dispatch(this, NS_DISPATCH_NORMAL); + } + return NS_OK; +} + +nsresult +NS_SetGlobalThreadObserver(nsIThreadObserver* aObserver) +{ + if (aObserver && nsThread::sGlobalObserver) { + return NS_ERROR_NOT_AVAILABLE; + } + + if (!NS_IsMainThread()) { + return NS_ERROR_UNEXPECTED; + } + + nsThread::sGlobalObserver = aObserver; + return NS_OK; +} diff --git a/tests/cpp/nsThread.h b/tests/cpp/nsThread.h new file mode 100644 index 0000000..4ad1032 --- /dev/null +++ b/tests/cpp/nsThread.h @@ -0,0 +1,170 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla code. + * + * The Initial Developer of the Original Code is Google Inc. + * Portions created by the Initial Developer are Copyright (C) 2006 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Darin Fisher + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#ifndef nsThread_h__ +#define nsThread_h__ + +#include "nsIThreadInternal.h" +#include "nsISupportsPriority.h" +#include "nsEventQueue.h" +#include "nsThreadUtils.h" +#include "nsString.h" +#include "nsAutoLock.h" +#include "nsAutoPtr.h" + +// A native thread +class nsThread : public nsIThreadInternal, public nsISupportsPriority +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIEVENTTARGET + NS_DECL_NSITHREAD + NS_DECL_NSITHREADINTERNAL + NS_DECL_NSISUPPORTSPRIORITY + + nsThread(); + + // Initialize this as a wrapper for a new PRThread. + nsresult Init(); + + // Initialize this as a wrapper for the current PRThread. + nsresult InitCurrentThread(); + + // The PRThread corresponding to this thread. + PRThread *GetPRThread() { return mThread; } + + // If this flag is true, then the nsThread was created using + // nsIThreadManager::NewThread. + PRBool ShutdownRequired() { return mShutdownRequired; } + + // The global thread observer + static nsIThreadObserver* sGlobalObserver; + +private: + friend class nsThreadShutdownEvent; + + ~nsThread(); + + PRBool ShuttingDown() { return mShutdownContext != nsnull; } + + static void ThreadFunc(void *arg); + + // Helper + already_AddRefed GetObserver() { + nsIThreadObserver *obs; + nsThread::GetObserver(&obs); + return already_AddRefed(obs); + } + + // Wrappers for event queue methods: + PRBool GetEvent(PRBool mayWait, nsIRunnable **event) { + return mEvents->GetEvent(mayWait, event); + } + nsresult PutEvent(nsIRunnable *event); + + // Wrapper for nsEventQueue that supports chaining. + class nsChainedEventQueue { + public: + nsChainedEventQueue(nsIThreadEventFilter *filter = nsnull) + : mNext(nsnull), mFilter(filter) { + } + + PRBool IsInitialized() { + return mQueue.IsInitialized(); + } + + PRBool GetEvent(PRBool mayWait, nsIRunnable **event) { + return mQueue.GetEvent(mayWait, event); + } + + PRBool PutEvent(nsIRunnable *event); + + PRBool HasPendingEvent() { + return mQueue.HasPendingEvent(); + } + + class nsChainedEventQueue *mNext; + private: + nsCOMPtr mFilter; + nsEventQueue mQueue; + }; + + // This lock protects access to mObserver, mEvents and mEventsAreDoomed. + // All of those fields are only modified on the thread itself (never from + // another thread). This means that we can avoid holding the lock while + // using mObserver and mEvents on the thread itself. When calling PutEvent + // on mEvents, we have to hold the lock to synchronize with PopEventQueue. + PRLock *mLock; + + nsCOMPtr mObserver; + + nsChainedEventQueue *mEvents; // never null + nsChainedEventQueue mEventsRoot; + + PRInt32 mPriority; + PRThread *mThread; + PRUint32 mRunningEvent; // counter + + struct nsThreadShutdownContext *mShutdownContext; + + PRPackedBool mShutdownRequired; + PRPackedBool mShutdownPending; + // Set to true when events posted to this thread will never run. + PRPackedBool mEventsAreDoomed; +}; + +//----------------------------------------------------------------------------- + +class nsThreadSyncDispatch : public nsRunnable { +public: + nsThreadSyncDispatch(nsIThread *origin, nsIRunnable *task) + : mOrigin(origin), mSyncTask(task) { + } + + PRBool IsPending() { + return mSyncTask != nsnull; + } + +private: + NS_DECL_NSIRUNNABLE + + nsCOMPtr mOrigin; + nsCOMPtr mSyncTask; +}; + +#endif // nsThread_h__ diff --git a/tests/cpp/nsThread_part.cpp b/tests/cpp/nsThread_part.cpp new file mode 100644 index 0000000..6921941 --- /dev/null +++ b/tests/cpp/nsThread_part.cpp @@ -0,0 +1,46 @@ +namespace { +struct nsThreadShutdownContext { + nsThread *joiningThread; + PRBool shutdownAck; + union { + int a; + int b; + union { + char f; + char g; + }; + }; +}; +}; + +struct teststruct { + char foo; + union { + int x; + int y; + }; +}; + +namespace +{ + short int i = 0; + + class Testclass + { + int j; + + public: + + Testclass(); + ~Testclass(); + }; + + namespace + { + string str = ""; + class FooClass + { + FooClass(); + }; + } +}; diff --git a/tests/cpp/nsXPComInit.cpp b/tests/cpp/nsXPComInit.cpp new file mode 100644 index 0000000..86839c2 --- /dev/null +++ b/tests/cpp/nsXPComInit.cpp @@ -0,0 +1,1023 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* vim:set ts=4 sw=4 sts=4 ci et: */ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is mozilla.org code. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Benjamin Smedberg + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#ifdef MOZ_IPC +#include "base/basictypes.h" +#endif + +#include "mozilla/XPCOM.h" +#include "nsXULAppAPI.h" + +#include "nsXPCOMPrivate.h" +#include "nsXPCOMCIDInternal.h" + +#include "nsStaticComponents.h" +#include "prlink.h" + +#include "nsObserverList.h" +#include "nsObserverService.h" +#include "nsProperties.h" +#include "nsPersistentProperties.h" +#include "nsScriptableInputStream.h" +#include "nsBinaryStream.h" +#include "nsStorageStream.h" +#include "nsPipe.h" + +#include "nsMemoryImpl.h" +#include "nsDebugImpl.h" +#include "nsTraceRefcntImpl.h" +#include "nsErrorService.h" +#include "nsByteBuffer.h" + +#include "nsSupportsArray.h" +#include "nsArray.h" +#include "nsINIParserImpl.h" +#include "nsSupportsPrimitives.h" +#include "nsConsoleService.h" +#include "nsExceptionService.h" + +#include "nsComponentManager.h" +#include "nsCategoryManagerUtils.h" +#include "nsIServiceManager.h" +#include "nsGenericFactory.h" + +#include "nsThreadManager.h" +#include "nsThreadPool.h" + +#include "nsIProxyObjectManager.h" +#include "nsProxyEventPrivate.h" // access to the impl of nsProxyObjectManager for the generic factory registration. + +#include "xptinfo.h" +#include "nsIInterfaceInfoManager.h" +#include "xptiprivate.h" + +#include "nsTimerImpl.h" +#include "TimerThread.h" + +#include "nsThread.h" +#include "nsProcess.h" +#include "nsEnvironment.h" +#include "nsVersionComparatorImpl.h" + +#include "nsILocalFile.h" +#include "nsLocalFile.h" +#if defined(XP_UNIX) || defined(XP_OS2) +#include "nsNativeCharsetUtils.h" +#endif +#include "nsDirectoryService.h" +#include "nsDirectoryServiceDefs.h" +#include "nsCategoryManager.h" +#include "nsICategoryManager.h" +#include "nsMultiplexInputStream.h" + +#include "nsStringStream.h" +extern NS_METHOD nsStringInputStreamConstructor(nsISupports *, REFNSIID, void **); +NS_DECL_CLASSINFO(nsStringInputStream) + +#include "nsFastLoadService.h" + +#include "nsAtomService.h" +#include "nsAtomTable.h" +#include "nsTraceRefcnt.h" +#include "nsTimelineService.h" + +#include "nsHashPropertyBag.h" + +#include "nsUnicharInputStream.h" +#include "nsVariant.h" + +#include "nsUUIDGenerator.h" + +#include "nsIOUtil.h" + +#ifdef GC_LEAK_DETECTOR +#include "nsLeakDetector.h" +#endif +#include "nsRecyclingAllocator.h" + +#include "SpecialSystemDirectory.h" + +#if defined(XP_WIN) +#include "nsWindowsRegKey.h" +#endif + +#ifdef XP_MACOSX +#include "nsMacUtilsImpl.h" +#endif + +#include "nsSystemInfo.h" +#include "nsMemoryReporterManager.h" + +#include + +#ifdef MOZ_IPC +#include "base/at_exit.h" +#include "base/command_line.h" +#include "base/message_loop.h" + +#include "mozilla/ipc/BrowserProcessSubThread.h" + +using base::AtExitManager; +using mozilla::ipc::BrowserProcessSubThread; + +namespace { + +static AtExitManager* sExitManager; +static MessageLoop* sMessageLoop; +static bool sCommandLineWasInitialized; +static BrowserProcessSubThread* sIOThread; + +} /* anonymous namespace */ +#endif + +using mozilla::TimeStamp; + +// Registry Factory creation function defined in nsRegistry.cpp +// We hook into this function locally to create and register the registry +// Since noone outside xpcom needs to know about this and nsRegistry.cpp +// does not have a local include file, we are putting this definition +// here rather than in nsIRegistry.h +extern nsresult NS_RegistryGetFactory(nsIFactory** aFactory); +extern nsresult NS_CategoryManagerGetFactory( nsIFactory** ); + +#ifdef DEBUG +extern void _FreeAutoLockStatics(); +#endif + +static NS_DEFINE_CID(kComponentManagerCID, NS_COMPONENTMANAGER_CID); +static NS_DEFINE_CID(kMemoryCID, NS_MEMORY_CID); +static NS_DEFINE_CID(kINIParserFactoryCID, NS_INIPARSERFACTORY_CID); +static NS_DEFINE_CID(kSimpleUnicharStreamFactoryCID, NS_SIMPLE_UNICHAR_STREAM_FACTORY_CID); + +NS_GENERIC_FACTORY_CONSTRUCTOR(nsProcess) + +#define NS_ENVIRONMENT_CLASSNAME "Environment Service" + +// ds/nsISupportsPrimitives +#define NS_SUPPORTS_ID_CLASSNAME "Supports ID" +#define NS_SUPPORTS_CSTRING_CLASSNAME "Supports String" +#define NS_SUPPORTS_STRING_CLASSNAME "Supports WString" +#define NS_SUPPORTS_PRBOOL_CLASSNAME "Supports PRBool" +#define NS_SUPPORTS_PRUINT8_CLASSNAME "Supports PRUint8" +#define NS_SUPPORTS_PRUINT16_CLASSNAME "Supports PRUint16" +#define NS_SUPPORTS_PRUINT32_CLASSNAME "Supports PRUint32" +#define NS_SUPPORTS_PRUINT64_CLASSNAME "Supports PRUint64" +#define NS_SUPPORTS_PRTIME_CLASSNAME "Supports PRTime" +#define NS_SUPPORTS_CHAR_CLASSNAME "Supports Char" +#define NS_SUPPORTS_PRINT16_CLASSNAME "Supports PRInt16" +#define NS_SUPPORTS_PRINT32_CLASSNAME "Supports PRInt32" +#define NS_SUPPORTS_PRINT64_CLASSNAME "Supports PRInt64" +#define NS_SUPPORTS_FLOAT_CLASSNAME "Supports float" +#define NS_SUPPORTS_DOUBLE_CLASSNAME "Supports double" +#define NS_SUPPORTS_VOID_CLASSNAME "Supports void" +#define NS_SUPPORTS_INTERFACE_POINTER_CLASSNAME "Supports interface pointer" + +NS_GENERIC_FACTORY_CONSTRUCTOR(nsSupportsIDImpl) +NS_GENERIC_FACTORY_CONSTRUCTOR(nsSupportsStringImpl) +NS_GENERIC_FACTORY_CONSTRUCTOR(nsSupportsCStringImpl) +NS_GENERIC_FACTORY_CONSTRUCTOR(nsSupportsPRBoolImpl) +NS_GENERIC_FACTORY_CONSTRUCTOR(nsSupportsPRUint8Impl) +NS_GENERIC_FACTORY_CONSTRUCTOR(nsSupportsPRUint16Impl) +NS_GENERIC_FACTORY_CONSTRUCTOR(nsSupportsPRUint32Impl) +NS_GENERIC_FACTORY_CONSTRUCTOR(nsSupportsPRUint64Impl) +NS_GENERIC_FACTORY_CONSTRUCTOR(nsSupportsPRTimeImpl) +NS_GENERIC_FACTORY_CONSTRUCTOR(nsSupportsCharImpl) +NS_GENERIC_FACTORY_CONSTRUCTOR(nsSupportsPRInt16Impl) +NS_GENERIC_FACTORY_CONSTRUCTOR(nsSupportsPRInt32Impl) +NS_GENERIC_FACTORY_CONSTRUCTOR(nsSupportsPRInt64Impl) +NS_GENERIC_FACTORY_CONSTRUCTOR(nsSupportsFloatImpl) +NS_GENERIC_FACTORY_CONSTRUCTOR(nsSupportsDoubleImpl) +NS_GENERIC_FACTORY_CONSTRUCTOR(nsSupportsVoidImpl) +NS_GENERIC_FACTORY_CONSTRUCTOR(nsSupportsInterfacePointerImpl) + +NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsConsoleService, Init) +NS_DECL_CLASSINFO(nsConsoleService) +NS_GENERIC_FACTORY_CONSTRUCTOR(nsAtomService) +NS_GENERIC_FACTORY_CONSTRUCTOR(nsExceptionService) +NS_GENERIC_FACTORY_CONSTRUCTOR(nsTimerImpl) +NS_GENERIC_FACTORY_CONSTRUCTOR(nsBinaryOutputStream) +NS_GENERIC_FACTORY_CONSTRUCTOR(nsBinaryInputStream) +NS_GENERIC_FACTORY_CONSTRUCTOR(nsStorageStream) +NS_GENERIC_FACTORY_CONSTRUCTOR(nsVersionComparatorImpl) + +NS_GENERIC_FACTORY_CONSTRUCTOR(nsVariant) + +NS_GENERIC_FACTORY_CONSTRUCTOR(nsRecyclingAllocatorImpl) + +#ifdef MOZ_TIMELINE +NS_GENERIC_FACTORY_CONSTRUCTOR(nsTimelineService) +#endif + +NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsHashPropertyBag, Init) + +NS_GENERIC_AGGREGATED_CONSTRUCTOR_INIT(nsProperties, Init) + +NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsUUIDGenerator, Init) + +#ifdef XP_MACOSX +NS_GENERIC_FACTORY_CONSTRUCTOR(nsMacUtilsImpl) +#endif + +NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsSystemInfo, Init) + +NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsMemoryReporterManager, Init) + +NS_GENERIC_FACTORY_CONSTRUCTOR(nsIOUtil) + +static NS_METHOD +nsThreadManagerGetSingleton(nsISupports* outer, + const nsIID& aIID, + void* *aInstancePtr) +{ + NS_ASSERTION(aInstancePtr, "null outptr"); + NS_ENSURE_TRUE(!outer, NS_ERROR_NO_AGGREGATION); + + return nsThreadManager::get()->QueryInterface(aIID, aInstancePtr); +} +NS_DECL_CLASSINFO(nsThreadManager) + +NS_GENERIC_FACTORY_CONSTRUCTOR(nsThreadPool) +NS_DECL_CLASSINFO(nsThreadPool) + +static NS_METHOD +nsXPTIInterfaceInfoManagerGetSingleton(nsISupports* outer, + const nsIID& aIID, + void* *aInstancePtr) +{ + NS_ASSERTION(aInstancePtr, "null outptr"); + NS_ENSURE_TRUE(!outer, NS_ERROR_NO_AGGREGATION); + + nsCOMPtr iim + (xptiInterfaceInfoManager::GetInterfaceInfoManagerNoAddRef()); + if (!iim) + return NS_ERROR_FAILURE; + + return iim->QueryInterface(aIID, aInstancePtr); +} + + +static nsresult +RegisterGenericFactory(nsIComponentRegistrar* registrar, + const nsModuleComponentInfo *info) +{ + nsresult rv; + nsIGenericFactory* fact; + rv = NS_NewGenericFactory(&fact, info); + if (NS_FAILED(rv)) return rv; + + rv = registrar->RegisterFactory(info->mCID, + info->mDescription, + info->mContractID, + fact); + NS_RELEASE(fact); + return rv; +} + +// In order to support the installer, we need +// to be told out of band if we should cause +// an autoregister. If the file ".autoreg" exists in the binary +// directory, we check its timestamp against the timestamp of the +// compreg.dat file. If the .autoreg file is newer, we autoregister. +static PRBool CheckUpdateFile() +{ + nsresult rv; + nsCOMPtr compregFile; + rv = nsDirectoryService::gService->Get(NS_XPCOM_COMPONENT_REGISTRY_FILE, + NS_GET_IID(nsIFile), + getter_AddRefs(compregFile)); + + if (NS_FAILED(rv)) { + NS_WARNING("Getting NS_XPCOM_COMPONENT_REGISTRY_FILE failed"); + return PR_FALSE; + } + + PRInt64 compregModTime; + rv = compregFile->GetLastModifiedTime(&compregModTime); + if (NS_FAILED(rv)) + return PR_TRUE; + + nsCOMPtr file; + rv = nsDirectoryService::gService->Get(NS_XPCOM_CURRENT_PROCESS_DIR, + NS_GET_IID(nsIFile), + getter_AddRefs(file)); + + if (NS_FAILED(rv)) { + NS_WARNING("Getting NS_XPCOM_CURRENT_PROCESS_DIR failed"); + return PR_FALSE; + } + + file->AppendNative(nsDependentCString(".autoreg")); + + // superfluous cast + PRInt64 nowTime = PR_Now() / PR_USEC_PER_MSEC; + PRInt64 autoregModTime; + rv = file->GetLastModifiedTime(&autoregModTime); + if (NS_FAILED(rv)) + goto next; + + if (autoregModTime > compregModTime) { + if (autoregModTime < nowTime) { + return PR_TRUE; + } else { + NS_WARNING("Screwy timestamps, ignoring .autoreg"); + } + } + +next: + nsCOMPtr greFile; + rv = nsDirectoryService::gService->Get(NS_GRE_DIR, + NS_GET_IID(nsIFile), + getter_AddRefs(greFile)); + + if (NS_FAILED(rv)) { + NS_WARNING("Getting NS_GRE_DIR failed"); + return PR_FALSE; + } + + greFile->AppendNative(nsDependentCString(".autoreg")); + + PRBool equals; + rv = greFile->Equals(file, &equals); + if (NS_SUCCEEDED(rv) && equals) + return PR_FALSE; + + rv = greFile->GetLastModifiedTime(&autoregModTime); + if (NS_FAILED(rv)) + return PR_FALSE; + + if (autoregModTime > nowTime) { + NS_WARNING("Screwy timestamps, ignoring .autoreg"); + return PR_FALSE; + } + return autoregModTime > compregModTime; +} + + +nsComponentManagerImpl* nsComponentManagerImpl::gComponentManager = NULL; +PRBool gXPCOMShuttingDown = PR_FALSE; + +// For each class that wishes to support nsIClassInfo, add a line like this +// NS_DECL_CLASSINFO(nsMyClass) + +#define COMPONENT(NAME, Ctor) \ + { NS_##NAME##_CLASSNAME, NS_##NAME##_CID, NS_##NAME##_CONTRACTID, Ctor } + +#define COMPONENT_CI(NAME, Ctor, Class) \ + { NS_##NAME##_CLASSNAME, NS_##NAME##_CID, NS_##NAME##_CONTRACTID, Ctor, \ + NULL, NULL, NULL, NS_CI_INTERFACE_GETTER_NAME(Class), NULL, \ + &NS_CLASSINFO_NAME(Class) } + +#define COMPONENT_CI_FLAGS(NAME, Ctor, Class, Flags) \ + { NS_##NAME##_CLASSNAME, NS_##NAME##_CID, NS_##NAME##_CONTRACTID, Ctor, \ + NULL, NULL, NULL, NS_CI_INTERFACE_GETTER_NAME(Class), NULL, \ + &NS_CLASSINFO_NAME(Class), Flags } + +static const nsModuleComponentInfo components[] = { + COMPONENT(MEMORY, nsMemoryImpl::Create), + COMPONENT(DEBUG, nsDebugImpl::Create), +#define NS_ERRORSERVICE_CLASSNAME NS_ERRORSERVICE_NAME + COMPONENT(ERRORSERVICE, nsErrorService::Create), + + COMPONENT(BYTEBUFFER, ByteBufferImpl::Create), + COMPONENT(SCRIPTABLEINPUTSTREAM, nsScriptableInputStream::Create), + COMPONENT(BINARYINPUTSTREAM, nsBinaryInputStreamConstructor), + COMPONENT(BINARYOUTPUTSTREAM, nsBinaryOutputStreamConstructor), + COMPONENT(STORAGESTREAM, nsStorageStreamConstructor), + COMPONENT(VERSIONCOMPARATOR, nsVersionComparatorImplConstructor), + COMPONENT(PIPE, nsPipeConstructor), + +#define NS_PROPERTIES_CLASSNAME "Properties" + COMPONENT(PROPERTIES, nsPropertiesConstructor), + +#define NS_PERSISTENTPROPERTIES_CID NS_IPERSISTENTPROPERTIES_CID /* sigh */ + COMPONENT(PERSISTENTPROPERTIES, nsPersistentProperties::Create), + + COMPONENT(SUPPORTSARRAY, nsSupportsArray::Create), + COMPONENT(ARRAY, nsArrayConstructor), + COMPONENT_CI_FLAGS(CONSOLESERVICE, nsConsoleServiceConstructor, + nsConsoleService, + nsIClassInfo::THREADSAFE | nsIClassInfo::SINGLETON), + COMPONENT(EXCEPTIONSERVICE, nsExceptionServiceConstructor), + COMPONENT(ATOMSERVICE, nsAtomServiceConstructor), +#ifdef MOZ_TIMELINE + COMPONENT(TIMELINESERVICE, nsTimelineServiceConstructor), +#endif + COMPONENT(OBSERVERSERVICE, nsObserverService::Create), + COMPONENT(GENERICFACTORY, nsGenericFactory::Create), + +#define NS_XPCOMPROXY_CID NS_PROXYEVENT_MANAGER_CID + COMPONENT(XPCOMPROXY, nsProxyObjectManager::Create), + + COMPONENT(TIMER, nsTimerImplConstructor), + +#define COMPONENT_SUPPORTS(TYPE, Type) \ + COMPONENT(SUPPORTS_##TYPE, nsSupports##Type##ImplConstructor) + + COMPONENT_SUPPORTS(ID, ID), + COMPONENT_SUPPORTS(STRING, String), + COMPONENT_SUPPORTS(CSTRING, CString), + COMPONENT_SUPPORTS(PRBOOL, PRBool), + COMPONENT_SUPPORTS(PRUINT8, PRUint8), + COMPONENT_SUPPORTS(PRUINT16, PRUint16), + COMPONENT_SUPPORTS(PRUINT32, PRUint32), + COMPONENT_SUPPORTS(PRUINT64, PRUint64), + COMPONENT_SUPPORTS(PRTIME, PRTime), + COMPONENT_SUPPORTS(CHAR, Char), + COMPONENT_SUPPORTS(PRINT16, PRInt16), + COMPONENT_SUPPORTS(PRINT32, PRInt32), + COMPONENT_SUPPORTS(PRINT64, PRInt64), + COMPONENT_SUPPORTS(FLOAT, Float), + COMPONENT_SUPPORTS(DOUBLE, Double), + COMPONENT_SUPPORTS(VOID, Void), + COMPONENT_SUPPORTS(INTERFACE_POINTER, InterfacePointer), + +#undef COMPONENT_SUPPORTS +#define NS_LOCAL_FILE_CLASSNAME "Local File Specification" + COMPONENT(LOCAL_FILE, nsLocalFile::nsLocalFileConstructor), +#define NS_DIRECTORY_SERVICE_CLASSNAME "nsIFile Directory Service" + COMPONENT(DIRECTORY_SERVICE, nsDirectoryService::Create), + COMPONENT(PROCESS, nsProcessConstructor), + COMPONENT(ENVIRONMENT, nsEnvironment::Create), + + COMPONENT_CI_FLAGS(THREADMANAGER, nsThreadManagerGetSingleton, + nsThreadManager, + nsIClassInfo::THREADSAFE | nsIClassInfo::SINGLETON), + COMPONENT_CI_FLAGS(THREADPOOL, nsThreadPoolConstructor, + nsThreadPool, nsIClassInfo::THREADSAFE), + + COMPONENT_CI_FLAGS(STRINGINPUTSTREAM, nsStringInputStreamConstructor, + nsStringInputStream, nsIClassInfo::THREADSAFE), + COMPONENT(MULTIPLEXINPUTSTREAM, nsMultiplexInputStreamConstructor), + +#ifndef MOZ_NO_FAST_LOAD + COMPONENT(FASTLOADSERVICE, nsFastLoadService::Create), +#endif + + COMPONENT(VARIANT, nsVariantConstructor), + COMPONENT(INTERFACEINFOMANAGER_SERVICE, nsXPTIInterfaceInfoManagerGetSingleton), + + COMPONENT(RECYCLINGALLOCATOR, nsRecyclingAllocatorImplConstructor), + +#define NS_HASH_PROPERTY_BAG_CLASSNAME "Hashtable Property Bag" + COMPONENT(HASH_PROPERTY_BAG, nsHashPropertyBagConstructor), + + COMPONENT(UUID_GENERATOR, nsUUIDGeneratorConstructor), + +#if defined(XP_WIN) + COMPONENT(WINDOWSREGKEY, nsWindowsRegKeyConstructor), +#endif + +#ifdef XP_MACOSX + COMPONENT(MACUTILSIMPL, nsMacUtilsImplConstructor), +#endif + + COMPONENT(SYSTEMINFO, nsSystemInfoConstructor), +#define NS_MEMORY_REPORTER_MANAGER_CLASSNAME "Memory Reporter Manager" + COMPONENT(MEMORY_REPORTER_MANAGER, nsMemoryReporterManagerConstructor), + COMPONENT(IOUTIL, nsIOUtilConstructor), +}; + +#undef COMPONENT + +const int components_length = sizeof(components) / sizeof(components[0]); + +// gDebug will be freed during shutdown. +static nsIDebug* gDebug = nsnull; + +EXPORT_XPCOM_API(nsresult) +NS_GetDebug(nsIDebug** result) +{ + return nsDebugImpl::Create(nsnull, + NS_GET_IID(nsIDebug), + (void**) result); +} + +EXPORT_XPCOM_API(nsresult) +NS_GetTraceRefcnt(nsITraceRefcnt** result) +{ + return nsTraceRefcntImpl::Create(nsnull, + NS_GET_IID(nsITraceRefcnt), + (void**) result); +} + +EXPORT_XPCOM_API(nsresult) +NS_InitXPCOM(nsIServiceManager* *result, + nsIFile* binDirectory) +{ + return NS_InitXPCOM3(result, binDirectory, nsnull, nsnull, 0); +} + +EXPORT_XPCOM_API(nsresult) +NS_InitXPCOM2(nsIServiceManager* *result, + nsIFile* binDirectory, + nsIDirectoryServiceProvider* appFileLocationProvider) +{ + return NS_InitXPCOM3(result, binDirectory, appFileLocationProvider, nsnull, 0); +} + +#include "timelog.h" + +void get_log_time(struct logtime *out) +{ + char timeformat[] = "%Y-%m-%d %H:%M:%S"; + struct timespec ts; + clock_gettime(CLOCK_REALTIME, &ts); + strftime(out->str, TIMELEN, timeformat, localtime(&ts.tv_sec)); + out->nsec = ts.tv_nsec; +} + +FILE *logfp; + +EXPORT_XPCOM_API(nsresult) +NS_InitXPCOM3(nsIServiceManager* *result, + nsIFile* binDirectory, + nsIDirectoryServiceProvider* appFileLocationProvider, + nsStaticModuleInfo const *staticComponents, + PRUint32 componentCount) +{ + nsresult rv = NS_OK; + +#ifdef MOZ_ENABLE_LIBXUL + if (!staticComponents) { + staticComponents = kPStaticModules; + componentCount = kStaticModuleCount; + } +#endif + + // We are not shutting down + gXPCOMShuttingDown = PR_FALSE; + +#ifdef MOZ_IPC + // Set up chromium libs + NS_ASSERTION(!sExitManager && !sMessageLoop, "Bad logic!"); + + if (!AtExitManager::AlreadyRegistered()) { + sExitManager = new AtExitManager(); + NS_ENSURE_STATE(sExitManager); + } + + if (!MessageLoop::current()) { + sMessageLoop = new MessageLoopForUI(MessageLoop::TYPE_MOZILLA_UI); + NS_ENSURE_STATE(sMessageLoop); + } + + if (XRE_GetProcessType() == GeckoProcessType_Default && + !BrowserProcessSubThread::GetMessageLoop(BrowserProcessSubThread::IO)) { + scoped_ptr ioThread( + new BrowserProcessSubThread(BrowserProcessSubThread::IO)); + NS_ENSURE_TRUE(ioThread.get(), NS_ERROR_OUT_OF_MEMORY); + + base::Thread::Options options; + options.message_loop_type = MessageLoop::TYPE_IO; + NS_ENSURE_TRUE(ioThread->StartWithOptions(options), NS_ERROR_FAILURE); + + sIOThread = ioThread.release(); + } +#endif + + NS_LogInit(); + + logfp = fopen("time.log", "w"); +// logfp = stdout; + + // Set up TimeStamp + rv = TimeStamp::Startup(); + NS_ENSURE_SUCCESS(rv, rv); + + // Establish the main thread here. + struct logtime out; + get_log_time(&out); + fprintf(logfp, "%s.%09ld: Establish main thread\n", out.str, out.nsec); + rv = nsThreadManager::get()->Init(); + if (NS_FAILED(rv)) return rv; + + // Set up the timer globals/timer thread + rv = nsTimerImpl::Startup(); + NS_ENSURE_SUCCESS(rv, rv); + +#ifndef WINCE + // If the locale hasn't already been setup by our embedder, + // get us out of the "C" locale and into the system + if (strcmp(setlocale(LC_ALL, NULL), "C") == 0) + setlocale(LC_ALL, ""); +#endif + +#if defined(XP_UNIX) || defined(XP_OS2) + NS_StartupNativeCharsetUtils(); +#endif + NS_StartupLocalFile(); + + StartupSpecialSystemDirectory(); + + rv = nsDirectoryService::RealInit(); + if (NS_FAILED(rv)) + return rv; + + nsCOMPtr xpcomLib; + + PRBool value; + if (binDirectory) + { + rv = binDirectory->IsDirectory(&value); + + if (NS_SUCCEEDED(rv) && value) { + nsDirectoryService::gService->Set(NS_XPCOM_INIT_CURRENT_PROCESS_DIR, binDirectory); + binDirectory->Clone(getter_AddRefs(xpcomLib)); + } + } + else { + nsDirectoryService::gService->Get(NS_XPCOM_CURRENT_PROCESS_DIR, + NS_GET_IID(nsIFile), + getter_AddRefs(xpcomLib)); + } + + if (xpcomLib) { + xpcomLib->AppendNative(nsDependentCString(XPCOM_DLL)); + nsDirectoryService::gService->Set(NS_XPCOM_LIBRARY_FILE, xpcomLib); + } + + if (appFileLocationProvider) { + rv = nsDirectoryService::gService->RegisterProvider(appFileLocationProvider); + if (NS_FAILED(rv)) return rv; + } + +#ifdef MOZ_IPC + if ((sCommandLineWasInitialized = !CommandLine::IsInitialized())) { +#ifdef OS_WIN + CommandLine::Init(0, nsnull); +#else + nsCOMPtr binaryFile; + nsDirectoryService::gService->Get(NS_XPCOM_CURRENT_PROCESS_DIR, + NS_GET_IID(nsIFile), + getter_AddRefs(binaryFile)); + NS_ENSURE_STATE(binaryFile); + + rv = binaryFile->AppendNative(NS_LITERAL_CSTRING("nonexistent-executable")); + NS_ENSURE_SUCCESS(rv, rv); + + nsCString binaryPath; + rv = binaryFile->GetNativePath(binaryPath); + NS_ENSURE_SUCCESS(rv, rv); + + static char const *const argv = { strdup(binaryPath.get()) }; + CommandLine::Init(1, &argv); +#endif + } +#endif + + NS_ASSERTION(nsComponentManagerImpl::gComponentManager == NULL, "CompMgr not null at init"); + + // Create the Component/Service Manager + nsComponentManagerImpl *compMgr = new nsComponentManagerImpl(); + if (compMgr == NULL) + return NS_ERROR_OUT_OF_MEMORY; + NS_ADDREF(compMgr); + + rv = compMgr->Init(staticComponents, componentCount); + if (NS_FAILED(rv)) + { + NS_RELEASE(compMgr); + return rv; + } + + nsComponentManagerImpl::gComponentManager = compMgr; + + if (result) { + nsIServiceManager *serviceManager = + static_cast(compMgr); + + NS_ADDREF(*result = serviceManager); + } + + nsCOMPtr memory; + NS_GetMemoryManager(getter_AddRefs(memory)); + rv = compMgr->RegisterService(kMemoryCID, memory); + if (NS_FAILED(rv)) return rv; + + rv = compMgr->RegisterService(kComponentManagerCID, static_cast(compMgr)); + if (NS_FAILED(rv)) return rv; + +#ifdef GC_LEAK_DETECTOR + rv = NS_InitLeakDetector(); + if (NS_FAILED(rv)) return rv; +#endif + + rv = nsCycleCollector_startup(); + if (NS_FAILED(rv)) return rv; + + // 2. Register the global services with the component manager so that + // clients can create new objects. + + // Category Manager + { + nsCOMPtr categoryManagerFactory; + if ( NS_FAILED(rv = NS_CategoryManagerGetFactory(getter_AddRefs(categoryManagerFactory))) ) + return rv; + + NS_DEFINE_CID(kCategoryManagerCID, NS_CATEGORYMANAGER_CID); + + rv = compMgr->RegisterFactory(kCategoryManagerCID, + NS_CATEGORYMANAGER_CLASSNAME, + NS_CATEGORYMANAGER_CONTRACTID, + categoryManagerFactory, + PR_TRUE); + if ( NS_FAILED(rv) ) return rv; + } + + nsCOMPtr registrar = do_QueryInterface( + static_cast(compMgr), &rv); + if (registrar) { + for (int i = 0; i < components_length; i++) + RegisterGenericFactory(registrar, &components[i]); + + nsCOMPtr iniParserFactory(new nsINIParserFactory()); + if (iniParserFactory) + registrar->RegisterFactory(kINIParserFactoryCID, + "nsINIParserFactory", + NS_INIPARSERFACTORY_CONTRACTID, + iniParserFactory); + + registrar-> + RegisterFactory(kSimpleUnicharStreamFactoryCID, + "nsSimpleUnicharStreamFactory", + NS_SIMPLE_UNICHAR_STREAM_FACTORY_CONTRACTID, + nsSimpleUnicharStreamFactory::GetInstance()); + } + + // Pay the cost at startup time of starting this singleton. + nsIInterfaceInfoManager* iim = + xptiInterfaceInfoManager::GetInterfaceInfoManagerNoAddRef(); + + if (CheckUpdateFile() || NS_FAILED( + nsComponentManagerImpl::gComponentManager->ReadPersistentRegistry())) { + // If the component registry is out of date, malformed, or incomplete, + // autoregister the default component directories. + (void) iim->AutoRegisterInterfaces(); + nsComponentManagerImpl::gComponentManager->AutoRegister(nsnull); + } + + // After autoreg, but before we actually instantiate any components, + // add any services listed in the "xpcom-directory-providers" category + // to the directory service. + nsDirectoryService::gService->RegisterCategoryProviders(); + + // Notify observers of xpcom autoregistration start + NS_CreateServicesFromCategory(NS_XPCOM_STARTUP_CATEGORY, + nsnull, + NS_XPCOM_STARTUP_OBSERVER_ID); + + return NS_OK; +} + + +// +// NS_ShutdownXPCOM() +// +// The shutdown sequence for xpcom would be +// +// - Notify "xpcom-shutdown" for modules to release primary (root) references +// - Shutdown XPCOM timers +// - Notify "xpcom-shutdown-threads" for thread joins +// - Shutdown the event queues +// - Release the Global Service Manager +// - Release all service instances held by the global service manager +// - Release the Global Service Manager itself +// - Release the Component Manager +// - Release all factories cached by the Component Manager +// - Notify module loaders to shut down +// - Unload Libraries +// - Release Contractid Cache held by Component Manager +// - Release dll abstraction held by Component Manager +// - Release the Registry held by Component Manager +// - Finally, release the component manager itself +// +EXPORT_XPCOM_API(nsresult) +NS_ShutdownXPCOM(nsIServiceManager* servMgr) +{ + return mozilla::ShutdownXPCOM(servMgr); +} + +namespace mozilla { + +nsresult +ShutdownXPCOM(nsIServiceManager* servMgr) +{ + NS_ENSURE_STATE(NS_IsMainThread()); + + nsresult rv; + nsCOMPtr moduleLoaders; + + // Notify observers of xpcom shutting down + { + // Block it so that the COMPtr will get deleted before we hit + // servicemanager shutdown + + nsCOMPtr thread = do_GetCurrentThread(); + NS_ENSURE_STATE(thread); + + nsRefPtr observerService; + CallGetService("@mozilla.org/observer-service;1", + (nsObserverService**) getter_AddRefs(observerService)); + + if (observerService) + { + (void) observerService-> + NotifyObservers(nsnull, NS_XPCOM_WILL_SHUTDOWN_OBSERVER_ID, + nsnull); + + nsCOMPtr mgr; + rv = NS_GetServiceManager(getter_AddRefs(mgr)); + if (NS_SUCCEEDED(rv)) + { + (void) observerService-> + NotifyObservers(mgr, NS_XPCOM_SHUTDOWN_OBSERVER_ID, + nsnull); + } + } + + NS_ProcessPendingEvents(thread); + + if (observerService) + (void) observerService-> + NotifyObservers(nsnull, NS_XPCOM_SHUTDOWN_THREADS_OBSERVER_ID, + nsnull); + + NS_ProcessPendingEvents(thread); + + // Shutdown the timer thread and all timers that might still be alive before + // shutting down the component manager + nsTimerImpl::Shutdown(); + + NS_ProcessPendingEvents(thread); + + // Shutdown all remaining threads. This method does not return until + // all threads created using the thread manager (with the exception of + // the main thread) have exited. + nsThreadManager::get()->Shutdown(); + + NS_ProcessPendingEvents(thread); + + // We save the "xpcom-shutdown-loaders" observers to notify after + // the observerservice is gone. + if (observerService) { + observerService-> + EnumerateObservers(NS_XPCOM_SHUTDOWN_LOADERS_OBSERVER_ID, + getter_AddRefs(moduleLoaders)); + + observerService->Shutdown(); + } + } + + // XPCOM is officially in shutdown mode NOW + // Set this only after the observers have been notified as this + // will cause servicemanager to become inaccessible. + gXPCOMShuttingDown = PR_TRUE; + +#ifdef DEBUG_dougt + fprintf(stderr, "* * * * XPCOM shutdown. Access will be denied * * * * \n"); +#endif + // We may have AddRef'd for the caller of NS_InitXPCOM, so release it + // here again: + NS_IF_RELEASE(servMgr); + + // Shutdown global servicemanager + if (nsComponentManagerImpl::gComponentManager) { + nsComponentManagerImpl::gComponentManager->FreeServices(); + } + + nsProxyObjectManager::Shutdown(); + + // Release the directory service + NS_IF_RELEASE(nsDirectoryService::gService); + + nsCycleCollector_shutdown(); + + if (moduleLoaders) { + PRBool more; + nsCOMPtr el; + while (NS_SUCCEEDED(moduleLoaders->HasMoreElements(&more)) && + more) { + moduleLoaders->GetNext(getter_AddRefs(el)); + + // Don't worry about weak-reference observers here: there is + // no reason for weak-ref observers to register for + // xpcom-shutdown-loaders + + nsCOMPtr obs(do_QueryInterface(el)); + if (obs) + (void) obs->Observe(nsnull, + NS_XPCOM_SHUTDOWN_LOADERS_OBSERVER_ID, + nsnull); + } + + moduleLoaders = nsnull; + } + + // Shutdown nsLocalFile string conversion + NS_ShutdownLocalFile(); +#ifdef XP_UNIX + NS_ShutdownNativeCharsetUtils(); +#endif + + // Shutdown xpcom. This will release all loaders and cause others holding + // a refcount to the component manager to release it. + if (nsComponentManagerImpl::gComponentManager) { + rv = (nsComponentManagerImpl::gComponentManager)->Shutdown(); + NS_ASSERTION(NS_SUCCEEDED(rv), "Component Manager shutdown failed."); + } else + NS_WARNING("Component Manager was never created ..."); + + // Release our own singletons + // Do this _after_ shutting down the component manager, because the + // JS component loader will use XPConnect to call nsIModule::canUnload, + // and that will spin up the InterfaceInfoManager again -- bad mojo + xptiInterfaceInfoManager::FreeInterfaceInfoManager(); + + // Finally, release the component manager last because it unloads the + // libraries: + if (nsComponentManagerImpl::gComponentManager) { + nsrefcnt cnt; + NS_RELEASE2(nsComponentManagerImpl::gComponentManager, cnt); + NS_ASSERTION(cnt == 0, "Component Manager being held past XPCOM shutdown."); + } + nsComponentManagerImpl::gComponentManager = nsnull; + +#ifdef DEBUG + // FIXME BUG 456272: this should disappear + _FreeAutoLockStatics(); +#endif + + ShutdownSpecialSystemDirectory(); + + NS_PurgeAtomTable(); + + NS_IF_RELEASE(gDebug); + + TimeStamp::Shutdown(); + + NS_LogTerm(); + +#ifdef MOZ_IPC + if (sIOThread) { + delete sIOThread; + sIOThread = nsnull; + } + if (sMessageLoop) { + delete sMessageLoop; + sMessageLoop = nsnull; + } + if (sCommandLineWasInitialized) { + CommandLine::Terminate(); + sCommandLineWasInitialized = false; + } + if (sExitManager) { + delete sExitManager; + sExitManager = nsnull; + } +#endif + +#ifdef GC_LEAK_DETECTOR + // Shutdown the Leak detector. + NS_ShutdownLeakDetector(); +#endif + + return NS_OK; +} + +} // namespace mozilla diff --git a/tests/cpp/proxytests.cpp b/tests/cpp/proxytests.cpp new file mode 100644 index 0000000..7f95dc1 --- /dev/null +++ b/tests/cpp/proxytests.cpp @@ -0,0 +1,558 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is mozilla.org code. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Pierre Phaneuf + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#include + +#include "nsXPCOM.h" +#include "nsXPCOMCIDInternal.h" +#include "nsIComponentManager.h" +#include "nsIComponentRegistrar.h" +#include "nsIServiceManager.h" +#include "nsAutoPtr.h" +#include "nsCOMPtr.h" +#include "nsCOMArray.h" + +#include "nscore.h" +#include "nspr.h" +#include "prmon.h" + +#include "nsITestProxy.h" + +#include "nsIRunnable.h" +#include "nsIProxyObjectManager.h" +#include "nsIThreadPool.h" +#include "nsXPCOMCIDInternal.h" +#include "nsComponentManagerUtils.h" +#include "nsServiceManagerUtils.h" +#include "nsThreadUtils.h" + +#include "prlog.h" +#ifdef PR_LOGGING +static PRLogModuleInfo *sLog = PR_NewLogModule("Test"); +#define LOG(args) PR_LOG(sLog, PR_LOG_DEBUG, args) +#else +#define LOG(args) printf args +#endif + +namespace proxytests { + +static nsresult +GetThreadFromPRThread(PRThread *prthread, nsIThread **result) +{ + LOG(("TEST: GetThreadFromPRThread [%p]\n", prthread)); + + nsCOMPtr tm = do_GetService(NS_THREADMANAGER_CONTRACTID); + NS_ENSURE_STATE(tm); + return tm->GetThreadFromPRThread(prthread, result); +} + +/***************************************************************************/ +/* nsTestXPCFoo */ +/***************************************************************************/ +class nsTestXPCFoo : public nsITestProxy +{ + NS_DECL_ISUPPORTS + NS_IMETHOD Test(PRInt32 p1, PRInt32 p2, PRInt32* retval); + NS_IMETHOD Test2(); + NS_IMETHOD Test3(nsISupports *p1, nsISupports **p2); + + nsTestXPCFoo(); +}; + +nsTestXPCFoo::nsTestXPCFoo() +{ + NS_ADDREF_THIS(); +} + +NS_IMPL_ISUPPORTS1(nsTestXPCFoo, nsITestProxy) + +NS_IMETHODIMP nsTestXPCFoo::Test(PRInt32 p1, PRInt32 p2, PRInt32* retval) +{ + LOG(("TEST: Thread (%d) Test Called successfully! Party on...\n", p1)); + *retval = p1+p2; + return NS_OK; +} + + +NS_IMETHODIMP nsTestXPCFoo::Test2() +{ + LOG(("TEST: The quick brown netscape jumped over the old lazy ie..\n")); + + return NS_OK; +} + +NS_IMETHODIMP nsTestXPCFoo::Test3(nsISupports *p1, nsISupports **p2) +{ + if (p1 != nsnull) + { + nsITestProxy *test; + + p1->QueryInterface(NS_GET_IID(nsITestProxy), (void**)&test); + + test->Test2(); + PRInt32 a; + test->Test( 1, 2, &a); + LOG(("TEST: \n1+2=%d\n",a)); + } + + + *p2 = new nsTestXPCFoo(); + return NS_OK; +} + +/***************************************************************************/ +/* nsTestXPCFoo2 */ +/***************************************************************************/ +class nsTestXPCFoo2 : public nsITestProxy +{ + NS_DECL_ISUPPORTS + NS_IMETHOD Test(PRInt32 p1, PRInt32 p2, PRInt32* retval); + NS_IMETHOD Test2(); + NS_IMETHOD Test3(nsISupports *p1, nsISupports **p2); + + nsTestXPCFoo2(); +}; + +nsTestXPCFoo2::nsTestXPCFoo2() +{ + NS_ADDREF_THIS(); +} + +NS_IMPL_THREADSAFE_ISUPPORTS1(nsTestXPCFoo2, nsITestProxy) + +NS_IMETHODIMP nsTestXPCFoo2::Test(PRInt32 p1, PRInt32 p2, PRInt32* retval) +{ + LOG(("TEST: calling back to caller!\n")); + + nsCOMPtr manager = + do_GetService(NS_XPCOMPROXY_CONTRACTID); + + LOG(("TEST: ProxyObjectManager: %p \n", (void *) manager.get())); + + PR_ASSERT(manager); + + nsCOMPtr thread; + GetThreadFromPRThread((PRThread *) p1, getter_AddRefs(thread)); + NS_ENSURE_STATE(thread); + + nsCOMPtr proxyObject; + manager->GetProxyForObject(thread, NS_GET_IID(nsITestProxy), this, NS_PROXY_SYNC, (void**)&proxyObject); + proxyObject->Test3(nsnull, nsnull); + + LOG(("TEST: Deleting Proxy Object\n")); + return NS_OK; +} + + +NS_IMETHODIMP nsTestXPCFoo2::Test2() +{ + LOG(("TEST: nsTestXPCFoo2::Test2() called\n")); + + return NS_OK; +} + + +NS_IMETHODIMP nsTestXPCFoo2::Test3(nsISupports *p1, nsISupports **p2) +{ + LOG(("TEST: Got called")); + return NS_OK; +} + + + +#if 0 +struct ArgsStruct { + nsIThread* thread; + PRInt32 threadNumber; +}; + + + +// This will create two objects both descendants of a single IID. +void TestCase_TwoClassesOneInterface(void *arg) +{ + ArgsStruct *argsStruct = (ArgsStruct*) arg; + + + nsCOMPtr manager = + do_GetService(NS_XPCOMPROXY_CONTRACTID); + + printf("ProxyObjectManager: %p \n", (void *) manager.get()); + + PR_ASSERT(manager); + + nsITestProxy *proxyObject; + nsITestProxy *proxyObject2; + + nsTestXPCFoo* foo = new nsTestXPCFoo(); + nsTestXPCFoo2* foo2 = new nsTestXPCFoo2(); + + PR_ASSERT(foo); + PR_ASSERT(foo2); + + + manager->GetProxyForObject(argsStruct->thread, NS_GET_IID(nsITestProxy), foo, NS_PROXY_SYNC, (void**)&proxyObject); + + manager->GetProxyForObject(argsStruct->thread, NS_GET_IID(nsITestProxy), foo2, NS_PROXY_SYNC, (void**)&proxyObject2); + + + + if (proxyObject && proxyObject2) + { + // release ownership of the real object. + + PRInt32 a; + nsresult rv; + PRInt32 threadNumber = argsStruct->threadNumber; + + printf("Deleting real Object (%d)\n", threadNumber); + NS_RELEASE(foo); + + printf("Deleting real Object 2 (%d)\n", threadNumber); + NS_RELEASE(foo2); + + + printf("Thread (%d) Prior to calling proxyObject->Test.\n", threadNumber); + rv = proxyObject->Test(threadNumber, 0, &a); + printf("Thread (%d) error: %d.\n", threadNumber, rv); + + + printf("Thread (%d) Prior to calling proxyObject->Test2.\n", threadNumber); + rv = proxyObject->Test2(); + printf("Thread (%d) error: %d.\n", threadNumber, rv); + + printf("Thread (%d) Prior to calling proxyObject2->Test2.\n", threadNumber); + rv = proxyObject2->Test2(); + printf("Thread (%d) proxyObject2 error: %d.\n", threadNumber, rv); + + printf("Deleting Proxy Object (%d)\n", threadNumber ); + NS_RELEASE(proxyObject); + + printf("Deleting Proxy Object 2 (%d)\n", threadNumber ); + NS_RELEASE(proxyObject2); + } + + PR_Sleep( PR_MillisecondsToInterval(1000) ); // If your thread goes away, your stack goes away. Only use ASYNC on calls that do not have out parameters +} +#endif + + + +void TestCase_NestedLoop(nsIThread *thread, PRInt32 index) +{ + nsCOMPtr manager = + do_GetService(NS_XPCOMPROXY_CONTRACTID); + + LOG(("TEST: ProxyObjectManager: %p\n", (void *) manager.get())); + + PR_ASSERT(manager); + + nsITestProxy *proxyObject; + nsTestXPCFoo2* foo = new nsTestXPCFoo2(); + + PR_ASSERT(foo); + + + manager->GetProxyForObject(thread, NS_GET_IID(nsITestProxy), foo, NS_PROXY_SYNC, (void**)&proxyObject); + + if (proxyObject) + { + // release ownership of the real object. + + nsresult rv; + + LOG(("TEST: Deleting real Object (%d)\n", index)); + NS_RELEASE(foo); + + PRInt32 retval; + + LOG(("TEST: Getting EventThread...\n")); + + //nsCOMPtr curThread = do_GetCurrentThread(); + PRThread *curThread = PR_GetCurrentThread(); + if (curThread) + { + LOG(("TEST: Thread (%d) Prior to calling proxyObject->Test.\n", index)); + rv = proxyObject->Test(NS_PTR_TO_INT32((void*)curThread), 0, &retval); // XXX broken on 64-bit arch + LOG(("TEST: Thread (%d) proxyObject error: %x.\n", index, rv)); + + LOG(("TEST: Deleting Proxy Object (%d)\n", index)); + NS_RELEASE(proxyObject); + } + + PR_Sleep( PR_MillisecondsToInterval(1000) ); // If your thread goes away, your stack goes away. Only use ASYNC on calls that do not have out parameters + } +} + + +#if 0 +void TestCase_nsISupports(void *arg) +{ + + ArgsStruct *argsStruct = (ArgsStruct*) arg; + + nsCOMPtr manager = + do_GetService(NS_XPCOMPROXY_CONTRACTID); + + PR_ASSERT(manager); + + nsITestProxy *proxyObject; + nsTestXPCFoo* foo = new nsTestXPCFoo(); + + PR_ASSERT(foo); + + manager->GetProxyForObject(argsStruct->thread, NS_GET_IID(nsITestProxy), foo, NS_PROXY_SYNC, (void**)&proxyObject); + + if (proxyObject != nsnull) + { + nsISupports *bISupports = nsnull, *cISupports = nsnull; + + proxyObject->Test3(foo, &bISupports); + proxyObject->Test3(bISupports, &cISupports); + + nsITestProxy *test; + bISupports->QueryInterface(NS_GET_IID(nsITestProxy), (void**)&test); + + test->Test2(); + + NS_RELEASE(foo); + NS_RELEASE(proxyObject); + } +} +#endif + +/***************************************************************************/ +/* ProxyTest */ +/***************************************************************************/ + +class ProxyTest : public nsIRunnable +{ +public: + NS_DECL_ISUPPORTS + + ProxyTest(PRThread *eventLoopThread, PRInt32 index) + : mEventLoopThread(eventLoopThread) + , mIndex(index) + {} + + NS_IMETHOD Run() + { + //TestCase_TwoClassesOneInterface(arg); + //TestCase_nsISupports(arg); + nsCOMPtr thread; + GetThreadFromPRThread(mEventLoopThread, getter_AddRefs(thread)); + TestCase_NestedLoop(thread, mIndex); + + return NS_OK; + } + +private: + PRThread *mEventLoopThread; + PRInt32 mIndex; +}; +NS_IMPL_THREADSAFE_ISUPPORTS1(ProxyTest, nsIRunnable) + +class TestSyncProxyToSelf : public nsIRunnable +{ +public: + NS_DECL_ISUPPORTS + + NS_IMETHOD Run() + { + LOG(("TEST: Verifing calling Proxy on eventQ thread.\n")); + + nsCOMPtr thread = do_GetCurrentThread(); + + nsITestProxy *proxyObject; + nsTestXPCFoo *foo = new nsTestXPCFoo(); + NS_ENSURE_STATE(foo); + + nsCOMPtr manager = + do_GetService(NS_XPCOMPROXY_CONTRACTID); + + manager->GetProxyForObject(thread, + NS_GET_IID(nsITestProxy), foo, + NS_PROXY_SYNC, (void**)&proxyObject); + + PRInt32 a; + proxyObject->Test(1, 2, &a); + proxyObject->Test2(); + + NS_RELEASE(proxyObject); + delete foo; + + LOG(("TEST: End of Verification calling Proxy on eventQ thread.\n")); + + return NS_OK; + } +}; +NS_IMPL_THREADSAFE_ISUPPORTS1(TestSyncProxyToSelf, nsIRunnable) + +//--------------------------------------------------------------------------- +// Test to make sure we can call methods on a "main thread only" object from +// a background thread. + +class MainThreadOnly : public nsIRunnable { +public: + NS_DECL_ISUPPORTS + NS_IMETHOD Run() { + NS_ASSERTION(NS_IsMainThread(), "method called on wrong thread"); + *mNumRuns -= 1; + return NS_OK; + } + MainThreadOnly(PRUint32 *numRuns) : mNumRuns(numRuns) {} + ~MainThreadOnly() { + NS_ASSERTION(NS_IsMainThread(), "method called on wrong thread"); + } + PRBool IsDone() { return mNumRuns == 0; } +private: + PRUint32 *mNumRuns; +}; +NS_IMPL_ISUPPORTS1(MainThreadOnly, nsIRunnable) // not threadsafe! + +static nsresult +RunApartmentTest() +{ + LOG(("RunApartmentTest: start\n")); + + const PRUint32 numDispatched = 160; + + PRUint32 numCompleted = 0; + nsCOMPtr obj = new MainThreadOnly(&numCompleted); + + nsCOMPtr manager = + do_GetService(NS_XPCOMPROXY_CONTRACTID); + + nsCOMPtr objProxy; + manager->GetProxyForObject(NS_PROXY_TO_CURRENT_THREAD, + NS_GET_IID(nsIRunnable), + obj, + NS_PROXY_ASYNC, + getter_AddRefs(objProxy)); + nsCOMPtr thread; + NS_NewThread(getter_AddRefs(thread)); + + obj = nsnull; + + nsCOMPtr pool = do_CreateInstance(NS_THREADPOOL_CONTRACTID); + + pool->SetThreadLimit(8); + for (PRUint32 i = 0; i < numDispatched; ++i) + pool->Dispatch(objProxy, NS_DISPATCH_NORMAL); + + objProxy = nsnull; + + nsCOMPtr curThread = do_GetCurrentThread(); + while (numCompleted < numDispatched) { + NS_ProcessNextEvent(curThread); + } + + pool->Shutdown(); + + LOG(("RunApartmentTest: end\n")); + return NS_OK; +} + +} // namespace + +using namespace proxytests; + +int +main(int argc, char **argv) +{ + int numberOfThreads = 1; + + if (argc > 1) + numberOfThreads = atoi(argv[1]); + + NS_InitXPCOM2(nsnull, nsnull, nsnull); + + // Scope code so everything is destroyed before we run call NS_ShutdownXPCOM + { + nsCOMPtr registrar; + NS_GetComponentRegistrar(getter_AddRefs(registrar)); + registrar->AutoRegister(nsnull); + + RunApartmentTest(); + + nsCOMPtr eventLoopThread; + NS_NewThread(getter_AddRefs(eventLoopThread)); + + nsCOMPtr test = new TestSyncProxyToSelf(); + eventLoopThread->Dispatch(test, NS_DISPATCH_NORMAL); + + PRThread *eventLoopPRThread; + eventLoopThread->GetPRThread(&eventLoopPRThread); + PR_ASSERT(eventLoopPRThread); + + LOG(("TEST: Spawn Threads:\n")); + nsCOMArray threads; + for (PRInt32 spawn = 0; spawn < numberOfThreads; spawn++) + { + test = new ProxyTest(eventLoopPRThread, spawn); + + nsCOMPtr thread; + NS_NewThread(getter_AddRefs(thread), test); + + threads.AppendObject(thread); + + LOG(("TEST: \tThread (%d) spawned\n", spawn)); + + PR_Sleep( PR_MillisecondsToInterval(250) ); + } + + LOG(("TEST: All Threads Spawned.\n")); + + LOG(("TEST: Wait for threads.\n")); + for (PRInt32 i = 0; i < numberOfThreads; i++) + { + LOG(("TEST: Thread (%d) Join...\n", i)); + nsresult rv = threads[i]->Shutdown(); + LOG(("TEST: Thread (%d) Joined. (error: %x).\n", i, rv)); + } + + LOG(("TEST: Shutting down event loop thread\n")); + eventLoopThread->Shutdown(); + } + + LOG(("TEST: Calling Cleanup.\n")); + NS_ShutdownXPCOM(nsnull); + + LOG(("TEST: Return zero.\n")); + return 0; +} diff --git a/tests/cpp/test.cpp b/tests/cpp/test.cpp new file mode 100644 index 0000000..c076e67 --- /dev/null +++ b/tests/cpp/test.cpp @@ -0,0 +1,33 @@ +namespace Parser +{ + namespace XXX + { + class Foobar + { + class Blah + { + class Bar + { + class Lalala + { + public: + Foobar(); + } + } + } + + }; + }; +}; + +void* +Parser::XXX::Foobar::wait(int i, const char const * const * p) +{ + return; +} + +void +Foobar::non_nil() +{ + return; +} diff --git a/tests/cpp/testsubclass.cpp b/tests/cpp/testsubclass.cpp new file mode 100644 index 0000000..4c5e9ae --- /dev/null +++ b/tests/cpp/testsubclass.cpp @@ -0,0 +1,12 @@ +int animal::moose::getFeet() //^2^ +{ + return fFeet; +} + +void animal::moose::doNothing() //^3^ +{ + animal::moose foo(); + + fFeet = N// -15- + ; // #15# ( "NAME1" "NAME2" "NAME3" ) +} diff --git a/tests/cs/ICoder.cs b/tests/cs/ICoder.cs new file mode 100644 index 0000000..875cb27 --- /dev/null +++ b/tests/cs/ICoder.cs @@ -0,0 +1,157 @@ +// ICoder.h + +using System; + +namespace SevenZip +{ + /// + /// The exception that is thrown when an error in input stream occurs during decoding. + /// + class DataErrorException : ApplicationException + { + public DataErrorException(): base("Data Error") { } + } + + /// + /// The exception that is thrown when the value of an argument is outside the allowable range. + /// + class InvalidParamException : ApplicationException + { + public InvalidParamException(): base("Invalid Parameter") { } + } + + public interface ICodeProgress + { + /// + /// Callback progress. + /// + /// + /// input size. -1 if unknown. + /// + /// + /// output size. -1 if unknown. + /// + void SetProgress(Int64 inSize, Int64 outSize); + }; + + public interface ICoder + { + /// + /// Codes streams. + /// + /// + /// input Stream. + /// + /// + /// output Stream. + /// + /// + /// input Size. -1 if unknown. + /// + /// + /// output Size. -1 if unknown. + /// + /// + /// callback progress reference. + /// + /// + /// if input stream is not valid + /// + void Code(System.IO.Stream inStream, System.IO.Stream outStream, + Int64 inSize, Int64 outSize, ICodeProgress progress); + }; + + /* + public interface ICoder2 + { + void Code(ISequentialInStream []inStreams, + const UInt64 []inSizes, + ISequentialOutStream []outStreams, + UInt64 []outSizes, + ICodeProgress progress); + }; + */ + + /// + /// Provides the fields that represent properties idenitifiers for compressing. + /// + public enum CoderPropID + { + /// + /// Specifies default property. + /// + DefaultProp = 0, + /// + /// Specifies size of dictionary. + /// + DictionarySize, + /// + /// Specifies size of memory for PPM*. + /// + UsedMemorySize, + /// + /// Specifies order for PPM methods. + /// + Order, + /// + /// Specifies Block Size. + /// + BlockSize, + /// + /// Specifies number of postion state bits for LZMA (0 <= x <= 4). + /// + PosStateBits, + /// + /// Specifies number of literal context bits for LZMA (0 <= x <= 8). + /// + LitContextBits, + /// + /// Specifies number of literal position bits for LZMA (0 <= x <= 4). + /// + LitPosBits, + /// + /// Specifies number of fast bytes for LZ*. + /// + NumFastBytes, + /// + /// Specifies match finder. LZMA: "BT2", "BT4" or "BT4B". + /// + MatchFinder, + /// + /// Specifies the number of match finder cyckes. + /// + MatchFinderCycles, + /// + /// Specifies number of passes. + /// + NumPasses, + /// + /// Specifies number of algorithm. + /// + Algorithm, + /// + /// Specifies the number of threads. + /// + NumThreads, + /// + /// Specifies mode with end marker. + /// + EndMarker + }; + + + public interface ISetCoderProperties + { + void SetCoderProperties(CoderPropID[] propIDs, object[] properties); + }; + + public interface IWriteCoderProperties + { + void WriteCoderProperties(System.IO.Stream outStream); + } + + public interface ISetDecoderProperties + { + void SetDecoderProperties(byte[] properties); + } +} diff --git a/tests/cs/RangeCoder.cs b/tests/cs/RangeCoder.cs new file mode 100644 index 0000000..4ced247 --- /dev/null +++ b/tests/cs/RangeCoder.cs @@ -0,0 +1,234 @@ +using System; + +namespace SevenZip.Compression.RangeCoder +{ + class Encoder + { + public const uint kTopValue = (1 << 24); + + System.IO.Stream Stream; + + public UInt64 Low; + public uint Range; + uint _cacheSize; + byte _cache; + + long StartPosition; + + public void SetStream(System.IO.Stream stream) + { + Stream = stream; + } + + public void ReleaseStream() + { + Stream = null; + } + + public void Init() + { + StartPosition = Stream.Position; + + Low = 0; + Range = 0xFFFFFFFF; + _cacheSize = 1; + _cache = 0; + } + + public void FlushData() + { + for (int i = 0; i < 5; i++) + ShiftLow(); + } + + public void FlushStream() + { + Stream.Flush(); + } + + public void CloseStream() + { + Stream.Close(); + } + + public void Encode(uint start, uint size, uint total) + { + Low += start * (Range /= total); + Range *= size; + while (Range < kTopValue) + { + Range <<= 8; + ShiftLow(); + } + } + + public void ShiftLow() + { + if ((uint)Low < (uint)0xFF000000 || (uint)(Low >> 32) == 1) + { + byte temp = _cache; + do + { + Stream.WriteByte((byte)(temp + (Low >> 32))); + temp = 0xFF; + } + while (--_cacheSize != 0); + _cache = (byte)(((uint)Low) >> 24); + } + _cacheSize++; + Low = ((uint)Low) << 8; + } + + public void EncodeDirectBits(uint v, int numTotalBits) + { + for (int i = numTotalBits - 1; i >= 0; i--) + { + Range >>= 1; + if (((v >> i) & 1) == 1) + Low += Range; + if (Range < kTopValue) + { + Range <<= 8; + ShiftLow(); + } + } + } + + public void EncodeBit(uint size0, int numTotalBits, uint symbol) + { + uint newBound = (Range >> numTotalBits) * size0; + if (symbol == 0) + Range = newBound; + else + { + Low += newBound; + Range -= newBound; + } + while (Range < kTopValue) + { + Range <<= 8; + ShiftLow(); + } + } + + public long GetProcessedSizeAdd() + { + return _cacheSize + + Stream.Position - StartPosition + 4; + // (long)Stream.GetProcessedSize(); + } + } + + class Decoder + { + public const uint kTopValue = (1 << 24); + public uint Range; + public uint Code; + // public Buffer.InBuffer Stream = new Buffer.InBuffer(1 << 16); + public System.IO.Stream Stream; + + public void Init(System.IO.Stream stream) + { + // Stream.Init(stream); + Stream = stream; + + Code = 0; + Range = 0xFFFFFFFF; + for (int i = 0; i < 5; i++) + Code = (Code << 8) | (byte)Stream.ReadByte(); + } + + public void ReleaseStream() + { + // Stream.ReleaseStream(); + Stream = null; + } + + public void CloseStream() + { + Stream.Close(); + } + + public void Normalize() + { + while (Range < kTopValue) + { + Code = (Code << 8) | (byte)Stream.ReadByte(); + Range <<= 8; + } + } + + public void Normalize2() + { + if (Range < kTopValue) + { + Code = (Code << 8) | (byte)Stream.ReadByte(); + Range <<= 8; + } + } + + public uint GetThreshold(uint total) + { + return Code / (Range /= total); + } + + public void Decode(uint start, uint size, uint total) + { + Code -= start * Range; + Range *= size; + Normalize(); + } + + public uint DecodeDirectBits(int numTotalBits) + { + uint range = Range; + uint code = Code; + uint result = 0; + for (int i = numTotalBits; i > 0; i--) + { + range >>= 1; + /* + result <<= 1; + if (code >= range) + { + code -= range; + result |= 1; + } + */ + uint t = (code - range) >> 31; + code -= range & (t - 1); + result = (result << 1) | (1 - t); + + if (range < kTopValue) + { + code = (code << 8) | (byte)Stream.ReadByte(); + range <<= 8; + } + } + Range = range; + Code = code; + return result; + } + + public uint DecodeBit(uint size0, int numTotalBits) + { + uint newBound = (Range >> numTotalBits) * size0; + uint symbol; + if (Code < newBound) + { + symbol = 0; + Range = newBound; + } + else + { + symbol = 1; + Code -= newBound; + Range -= newBound; + } + Normalize(); + return symbol; + } + + // ulong GetProcessedSize() {return Stream.GetProcessedSize(); } + } +} diff --git a/tests/cs/RangeCoderBitTree.cs b/tests/cs/RangeCoderBitTree.cs new file mode 100644 index 0000000..3309c14 --- /dev/null +++ b/tests/cs/RangeCoderBitTree.cs @@ -0,0 +1,157 @@ +using System; + +namespace SevenZip.Compression.RangeCoder +{ + struct BitTreeEncoder + { + BitEncoder[] Models; + int NumBitLevels; + + public BitTreeEncoder(int numBitLevels) + { + NumBitLevels = numBitLevels; + Models = new BitEncoder[1 << numBitLevels]; + } + + public void Init() + { + for (uint i = 1; i < (1 << NumBitLevels); i++) + Models[i].Init(); + } + + public void Encode(Encoder rangeEncoder, UInt32 symbol) + { + UInt32 m = 1; + for (int bitIndex = NumBitLevels; bitIndex > 0; ) + { + bitIndex--; + UInt32 bit = (symbol >> bitIndex) & 1; + Models[m].Encode(rangeEncoder, bit); + m = (m << 1) | bit; + } + } + + public void ReverseEncode(Encoder rangeEncoder, UInt32 symbol) + { + UInt32 m = 1; + for (UInt32 i = 0; i < NumBitLevels; i++) + { + UInt32 bit = symbol & 1; + Models[m].Encode(rangeEncoder, bit); + m = (m << 1) | bit; + symbol >>= 1; + } + } + + public UInt32 GetPrice(UInt32 symbol) + { + UInt32 price = 0; + UInt32 m = 1; + for (int bitIndex = NumBitLevels; bitIndex > 0; ) + { + bitIndex--; + UInt32 bit = (symbol >> bitIndex) & 1; + price += Models[m].GetPrice(bit); + m = (m << 1) + bit; + } + return price; + } + + public UInt32 ReverseGetPrice(UInt32 symbol) + { + UInt32 price = 0; + UInt32 m = 1; + for (int i = NumBitLevels; i > 0; i--) + { + UInt32 bit = symbol & 1; + symbol >>= 1; + price += Models[m].GetPrice(bit); + m = (m << 1) | bit; + } + return price; + } + + public static UInt32 ReverseGetPrice(BitEncoder[] Models, UInt32 startIndex, + int NumBitLevels, UInt32 symbol) + { + UInt32 price = 0; + UInt32 m = 1; + for (int i = NumBitLevels; i > 0; i--) + { + UInt32 bit = symbol & 1; + symbol >>= 1; + price += Models[startIndex + m].GetPrice(bit); + m = (m << 1) | bit; + } + return price; + } + + public static void ReverseEncode(BitEncoder[] Models, UInt32 startIndex, + Encoder rangeEncoder, int NumBitLevels, UInt32 symbol) + { + UInt32 m = 1; + for (int i = 0; i < NumBitLevels; i++) + { + UInt32 bit = symbol & 1; + Models[startIndex + m].Encode(rangeEncoder, bit); + m = (m << 1) | bit; + symbol >>= 1; + } + } + } + + struct BitTreeDecoder + { + BitDecoder[] Models; + int NumBitLevels; + + public BitTreeDecoder(int numBitLevels) + { + NumBitLevels = numBitLevels; + Models = new BitDecoder[1 << numBitLevels]; + } + + public void Init() + { + for (uint i = 1; i < (1 << NumBitLevels); i++) + Models[i].Init(); + } + + public uint Decode(RangeCoder.Decoder rangeDecoder) + { + uint m = 1; + for (int bitIndex = NumBitLevels; bitIndex > 0; bitIndex--) + m = (m << 1) + Models[m].Decode(rangeDecoder); + return m - ((uint)1 << NumBitLevels); + } + + public uint ReverseDecode(RangeCoder.Decoder rangeDecoder) + { + uint m = 1; + uint symbol = 0; + for (int bitIndex = 0; bitIndex < NumBitLevels; bitIndex++) + { + uint bit = Models[m].Decode(rangeDecoder); + m <<= 1; + m += bit; + symbol |= (bit << bitIndex); + } + return symbol; + } + + public static uint ReverseDecode(BitDecoder[] Models, UInt32 startIndex, + RangeCoder.Decoder rangeDecoder, int NumBitLevels) + { + uint m = 1; + uint symbol = 0; + for (int bitIndex = 0; bitIndex < NumBitLevels; bitIndex++) + { + uint bit = Models[startIndex + m].Decode(rangeDecoder); + m <<= 1; + m += bit; + symbol |= (bit << bitIndex); + } + return symbol; + } + } +} diff --git a/tests/eiffel/poly.e b/tests/eiffel/poly.e new file mode 100644 index 0000000..fc14381 --- /dev/null +++ b/tests/eiffel/poly.e @@ -0,0 +1,64 @@ +class + POINT +inherit + ANY + redefine + out + end +create + make, make_origin + +feature -- Initialization + + make (a_x, a_y: INTEGER) + -- Create with values `a_x' and `a_y' + do + set_x (a_x) + set_y (a_y) + ensure + x_set: x = a_x + y_set: y = a_y + end + + make_origin + -- Create at origin + do + ensure + x_set: x = 0 + y_set: y = 0 + end + +feature -- Access + + x: INTEGER assign set_x + -- Horizontal axis coordinate + + y: INTEGER assign set_y + -- Vertical axis coordinate + +feature -- Element change + + set_x (a_x: INTEGER) + -- Set `x' coordinate to `a_x' + do + x := a_x + ensure + x_set: x = a_x + end + + set_y (a_y: INTEGER) + -- Set `y' coordinate to `a_y' + do + y := a_y + ensure + y_set: y = a_y + end + +feature -- Output + + out: STRING + -- Display as string + do + Result := "Point: x = " + x.out + " y = " + y.out + end +end diff --git a/tests/erlang/examples-2.0/ebnf.ecc b/tests/erlang/examples-2.0/ebnf.ecc new file mode 100644 index 0000000..ba70aae --- /dev/null +++ b/tests/erlang/examples-2.0/ebnf.ecc @@ -0,0 +1,34 @@ +COMPILER ebnf. + +CHARACTERS + small = "abcdefghijklmnopqrstuvwxyz"; + big = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + alpha = small + big; + dig = "0123456789"; + blank = CHR(9) + CHR(10) + CHR(32); + noQuote = ANY - '"'. + +COMMENTS + FROM "(*" TO "*)" NESTED. + +TOKENS + Nonterminal = small {alpha | dig}; + Terminal = big {alpha | dig}; + White = blank {blank}; + String = '"' { noQuote } '"'. + +IGNORE + White + Comment. + +PRODUCTIONS + ebnf = {production} "."; + production = Nonterminal "=" expr ";" ; + expr = term {"|" term}; + term = factor {factor}; + factor = "{" expr "}" + | "[" expr "]" + | "(" expr ")" + | Nonterminal | Terminal | String. + +END ebnf. + diff --git a/tests/erlang/examples-2.0/ecc.xrl b/tests/erlang/examples-2.0/ecc.xrl new file mode 100644 index 0000000..5f5c2d6 --- /dev/null +++ b/tests/erlang/examples-2.0/ecc.xrl @@ -0,0 +1,117 @@ +Definitions. + +Dig = [0-9] +Big = [A-Z] +Small = [a-z] +WS = [\000-\s] + +COMMENT = \(\*\(*([^*)]|[^*]\)|\*[^)])*\**\*\) +STRING = "(\\\^.|\\.|[^"])*" +QUOTE = '(\\\^.|\\.|[^'])*' + +Rules. + +({Small}({Small}|{Big}|{Dig}|_)*) : {token, {atom,YYline, YYtext}}. + +({Big}({Small}|{Big}|{Dig}|_)*) : {token, special(YYtext, YYline)}. + +({Dig}{Dig}*) : {token, {integer, YYline, list_to_integer(YYtext)}}. + +%% string + +{STRING} : %% Strip quotes. + S = lists:sublist(YYtext, 2, length(YYtext) - 2), + {token,{string,YYline,string_gen(S)}}. + +{QUOTE} : %% Strip quotes. + S = lists:sublist(YYtext, 2, length(YYtext) - 2), + {token,{quote,YYline,string_gen(S)}}. + + +{COMMENT} : . + + +%%--------------------------------------------------------- +%% Ignore stuff +%%--------------------------------------------------------- +%% "{WHITE}". %% whitespace +%% "#.*". %% Ignore Macro stuff for now +%% "{COMMENT}". %% Ignore Comments + +%% C comments are /* ... */ +%% Our comments are (* ... *) {we have to quote ( and * yuck +%% i.e. write \* and \( } +%% + +%% COMMENT "/\\*/*([^*/]|[^*]/|\\*[^/])*\\**\\*/". (tobbe) +%% COMMENT "(\\*/*([^*)]|[^*])|\\*[^)])*\\**\\*)". (modified) +%% COMMENT "\(\\*/*([^*\)]|[^*]\)|\\*[^\)])*\\**\\*\)". (quoted) + += : {token, {'=', YYline}}. +\+ : {token, {'+', YYline}}. +\- : {token, {'-', YYline}}. +\; : {token, {';', YYline}}. +} : {token, {'}', YYline}}. +{ : {token, {'{', YYline}}. +\[ : {token, {'[', YYline}}. +\] : {token, {']', YYline}}. +\( : {token, {'(', YYline}}. +\) : {token, {')', YYline}}. +\| : {token, {'|', YYline}}. +\: : {token, {':', YYline}}. + +(.|\n) : skip_token. + +\.[\s\t\n] : {end_token,{'$end', YYline}}. + +Erlang code. + +string_gen([$\\|Cs]) -> + string_escape(Cs); +string_gen([C|Cs]) -> + [C|string_gen(Cs)]; +string_gen([]) -> []. + +string_escape([O1,O2,O3|S]) when + O1 >= $0, O1 =< $7, O2 >= $0, O2 =< $7, O3 >= $0, O3 =< $7 -> + [(O1*8 + O2)*8 + O3 - 73*$0|string_gen(S)]; +string_escape([$^,C|Cs]) -> + [C band 31|string_gen(Cs)]; +string_escape([C|Cs]) when C >= 0, C =< $ -> + string_gen(Cs); +string_escape([C|Cs]) -> + [escape_char(C)|string_gen(Cs)]. + +escape_char($n) -> $\n; %\n = LF +escape_char($r) -> $\r; %\r = CR +escape_char($t) -> $\t; %\t = TAB +escape_char($v) -> $\v; %\v = VT +escape_char($b) -> $\b; %\b = BS +escape_char($f) -> $\f; %\f = FF +escape_char($e) -> $\e; %\e = ESC +escape_char($s) -> $ ; %\s = SPC +escape_char($d) -> $\d; %\d = DEL +escape_char(C) -> C. + +remove_brackets([_,_|T]) -> + [_,_|T1] = lists:reverse(T), + lists:reverse(T1). + +special("COMPILER", Line) -> {'COMPILER', Line}; +special("CHARACTERS", Line) -> {'CHARACTERS', Line}; +special("COMMENTS", Line) -> {'COMMENTS', Line}; +special("FROM", Line) -> {'FROM', Line}; +special("TO", Line) -> {'TO', Line}; +special("TOKENS", Line) -> {'TOKENS', Line}; +special("IGNORE", Line) -> {'IGNORE', Line}; +special("PRODUCTIONS", Line) -> {'PRODUCTIONS', Line}; +special("END", Line) -> {'END', Line}; +special("NESTED", Line) -> {'NESTED', Line}; +special("EOL", Line) -> {'EOL', Line}; +special("CHR", Line) -> {'CHR', Line}; +special("ANY", Line) -> {'ANY', Line}; +special(Other, Line) -> {var, Line, Other}. + + + + diff --git a/tests/erlang/examples-2.0/ecc.yrl b/tests/erlang/examples-2.0/ecc.yrl new file mode 100644 index 0000000..ad03c44 --- /dev/null +++ b/tests/erlang/examples-2.0/ecc.yrl @@ -0,0 +1,105 @@ +Nonterminals + +production +form lhs factor +nested syntax char_prods charline char_rhs char_prim +ignore moreignore expr term. + +Terminals +atom var string quote '|' '=' '}' '{' '(' ')' '[' ']' +'COMPILER' 'CHARACTERS' 'COMMENTS' 'FROM' 'TO' 'TOKENS' +'IGNORE' 'PRODUCTIONS' 'END' 'NESTED' 'EOL' 'CHR' 'ANY' integer comment +'+' '-' ';'. + + +Rootsymbol form. + +form -> 'COMPILER' atom : {compiler, unwrap('$2')}. +form -> 'CHARACTERS' char_prods : {characters, '$2'}. +form -> 'COMMENTS' 'FROM' string + 'TO' string nested : {comments,unwrap('$3'),unwrap('$5'), + '$6'}. +form -> 'TOKENS' syntax : {tokens, '$2'}. +form -> 'IGNORE' ignore : {ignore, '$2'}. +form -> 'PRODUCTIONS' syntax : {syntax, '$2'}. +form -> 'END' atom : {theend, '$2'}. +form -> comment. + +nested -> 'NESTED' : nested. +nested -> 'EOL' : eol. +nested -> '$empty' : not_nested. + +%% Character syntax + +char_prods -> charline ';' char_prods : ['$1'|'$3']. +char_prods -> charline : ['$1']. + +charline -> atom '=' char_rhs : {unwrap('$1'), '$3'}. + +char_rhs -> char_prim '+' char_rhs : {plus, '$1', '$3'}. +char_rhs -> char_prim '-' char_rhs : {minus, '$1', '$3'}. +char_rhs -> char_prim : '$1'. + +char_prim -> 'CHR' '(' integer ')' : {chr, unwrap('$3')}. +char_prim -> string : {string, unwrap('$1')}. +char_prim -> quote : {string, unwrap('$1')}. +char_prim -> atom : {atom, unwrap('$1')}. +char_prim -> 'ANY' : any. + +ignore -> var moreignore : [unwrap('$1')|'$2']. + +moreignore -> '+' ignore : '$2'. +moreignore -> '$empty' : []. + +%% The following deifinitions are taken from [WIR82] +%% WIR82 Programming in Modular2 +%% Springer Verlag 1982 + +%% statement : A syntactic form +%% expression : A list of alternatives +%% term : A concatination of factors +%% factor : A single syntactoc entity or a parenthesized expression + +%% Construct +%% ========= +%% [ A ] = zero or more A's +%% { A } = any number of A's +%% "A" = a string parse tree +%% A | B = A or B parse tree +%% A B = sequence of A followed by B +%% identifier = a name + +%% syntax = {production} +%% production = id "=" expr ";" +%% expr = term {"|" term} +%% term = factor {factor} +%% factor = id | string "{" expr "} + +syntax -> production ';' syntax : ['$1'|'$3']. +syntax -> production : ['$1']. + +production -> lhs '=' expr : {prod, '$1', '$3'}. + +lhs -> var : unwrap('$1'). +lhs -> atom : unwrap('$1'). + +expr -> term : '$1'. +expr -> term '|' expr : {alt, '$1', '$3'}. + +term -> factor : '$1'. +term -> factor term : {seq, '$1', '$2'}. + +factor -> atom : {nt, unwrap('$1')}. +factor -> var : {ta, unwrap('$1')}. +factor -> string : {ts, unwrap('$1')}. +factor -> quote : {tq, unwrap('$1')}. +factor -> '[' expr ']' : {one, '$2'}. +factor -> '{' expr '}' : {star, '$2'}. +factor -> '(' expr ')' : {bracket, '$2'}. + +Erlang code. + +unwrap({_,_,V}) -> V. + +simplify({Tag,A,nil}) -> A; +simplify(X) -> X. diff --git a/tests/erlang/examples-2.0/ecc_parse.erl b/tests/erlang/examples-2.0/ecc_parse.erl new file mode 100644 index 0000000..dae192e --- /dev/null +++ b/tests/erlang/examples-2.0/ecc_parse.erl @@ -0,0 +1,58 @@ +-module(ecc_parse). + +-doc([{author, 'Joe Armstrong'}, + {title, "Parser for the ecc language."}, + {keywords,[ecc,parser,yecc,leex]}, + {date, 891106}]). + +-export([make/0, file/1]). + +%% usage +%% ecc_parse:file(File) +%% Converts File.ebnf -> File.xbin +%% ecc_parse:make() +%% Makes the parser + +make() -> + %% The parser is made from + %% ecc.yrl and ecc.xrl + yecc:yecc("ecc", "ecc_yecc"), + c:c(ecc_yecc), + leex:gen(ecc, ecc_lex), + c:c(ecc_lex). + +file(F) -> + io:format("Parsing ~s.ecc~n", [F]), + {ok, Stream} = file:open(F ++ ".ecc", read), + Parse = handle(Stream, 1, [], 0), + file:close(Stream), + Parse. + +handle(Stream, LineNo, L, NErrors) -> + handle1(io:requests(Stream, [{get_until,foo,ecc_lex, + tokens,[LineNo]}]), Stream, L, NErrors). + +handle1({ok, Toks, Next}, Stream, L, Nerrs) -> + case ecc_yecc:parse(Toks) of + {ok, Parse} -> + handle(Stream, Next, [Parse|L], Nerrs); + {error, {Line, Mod, What}} -> + Str = apply(Mod, format_error, [What]), + io:format("** ~w ~s~n", [Line, Str]), + handle(Stream, Next, L, Nerrs+1); + Other -> + io:format("Bad_parse:~p\n", [Other]), + handle(Stream, Next, L, Nerrs+1) + end; +handle1({eof, _}, Stream, L, 0) -> + {ok, lists:reverse(L)}; +handle1({eof, _}, Stream, L, N) -> + {error, N}; +handle1(What, Stream, L, Nerrs) -> + io:format("Here:~p\n", [What]), + handle(Stream, 1, L, Nerrs+1). + +first([H]) -> []; +first([H|T]) -> [H|first(T)]; +first([]) -> []. + diff --git a/tests/erlang/examples-2.0/ermake.erl b/tests/erlang/examples-2.0/ermake.erl new file mode 100644 index 0000000..a8ffd21 --- /dev/null +++ b/tests/erlang/examples-2.0/ermake.erl @@ -0,0 +1,236 @@ +-module(ermake). + +-doc([{author,'Joe Armstrong'}, + {title,"Erlang make utility."}, + {keywords, [make]}, + {date,981103}]). + +-export([all/0, target/1, file/1, file/2]). + +-import(lists, [delete/2, filter/2, foldl/3, map/2, max/1, member/2, zf/2]). + +%% all() -> Makes first target in EMakefile +%% target(target::string()) -> Makes Target in EMakefile +%% file(file()) -> Makes first target in File +%% file(file(), target::string()) -> Makes Target in File + +all() -> file("EMakefile"). + +target(T) -> file("EMakefile", T). + +file(File) -> make(File, top). + +file(File, Target) -> make(File, {target, Target}). + +make(File, Target) -> + Lines = ermake_parse:parse(File), + Rules = filter(fun(X) -> element(1, X) == make end, Lines), + Suffix = filter(fun(X) -> element(1, X) == suffix end, Lines), + Rules1 = add_extra_rules(Rules, Suffix), + case Target of + top -> + case hd(Rules) of + {make, Ts, _, _} -> + make_everything(Ts, Rules1); + _ -> + nothing_to_do + end; + {target, T} -> + make_everything([T], Rules1) + end. + +add_extra_rules(Rules, Suffix) -> + %% If a dependent is mentioned and + %% there is no explicit rule for how to make the dependent then + %% add an extra rule if possible + Targets = [T || {make,Ts,_,_} <- Rules, T <- Ts], + Dependents = [D || {make, _, Ds, _} <- Rules, D <- Ds], + Missing = filter(fun(I) -> not member(I, Targets) end, Dependents), + Missing1 = remove_duplicates(Missing), + Extra = zf(fun(I) -> find_suffix_rule(I, Suffix) end, Missing1), + Rules ++ Extra. + +make_everything(Targets, Rules) -> + Ps = [{T, D} || {make, Ts, Ds, _} <- Rules, T <- Ts, D <- Ds], + %% trace all the rules we can reach from the root set + L0 = transitive:closure(Targets, Ps), + L = delete(true, L0), + %% keep those rules that are mentioned in targets or destinations + Ps1 = filter(fun({D,T}) -> + member(D, L) or member(T, L) + end, Ps), + %% reverse the order to build the bottom up tree + Ps2 = map(fun({I,J}) -> {J, I} end, Ps1), + %% order the result + case topological_sort:sort(Ps2) of + {ok, Order0} -> + Order = delete(true, Order0), + %% Order is the absolute order to build things + Cmds = map(fun(I) -> select_rule(I, Rules) end, Order), + foldl(fun do_cmd/2, [], Cmds), + true; + {cycle, Cycle} -> + exit({makefile,contains,cycle,Cycle}) + end. + +%% find which rule is needed to build Target + +select_rule(Target, Rules) -> + Matches = [{make, Ts,Ds,Fun}|| {make,Ts,Ds,Fun} <- Rules, + member(Target, Ts)], + case length(Matches) of + 0 -> {file, Target}; + 1 -> hd(Matches); + _ -> exit({multiple,rules,to,make,Target}) + end. + +%% do_cmd(cmd(), made()) -> make()' +%% cmd() = {make, Targets, Dependents, Fun} | {file, Target} +%% made() = [Target, time()]. + +do_cmd({make, Bins, Srcs, Fun}, Made) -> + case target_time(Bins, Made) of + none -> + eval(Bins, Fun); + {missing, M} -> + eval(Bins, Fun); + {max, TBin} -> + case target_time(Srcs, Made) of + {missing, M} -> + exit({'I don\'t know how to make',M}); + {max, TSrc} when TSrc > TBin -> + eval(Bins, Fun); + {max, _} -> + true; + none -> + exit({no,src,Srcs}) + end + end, + update_times(Srcs ++ Bins, this_time(), Made); +do_cmd({file,H}, Made) -> + update_times([H], this_time(), Made). + +%% target_time(Targets, Made) -> {max, Time} | {missing,M} +%% none +%% if no targets found +%% {missing, M} +%% if target M is missing +%% {max, Time} +%% Time is the last modified time of all the targets +%% the limes are derived from either the Made list +%% or from the time stamp of the file. + +target_time(Targets, Made) -> + target_time(Targets, Made, []). + +target_time([H|T], Made, Times) -> + case make_time(H, Made) of + {yes, Time} -> + target_time(T, Made, [Time|Times]); + no -> + case is_file(H) of + true -> + target_time(T, Made, [last_modified(H)|Times]); + false -> + {missing, H} + end + end; +target_time([], Made, []) -> + none; +target_time([], Made, Times) -> + {max, max(Times)}. + +make_time(X, [{X,Time}|_]) -> {yes, Time}; +make_time(X, [_|T]) -> make_time(X, T); +make_time(X, []) -> no. + +update_times([H|T], Now, Made) -> + case make_time(H, Made) of + {yes, _} -> update_times(T, Now, Made); + no -> + case is_file(H) of + true -> + update_times(T, Now, [{H, last_modified(H)}|Made]); + false -> + update_times(T, Now, [{H, Now}|Made]) + end + end; +update_times([], _, Made) -> + Made. + +%% see if a suffix rule can be applied to the file D + +find_suffix_rule(D, Suffix) -> + Ext = filename:extension(D), + find_suffix_rule(Ext, D, Suffix). + +find_suffix_rule(To, D, [{suffix, [From, To], Fun}|_]) -> + Root = filename:rootname(D), + Fun1 = expand_cmd(Fun, Root), + {true, {make, [D], [Root ++ From], Fun1}}; +find_suffix_rule(To, D, [_|T]) -> + find_suffix_rule(To, D, T); +find_suffix_rule(_, _, []) -> + false. + +expand_cmd([$$,$>|T], Root) -> + Root ++ expand_cmd(T, Root); +expand_cmd([H|T], Root) -> + [H|expand_cmd(T, Root)]; +expand_cmd([], _) -> + []. + +eval(_, []) -> + true; +eval(Target, Str) -> + io:format("make ~p ->~n~s~n",[Target, Str]), + case erl_scan:tokens([], "fun() -> " ++ Str ++ " end. ", 1) of + {done, {ok, Toks, _},_} -> + case erl_parse:parse_exprs(Toks) of + {ok, [Parse]} -> + %% io:format("Parse = ~p~n",[Parse]), + Env0 = erl_eval:new_bindings(), + Call = [{call,9999,Parse,[]}], + case erl_eval:exprs(Call, Env0) of + {value, Val, _} -> + Val; + O3 -> + exit({eval,error,O3}) + end; + O1 -> + exit({parse,error,o1,O1}) + end; + O2 -> + exit({tokenisation,error,O2}) + end. + +%% Stuff that should have been in the libraries (sigh :-) + +last_modified(F) -> + case file:file_info(F) of + {ok, {_, _, _, _, Time, _, _}} -> + Time; + _ -> + exit({last_modified, F}) + end. + +is_file(File) -> + case file:file_info(File) of + {ok, _} -> + true; + _ -> + false + end. + +remove_duplicates(L) -> + foldl(fun(I, Acc) -> + case member(I, Acc) of + true -> Acc; + false -> [I|Acc] + end + end, [], L). + +this_time() -> + {Y,M,D} = date(), + {H,Min,S} = time(), + {Y,M,D,H,Min,S}. diff --git a/tests/erlang/examples-2.0/ermake_line_reader.erl b/tests/erlang/examples-2.0/ermake_line_reader.erl new file mode 100644 index 0000000..3473815 --- /dev/null +++ b/tests/erlang/examples-2.0/ermake_line_reader.erl @@ -0,0 +1,246 @@ +-module(ermake_line_reader). + +-doc([{author,'Joe Armstrong'}, + {title,"Provide character level input for utilities such as make, lex, yacc etc. This module reads lines up to dot whitespace. Include files and named macros are expanded in place."}, + {keywords, [read,line,make,dot,whitespace,forms]}, + {date,981028}]). + +%% This module provides a common *character level input* +%% For Erlang look-alike utilities such as make, yecc, lex +%% It provides +%% 1) Multiple line input terminated by dot white space +%% 2) Variables VAR = Val, or VAR += Val +%% 3) Include files include("File") +%% 4) comment stripping %... are removed + +-export([test/1, read_file/1, read_file/2]). + +-import(lists, [keyreplace/4, keysearch/3, member/2, reverse/1, reverse/2]). + +test(1) -> read_file("EMakefile"); +test(2) -> read_file("test1"). + +read_file(File) -> + read_file(File, []). + +read_file(File, Macros) -> + {Macros1, Lines} = read_lines(File, Macros, [File]), + %% io:format("Macros were:~p~n",[Macros1]), + trim(Lines). + +trim([{{File,Line},Str}|T]) -> + Leading = count_leading_nls(Str, Line), + case trim_line(Str) of + [] -> trim(T); + Str1 -> [{File,Leading,Str1}|trim(T)] + end; +trim([]) -> []. + +trim_line(Str) -> + Str1 = skip_white(Str), + trim_end_of_line(Str1). + +trim_end_of_line(Str1) -> + case reverse(Str1) of + [X,$.|Tmp] -> + reverse(Tmp); + [] -> + []; + Other -> + exit({oops,Other}) + end. + +read_lines(File, Macros0, Stack) -> + case file:read_file(File) of + {ok, Bin} -> + Lines = gather_lines(binary_to_list(Bin), File, 1, []), + %% io:format("Lines=~p~n",[Lines]), + expand(Lines, Macros0, Stack, []); + _ -> + exit({cannot,read,file,File}) + end. + +expand([{Where, H}|T], Macros, Stack, L) -> + %% first expand any macros + H1 = expand_macro(H, Macros, Where), + %% now add any macro definitions + case is_macro_defn(H1) of + {new, Var, Val} -> + case keysearch(Var,1,Macros) of + {value,{_,Val}} -> + %% same value no problem + expand(T, Macros, Stack, L); + {value,{_,Replacement}} -> + %% some other value + exit({error, Where, cannot,redefine,macro, + Var,was,Replacement,is,Val}); + false -> + %% new definition + expand(T, [{Var,Val}|Macros], Stack, L) + end; + {plus, Var, Val} -> + case keysearch(Var,1,Macros) of + {value,{_,Old}} -> + %% some other value + Macros1 = keyreplace(Var,1,Macros,{Var,Old++Val}), + expand(T, Macros1, Stack, L); + false -> + exit({error, Where, no,previous,defn,for,Var}) + end; + no -> + case is_include(H1, Where) of + {yes, File} -> + case member(File, Stack) of + true -> + exit({error, Where, recursive_include, File}); + false -> + {Macros1, Lines1} = read_lines(File,Macros,Stack), + expand(T, Macros1, Stack, reverse(Lines1, L)) + end; + no -> + expand(T, Macros, Stack, [{Where, H1}|L]) + end + end; +expand([], Macros, Stack, L) -> + {Macros, reverse(L)}. + +expand_macro([$$,$(|T], Macros, Where) -> + case is_var(T) of + {yes, Var, [$)|T1]} -> + case keysearch(Var,1,Macros) of + {value,{_,Replacement}} -> + Replacement ++ expand_macro(T1, Macros, Where); + false -> + exit({error,Where,undefined,macro,Var}) + end; + no -> + [$$,$(|expand_macro(T, Macros, Where)] + end; +expand_macro([H|T], Macros, Where) -> + [H|expand_macro(T, Macros, Where)]; +expand_macro([], Macros, _) -> + []. + +is_include(Line, Where) -> + case skip_white(Line) of + [$i,$n,$c,$l,$u,$d,$e,$(,$"|T] -> + {File, T1} = get_quoted([$"|T], Where), + case skip_white(T1) of + [$)|_] -> + {yes, File}; + _ -> + exit({Where,bad,include,syntax}) + end; + _ -> + no + end. + +is_macro_defn(Line) -> + Str1 = skip_white(Line), + case is_var(Str1) of + {yes, Var, Str2} -> + case skip_white(Str2) of + [$=|T] -> + {new, Var, trim_end_of_line(T)}; + [$+,$=|T] -> + {plus, Var, trim_end_of_line(T)}; + _ -> + no + end; + no -> + no + end. + +is_var([H|T]) when $A =< H, H =< $Z -> + collect_var(T, [H]); +is_var(_) -> + no. + +collect_var([H|T], L) when $A =< H, H =< $Z -> + collect_var(T, [H|L]); +collect_var([H|T], L) when $1 =< H, H =< $9 -> + collect_var(T, [H|L]); +collect_var([H|T], L) when $a =< H, H =< $z -> + collect_var(T, [H|L]); +collect_var(X, L) -> + {yes, reverse(L), X}. + +skip_white([$ |T]) -> skip_white(T); +skip_white([$\n|T]) -> skip_white(T); +skip_white([$\t|T]) -> skip_white(T); +skip_white(T) -> T. + +gather_lines([], File, N, L) -> + reverse(L); +gather_lines(Str, File, N, L) -> + {Line, Str1} = get_line(Str, {File, N}, []), + Width = count_nls(Line, 0), + gather_lines(Str1, File, N + Width, [{{File,N},Line}|L]). + +count_nls([$\n|T], N) -> count_nls(T, N+1); +count_nls([_|T], N) -> count_nls(T, N); +count_nls([], N) -> N. + +count_leading_nls([$\n|T], N) -> count_leading_nls(T, N+1); +count_leading_nls(_, N) -> N. + +%% get_line collects a line up to . + +get_line([$.,X|T], Where, L) -> + case X of + $\n -> + {reverse(L, [$.,$\n]), T}; + $ -> + {reverse(L, [". "]), T}; + $\t -> + {reverse(L, [$.,$\t]), T}; + _ -> + get_line(T, Where, [X,$.|L]) + end; +get_line([$"|T], Where, L) -> + {Str, T1} = get_quoted([$"|T], Where), + get_line(T1, Where, [$"|reverse(Str, [$"|L])]); +get_line([$'|T], Where, L) -> + {Str, T1} = get_quoted([$'|T], Where), + get_line(T1, Where,[$'|reverse(Str, [$'|L])]); +get_line([$%|T], Where, L) -> + %% remove the comment + T1 = skip_to_eol(T), + get_line(T1, Where, L); +get_line([H|T], Where, L) -> + get_line(T, Where, [H|L]); +get_line([], Where, L) -> + {reverse(L), []}. + +skip_to_eol([$\n|T]) -> [$\n|T]; +skip_to_eol([_|T]) -> skip_to_eol(T); +skip_to_eol([]) -> []. + + +%% get_quoted(string(), {file(),line()}) -> {quoted(), rest()} +%% The " ' is not included + +get_quoted([End|T], Where) -> + get_quoted(T, Where, End, []). + +get_quoted([End|T], Where, End, Acc) -> + {reverse(Acc), T}; +get_quoted([$\\,C|T], Where, End, Acc) -> + get_quoted(T, Where, End, [quoted(C)|Acc]); +get_quoted([$\n|_], {File,Line}, _, _) -> + exit({error, file, File, line, Line, + "newline not allowed in string"}); +get_quoted([H|T], Where, End, Acc) -> + get_quoted(T, Where, End, [H|Acc]); +get_quoted([], {File,Line}, _, _) -> + exit({error, file, File, line, Line, + "end of line not allowed in string"}). + +%% Quoted characters + +quoted($n) -> $\n; +quoted($t) -> $\t; +quoted($r) -> $\r; +quoted($b) -> $\b; +quoted($v) -> $\v; +quoted(C) -> C. diff --git a/tests/erlang/examples-2.0/ermake_parse.erl b/tests/erlang/examples-2.0/ermake_parse.erl new file mode 100644 index 0000000..32698de --- /dev/null +++ b/tests/erlang/examples-2.0/ermake_parse.erl @@ -0,0 +1,73 @@ +-module(ermake_parse). + +-doc([{author,'Joe Armstrong'}, + {title,"Parser used by ermake."}, + {keywords, [parser,make]}, + {date,981029}]). + +-export([parse/1]). + +-import(lists, [reverse/1, prefix/2, map/2]). + +parse(File) -> + Dir = filename:dirname(code:which(?MODULE)), + Lines = ermake_line_reader:read_file(File, [{"MAKEDIR", Dir}]), + map(fun parse_line/1, Lines). + +parse_line({File,Line,Str}) -> + parse_str(Str). + +parse_str([$S,$u,$f,$f,$i,$x,$ |T]) -> + %% io:format("Suffix:~s~n",[T]), + case split("->", T) of + {yes, Pre, Fun} -> + Tmp = string:tokens(Pre,". "), + Tmp1 = map(fun(I) -> [$.|I] end, Tmp), + {suffix, Tmp1, Fun}; + no -> + exit({'No -> in suffix rule', T}) + end; +parse_str(Str) -> + case split("when", Str) of + {yes, As, BsF} -> + case split("->", BsF) of + %% As when Bs -> F + {yes, Bs, F} -> + {make, parse_files(As), parse_files(Bs), + parse_fun(F)}; + no -> + %% As when Bs. + {make, parse_files(As), parse_files(BsF), []} + end; + no -> + %% A -> F + case split("->", Str) of + no -> + exit({'No "when" or "->" in rule', Str}); + {yes, As, F} -> + {make, parse_files(As), [true], parse_fun(F)} + end + end. + +%% split(Prefix, String) -> {yes, Before, After} | no +%% splits String at Prefix + +split(Prefix, L) -> split(Prefix, L, []). + +split(_, [], L) -> + no; +split(Prefix, L, L1) -> + case prefix(Prefix, L) of + true -> + {yes, reverse(L1), string:substr(L, length(Prefix)+1)}; + false -> + split(Prefix, tl(L), [hd(L)|L1]) + end. + +parse_files(Str) -> + Files = string:tokens(Str, [$ ,$,,$\n,$\t]). + +parse_fun([$\n|F]) -> F; +parse_fun(F) -> F. + + diff --git a/tests/erlang/examples-2.0/error_handler.erl b/tests/erlang/examples-2.0/error_handler.erl new file mode 100644 index 0000000..95ac11c --- /dev/null +++ b/tests/erlang/examples-2.0/error_handler.erl @@ -0,0 +1,30 @@ +-module(error_handler). + +-doc([{author,joe}, + {title,"Special version of error handler used by sos.erl"}, + {date,981012}]). + +-export([undefined_function/3,undefined_global_name/2]). + +undefined_function(sos, F, A) -> + erlang:display({error_handler,undefined_function, + sos,F,A}), + exit(oops); +undefined_function(M, F, A) -> + case sos:load_module(M) of + {ok, M} -> + case erlang:function_exported(M,F,length(A)) of + true -> + apply(M, F, A); + false -> + sos:stop_system({undef,{M,F,A}}) + end; + {ok, Other} -> + sos:stop_system({undef,{M,F,A}}); + already_loaded -> + sos:stop_system({undef,{M,F,A}}); + {error, What} -> + sos:stop_system({load,error,What}) + end. +undefined_global_name(Name, Message) -> + exit({badarg,{Name,Message}}). diff --git a/tests/erlang/examples-2.0/find.erl b/tests/erlang/examples-2.0/find.erl new file mode 100644 index 0000000..bb0ea0c --- /dev/null +++ b/tests/erlang/examples-2.0/find.erl @@ -0,0 +1,170 @@ +-module(find). + +-doc([{author,'Joe Armstrong'}, + {title,"Find all files. Find all out of date files. +

A find utility which finds all files (and directories) +relative to a given root directory"}, + {keywords, [find,make]}, + {api,["find:files(\".\", \"*.erl\", false) finds all +entries in the current directory. +Recursive scan of sub-directories is also allowed.", +"find:out_of_date(\".\",\".erl\",\".jam\") finds all out of date +Erlang files in the current directory"]}, + {date,970203}]). + +-export([files/3, out_of_date/3]). + +-import(lists, [suffix/2, sublist/3, map/2, filter/2]). + + +%% files(Dir, ReExpr, Recursive) -> [File] +%% Find regular files starting from Dir +%% Which match ReExpr +%% If Recursive is true do recursivly on all sub-directories +%% Example find(".", "*.erl", false) will find all erlang files in the +%% Current directory +%% +%% out_of_date(Dir, SrcExt, ObjExt) find all "out of date files" in +%% Dir. +%% Example: +%% out_of_date(".", ".erl", ".jam") +%% Finds all out of date files in the current directory + +files(Dir, Re, Flag) -> + Re1 = string:re_sh_to_awk(Re), + find_files(Dir, Re1, Flag, []). + +%% +type find_files(dirname(), Regexp, bool(), [filename()]) -> [filename()] +%% when Regexp = string(). + +find_files(Dir, Re, Flag, L) -> + case file:list_dir(Dir) of + {ok, Files} -> find_files(Files, Dir, Re, Flag, L); + {error, _} -> L + end. + +%% +type find_files([filename()], dirname(), Regexp, bool(), [filename()]) -> +%% [filename()] when Regexp = string(). + +find_files([File|T], Dir, Re, Recursive, L) -> + FullName = Dir ++ [$/|File], + case file_type(FullName) of + regular -> + case string:re_match(FullName, Re) of + {match, _, _} -> + find_files(T, Dir, Re, Recursive, [FullName|L]); + _ -> + find_files(T, Dir, Re, Recursive, L) + end; + directory -> + case Recursive of + true -> + L1 = find_files(FullName, Re, Recursive, L), + find_files(T, Dir, Re, Recursive, L1); + false -> + find_files(T, Dir, Re, Recursive, L) + end; + error -> + find_files(T, Dir, Re, Recursive, L) + end; +find_files([], _, _, _, L) -> + L. + +%% +type file_type(string()) -> regular | directory | error. + +file_type(File) -> + case file:file_info(File) of + {ok, Facts} -> + case element(2, Facts) of + regular -> regular; + directory -> directory; + _ -> error + end; + _ -> + error + end. + + +%%______________________________________________________________________ +%% outofdate(Dir, InExtension, OutExtension) +%% scans Dir for all files with the extension "InExtension" +%% If a file with this extension is found then "OutExtension" is checked +%% +%% returns a list of files in

where *.OutExtension is +%% "out of date" with respect to *.InExtension +%% in the sence of "make" + +out_of_date(Dir, In, Out) -> + case file:list_dir(Dir) of + {ok, Files0} -> + Files1 = filter(fun(F) -> + suffix(In, F) + end, Files0), + Files2 = map(fun(F) -> + sublist(F, 1, + length(F)-length(In)) + end, Files1), + filter(fun(F) -> update(F, In, Out) end,Files2); + _ -> + [] + end. + +%% +type update(string(), string(), string()) -> bool(). + +update(File, In, Out) -> + InFile = File ++ In, + OutFile = File ++ Out, + case is_file(OutFile) of + true -> + case writeable(OutFile) of + true -> + outofdate(InFile, OutFile); + false -> + %% can't write so we can't update + false + end; + false -> + %% doesn't exist + true + end. + +%% +type is_file(string()) -> bool(). + +is_file(File) -> + case file:file_info(File) of + {ok, _} -> + true; + _ -> + false + end. + +%% +type outofdate(string(), string()) -> bool(). + +outofdate(In, Out) -> + case {last_modified(In), last_modified(Out)} of + {T1, T2} when T1 > T2 -> + true; + _ -> + false + end. + +%% +type last_modified(string()) -> {int(), int(),int(), int(),int(), int()} +%% | 'EXIT'({last_modified, string()}). + +last_modified(F) -> + case file:file_info(F) of + {ok, {_, _, _, _, Time, _, _}} -> + Time; + _ -> + exit({last_modified, F}) + end. + +%% +type writeable(string()) -> bool(). + +writeable(F) -> + case file:file_info(F) of + {ok, {_,_,read_write,_,_,_,_}} -> true; + {ok, {_,_,write ,_,_,_,_}} -> true; + _ -> false + end. + diff --git a/tests/erlang/examples-2.0/ftp_client.erl b/tests/erlang/examples-2.0/ftp_client.erl new file mode 100644 index 0000000..30506e6 --- /dev/null +++ b/tests/erlang/examples-2.0/ftp_client.erl @@ -0,0 +1,52 @@ +-module(ftp_client). + +-doc([{author, joe}, + {title, "FTP client in pure Erlang -- i.e. an FTP client as it might +have been written, i.e. not according to RFC 959"}, + {keywords,[ftp, client]}, + {date, 981014}]). + +-export([connect/3, pwd/1, cd/2, ls/1, put/2, get/2, lcd/1, lpwd/0, lls/0, + quit/1]). + +connect(Host, User, Password) -> + {ftp_server, Host} ! {connect,self(),User,Password}, + receive + {ftp_server, Reply} -> Reply; + Other -> Other + after 10000 -> + timeout + end. + +%S tag1 +pwd(Handle) -> remote(Handle, pwd). +cd(Handle, Dir) -> remote(Handle, {cd, Dir}). +ls(Handle) -> remote(Handle, ls). +get(Handle, File) -> remote(Handle, {get, File}). +quit(Handle) -> remote(Handle, quit). +%E tag1 + +%S tag2 +lcd(Dir) -> file:set_cwd(Dir), lpwd(). +lpwd() -> cwd(). +lls() -> element(2, file:list_dir(cwd())). +%E tag2 + +cwd() -> element(2, file:get_cwd()). + +remote(Handle, Op) -> + Handle ! {self(), Op}, + receive + {ftp_server, Any} -> + Any + after 1000 -> + timeout + end. + +put(Handle, File) -> + case file:read_file(File) of + {ok, Contents} -> + remote(Handle, {put, File, Contents}); + Other -> + Other + end. diff --git a/tests/erlang/examples-2.0/ftp_server.erl b/tests/erlang/examples-2.0/ftp_server.erl new file mode 100644 index 0000000..98d2f4f --- /dev/null +++ b/tests/erlang/examples-2.0/ftp_server.erl @@ -0,0 +1,123 @@ +-module(ftp_server). + +%% Look in ~tony/erlang/ftpd/ftpd.erl +%% For filename stuff + +-doc([{author, joe}, + {title, "FTP server in pure Erlang -- i.e. an FTP server as it +might have been written, i.e. not according to RFC 959"}, + {keywords,[ftp, server]}, + {date, 981014}]). + +-compile(export_all). + +-export([start/0, internal/0, handler/1]). +-import(lists, [member/2, reverse/1]). + +start() -> + case (catch register(ftp_server, + spawn(?MODULE, internal, []))) of + {'EXIT', _} -> + already_started; + Pid -> + ok + end. + +internal() -> + case file:consult("users") of + {ok, Users} -> + process_flag(trap_exit, true), + loop(Users, 0); + _ -> + exit(no_users_allowed) + end. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +loop(Users, N) -> + receive + {connect, Pid, User, Password} -> + io:format("connection request from:~p ~p ~p~n", + [Pid, User, Password]), + case member({User, Password}, Users) of + true -> + Max = max_connections(), + if + N > Max -> + Pid ! {ftp_server, + {error, too_many_connections}}, + loop(Users, N); + true -> + New = spawn_link(?MODULE, handler, [Pid]), + Pid ! {ftp_server, {ok, New}}, + loop(Users, N + 1) + end; + false -> + Pid ! {ftp_server, {error, rejected}}, + loop(Users, N) + end; + {'EXIT', Pid} -> + io:format("Handler ~p died~n", [Pid]), + loop(Users, lists:max(N-1, 0)); + Any -> + io:format("received:~p~n",[Any]), + loop(Users, N) + end. + +handler(Pid) -> + receive + {Pid, quit} -> + Pid ! {ftp_server, ok}; + {Pid, Op} -> + io:format("got:~p ~p~n",[Pid, Op]), + Pid ! {ftp_server, do_op(Op)}, + handler(Pid) + end. + +do_op({cd, Dir}) -> file:set_cwd(Dir), cwd(); +do_op(ls) -> element(2, file:list_dir(cwd())); +do_op(pwd) -> cwd(); +do_op({get_file, File}) -> file:read_file(File). + +max_connections() -> 10. + +cwd() -> element(2, file:get_cwd()). + +%% This was taken from Tony + +%% +%% Compose file/directory names +%% +rel_name(Name, Wd) -> + case filename:pathtype(Name) of + relative -> + rel_path(filename:join(Wd, Name)); + absolute -> + rel_path(Name); + volumerelative -> + rel_path(filename:join(Wd,Name)) + end. +%% +%% We sometime need a simulated root, then call abs_name +%% +abs_name(Name) -> + filename:join("/", Name). + +%% +%% rel_path returns a relative path i.e remove +%% and root or volume relative start components +%% +rel_path(Path) -> + rel_path(filename:split(Path),[]). + +%% remove absolute or volume relative stuff +rel_path([Root|Path], RP) -> + case filename:pathtype(Root) of + relative -> rpath(Path, [Root|RP]); + _ -> rpath(Path, RP) + end. + +rpath([".."|P], [_|RP]) -> rpath(P, RP); +rpath(["."|P], RP) -> rpath(P, RP); +rpath([F|P], RP) -> rpath(P, [F|RP]); +rpath([],[]) -> ""; +rpath([], RP) -> filename:join(reverse(RP)). diff --git a/tests/erlang/examples-2.0/leex.erl b/tests/erlang/examples-2.0/leex.erl new file mode 100644 index 0000000..ae1cc89 --- /dev/null +++ b/tests/erlang/examples-2.0/leex.erl @@ -0,0 +1,591 @@ +%% THIS IS A PRE-RELEASE OF LEEX - RELEASED ONLY BECAUSE MANY PEOPLE +%% WANTED IT - THE OFFICIAL RELEASE WILL PROVIDE A DIFFERENT INCOMPATIBLE +%% AND BETTER INTERFACE - BE WARNED +%% PLEASE REPORT ALL BUGS TO THE AUTHOR. + +%% Copyright (C) 1996, Ellemtel Telecommunications Systems Laboratories +%% File : leex.erl +%% Author : Robert Virding (rv@cslab.ericsson.se) +%% Purpose : A Lexical Analyser Generator for Erlang. + +%% Most of the algorithms used here are taken pretty much as +%% described in the "Dragon Book" by Aho, Sethi and Ullman. Some +%% completing details were taken from "Compiler Design in C" by +%% Hollub. + +-module(leex). + +-doc([{author,'Robert Virding'}, + {title,"A Lexical Analyser Generator for Erlang"}, + {keywords, [lex]}, + {date,981012}]). + +-copyright('Copyright (c) 1996 Ericsson Telecommunications AB'). + +-author('rv@cslab.ericsson.se'). + +-export([gen/2,format_error/1]). + +-import(lists, [member/2,reverse/1,seq/2,keymember/3,keysearch/3,keysort/2, + foreach/2]). +-import(ordsets, [is_element/2,add_element/2,union/2,subtract/2]). + +%%-compile([export_all]). + +-record(nfa_state, {no,edges=[],accept=noaccept}). +-record(dfa_state, {no,nfa=[],trans=[],accept=noaccept}). + +gen(In, Out) -> + InFile = lists:concat([In,".xrl"]), + OutFile = lists:concat([Out,".erl"]), + case parse_file(InFile) of + {ok,REAs,Actions,Code} -> + %% io:fwrite("REAs = ~p\n", [REAs]), + %% io:fwrite("Actions = ~p\n", [Actions]), + {NFA,NF} = build_combined_nfa(REAs), + io:fwrite("NFA contains ~w states, ", [size(NFA)]), + %% io:fwrite("NFA = ~p~n ", [NFA]), + {DFA0,DF0} = build_dfa(NFA, NF), + io:fwrite("DFA contains ~w states, ", [length(DFA0)]), + %% io:fwrite("DFA = ~p~n ", [DFA0]), + {DFA,DF} = minimise_dfa(DFA0, DF0), + io:fwrite("minimised to ~w states.~n", [length(DFA)]), + out_file(OutFile, Out, DFA, DF, Actions, Code); + {error,Error} -> + io:put_chars([$\n,gcc_error(InFile, Error),$\n]), + error + end. + +format_error({open,F}) -> ["error opening ",io_lib:write_string(F)]; +format_error(missing_rules) -> "missing rules"; +format_error(bad_rule) -> "bad rule"; +format_error({regexp,E}) -> ["bad regexp `",regexp:format_error(E),"'"]; +format_error({after_regexp,S}) -> + ["bad code after regexp ",io_lib:write_string(S)]. + +gcc_error(File, {Line,Mod,Error}) -> + io_lib:format("~s:~w: ~s", [File,Line,apply(Mod, format_error, [Error])]); +gcc_error(File, {Mod,Error}) -> + io_lib:format("~s: ~s", [File,apply(Mod, format_error, [Error])]). + +%% parse_file(InFile) -> {[REA],[Action],Code} | {error,Error} +%% when +%% REA = {RegExp,ActionNo}; +%% Action = {ActionNo,ActionString}; +%% Code = [char()]. +%% +%% Read and parse the file InFile. +%% After each section of the file has been parsed we directly call the +%% next section. This is done when we detect a line we don't recognise +%% in the current section. The file format is very simple and Erlang +%% token based, we allow empty lines and Erlang style comments. + +parse_file(InFile) -> + case file:open(InFile, read) of + {ok,Ifile} -> + io:fwrite("Parsing file ~s, ", [InFile]), + case parse_head(Ifile) of + {ok,REAs,Actions,Code} -> + io:fwrite("contained ~w rules.~n", [length(REAs)]), + file:close(Ifile), + {ok,REAs,Actions,Code}; + Error -> + file:close(Ifile), + Error + end; + {error,R} -> + {error,{leex,{open,InFile}}} + end. + +%% parse_head(File) +%% Parse the head of the file. + +parse_head(Ifile) -> + parse_defs(Ifile, nextline(Ifile, 0)). + +%% parse_defs(File, Line) +%% Parse the macro definition section of a file. Allow no definitions. + +parse_defs(Ifile, {ok,[$D,$e,$f,$i,$n,$i,$t,$i,$o,$n,$s,$.|_Rest],L}) -> + parse_defs(Ifile, nextline(Ifile, L), []); +parse_defs(Ifile, Line) -> + parse_rules(Ifile, Line, []). + +parse_defs(Ifile, {ok,Chars,L}, Ms) -> + case string:tokens(Chars, " \t\n") of + [Name,"=",Def] -> + parse_defs(Ifile, nextline(Ifile, L), [{Name,Def}|Ms]); + Other -> + parse_rules(Ifile, {ok,Chars,L}, Ms) + end; +parse_defs(Ifile, Line, Ms) -> + parse_rules(Ifile, Line, Ms). + +%% parse_rules(File, Line, Macros) +%% Parse the RE rules section of the file. This must exist. + +parse_rules(Ifile, {ok,[$R,$u,$l,$e,$s,$.|_Rest],L}, Ms) -> + parse_rules(Ifile, nextline(Ifile, L), Ms, [], [], 0); +parse_rules(Ifile, {ok,Other,L}, Ms) -> + {error,{L,leex,missing_rules}}; +parse_rules(Ifile, {eof,L}, Ms) -> + {error,{L,leex,missing_rules}}. + +collect_rule(Ifile, Chars, L0) -> + {match,St,Len} = regexp:first_match(Chars, "[^ \t]+"), + %% io:fwrite("RE = ~p~n", [string:substr(Chars, St, Len)]), + case collect_rule(Ifile, string:substr(Chars, St+Len), L0, []) of + {ok,[{':',Lc}|Toks],L1} -> {ok,string:substr(Chars, St, Len),Toks,L1}; + {ok,Toks,L1} -> {error,{L0,leex,bad_rule}}; + {eof,L1} -> {error,{L1,leex,bad_rule}}; + {error,E,L1} -> {error,E} + end. + +collect_rule(Ifile, Chars, L0, Cont0) -> + case erl_scan:tokens(Cont0, Chars, L0) of + {done,{ok,Toks,L1},Rest} -> {ok,Toks,L0}; + {done,{eof,L1},Rest} -> {eof,L0}; + {done,{error,E,L1},Rest} -> {error,E,L0}; + {more,Cont1} -> + collect_rule(Ifile, io:get_line(Ifile, leex), L0+1, Cont1) + end. + +parse_rules(Ifile, {ok,[$E,$r,$l,$a,$n,$g,$ ,$c,$o,$d,$e,$.|_Rest],L}, + Ms, REAs, As, N) -> + %% Must be careful to put rules in correct order! + parse_code(Ifile, L, reverse(REAs), reverse(As)); +parse_rules(Ifile, {ok,Chars,L0}, Ms, REAs, As, N) -> + %% io:fwrite("~w: ~p~n", [L0,Chars]), + case collect_rule(Ifile, Chars, L0) of + {ok,Re,Atoks,L1} -> + case parse_rule(Re, L0, Atoks, Ms, N) of + {ok,REA,A} -> + parse_rules(Ifile, nextline(Ifile, L1), Ms, + [REA|REAs], [A|As], N+1); + {error,E} -> {error,E} + end; + {error,E} -> {error,E} + end; +parse_rules(Ifile, {eof,Line}, Ms, REAs, As, N) -> + %% Must be careful to put rules in correct order! + {ok,reverse(REAs),reverse(As),[]}. + +%% parse_rule(RegExpString, RegExpLine, ActionTokens, Macros, Counter) +%% Parse one regexp after performing macro substition. + +parse_rule(S, Line, [{dot,Ld}], Ms, N) -> + case parse_rule_regexp(S, Ms) of + {ok,R} -> + {ok,{R,N},{N,empty_action}}; + {error,E} -> + {error,{Line,leex,{regexp,E}}} + end; +parse_rule(S, Line, Atoks, Ms, N) -> + case parse_rule_regexp(S, Ms) of + {ok,R} -> + case erl_parse:parse_exprs(Atoks) of + {ok,Aes} -> + YYtext = keymember('YYtext', 3, Atoks), + {ok,{R,N},{N,Aes,YYtext}}; + {error,E} -> + {error,{Line,leex,{after_regexp,S}}} + end; + {error,E} -> + {error,{Line,leex,{regexp,E}}} + end. + +parse_rule_regexp(RE0, [{M,Exp}|Ms]) -> + case regexp:gsub(RE0, "{" ++ M ++ "}", Exp) of + {ok,RE,N} -> parse_rule_regexp(RE, Ms); + {error,E} -> parse_rule_regexp(RE0, Ms) + end; +parse_rule_regexp(RE, []) -> + %% io:fwrite("RE = ~p~n", [RE]), + regexp:parse(RE). + +%% parse_code(File, Line, REAs, Actions) +%% Parse the code section of the file. + +parse_code(Ifile, Line, REAs, As) -> + {ok,REAs,As,io:get_chars(Ifile, leex, 102400)}. + +%% nextline(InputFile, PrevLineNo) -> {ok,Chars,LineNo} | {eof,LineNo}. +%% Get the next line skipping comment lines and blank lines. + +nextline(Ifile, L) -> + case io:get_line(Ifile, leex) of + eof -> {eof,L}; + Chars -> + case skip(Chars, " \t\n") of + [$%|_Rest] -> nextline(Ifile, L+1); + [] -> nextline(Ifile, L+1); + Other -> {ok,Chars,L+1} + end + end. + +%% skip(Str, Cs) -> lists:dropwhile(fun (C) -> member(C, Cs) end, Str). + +skip([C|Str], Cs) -> + case member(C, Cs) of + true -> skip(Str, Cs); + false -> [C|Str] + end; +skip([], Cs) -> []. + +%% build_combined_nfa(RegExpActionList) -> {NFA,FirstState}. Build +%% the combined NFA using Thompson's construction straight out of the +%% book. Build the separate NFAs in the same order as the rules so +%% that the accepting have ascending states have ascending state +%% numbers. Start numbering the states from 1 as we put the states +%% in a tuple with the state number as the index. + +build_combined_nfa(REAs) -> + {NFA0,Firsts,Free} = build_nfa_list(REAs, [], [], 1), + F = #nfa_state{no=Free,edges=epsilon_trans(Firsts)}, + {list_to_tuple(keysort(#nfa_state.no, [F|NFA0])),Free}. + +build_nfa_list([{RE,Action}|REAs], NFA0, Firsts, Free0) -> + {NFA1,Free1,First} = build_nfa(RE, Free0, Action), + build_nfa_list(REAs, NFA1 ++ NFA0, [First|Firsts], Free1); +build_nfa_list([], NFA, Firsts, Free) -> + {NFA,reverse(Firsts),Free}. + +epsilon_trans(Firsts) -> [ {epsilon,F} || F <- Firsts ]. + +%% {NFA,NextFreeState,FirstState} = build_nfa(RegExp, FreeState, Action) +%% When building the NFA states for a ??? we don't build the end +%% state, just allocate a State for it and return this state +%% number. This allows us to avoid building unnecessary states for +%% concatenation which would then have to be removed by overwriting +%% an existing state. + +build_nfa(RE, FreeState, Action) -> + {NFA,N,Es} = build_nfa(RE, FreeState+1, FreeState, []), + {[#nfa_state{no=Es,accept={accept,Action}}|NFA],N,FreeState}. + +%% build_nfa(RegExp, NextState, FirstState, NFA) -> {NFA,NextState,EndState}. +%% The NFA is a list of nfa_state is no predefined order. The state +%% number of the returned EndState is already allocated! + +build_nfa({'or',RE1,RE2}, N0, Fs, NFA0) -> + {NFA1,N1,Es1} = build_nfa(RE1, N0+1, N0, NFA0), + {NFA2,N2,Es2} = build_nfa(RE2, N1+1, N1, NFA1), + Es = N2, + {[#nfa_state{no=Fs,edges=[{epsilon,N0},{epsilon,N1}]}, + #nfa_state{no=Es1,edges=[{epsilon,Es}]}, + #nfa_state{no=Es2,edges=[{epsilon,Es}]}|NFA2], + N2+1,Es}; +build_nfa({concat,RE1, RE2}, N0, Fs, NFA0) -> + {NFA1,N1,Es1} = build_nfa(RE1, N0, Fs, NFA0), + {NFA2,N2,Es2} = build_nfa(RE2, N1, Es1, NFA1), + {NFA2,N2,Es2}; +build_nfa({kclosure,RE}, N0, Fs, NFA0) -> + {NFA1,N1,Es1} = build_nfa(RE, N0+1, N0, NFA0), + Es = N1, + {[#nfa_state{no=Fs,edges=[{epsilon,N0},{epsilon,Es}]}, + #nfa_state{no=Es1,edges=[{epsilon,N0},{epsilon,Es}]}|NFA1], + N1+1,Es}; +build_nfa({pclosure,RE}, N0, Fs, NFA0) -> + {NFA1,N1,Es1} = build_nfa(RE, N0+1, N0, NFA0), + Es = N1, + {[#nfa_state{no=Fs,edges=[{epsilon,N0}]}, + #nfa_state{no=Es1,edges=[{epsilon,N0},{epsilon,Es}]}|NFA1], + N1+1,Es}; +build_nfa({optional,RE}, N0, Fs, NFA0) -> + {NFA1,N1,Es1} = build_nfa(RE, N0+1, N0, NFA0), + Es = N1, + {[#nfa_state{no=Fs,edges=[{epsilon,N0},{epsilon,Es}]}, + #nfa_state{no=Es1,edges=[{epsilon,Es}]}|NFA1], + N1+1,Es}; +build_nfa({char_class,Cc}, N, Fs, NFA) -> + {[#nfa_state{no=Fs,edges=[{char_class(Cc),N}]}|NFA],N+1,N}; +build_nfa({comp_class,Cc}, N, Fs, NFA) -> + {[#nfa_state{no=Fs,edges=[{comp_class(Cc),N}]}|NFA],N+1,N}; +build_nfa(C, N, Fs, NFA) when integer(C) -> + {[#nfa_state{no=Fs,edges=[{[C],N}]}|NFA],N+1,N}. + +char_class(Cc) -> + lists:foldl(fun ({C1,C2}, Set) -> union(seq(C1, C2), Set); + (C, Set) -> add_element(C, Set) end, [], Cc). + +comp_class(Cc) -> subtract(seq(0, 255), char_class(Cc)). + +%% build_dfa(NFA, NfaFirstState) -> {DFA,DfaFirstState}. +%% Build a DFA from an NFA using "subset construction". The major +%% difference from the book is that we keep the marked and unmarked +%% DFA states in seperate lists. New DFA states are added to the +%% unmarked list and states are marked by moving them to the marked +%% list. We assume that the NFA accepting state numbers are in +%% ascending order for the rules and use ordsets to keep this order. + +build_dfa(NFA, Nf) -> + D = #dfa_state{no=0,nfa=eclosure([Nf], NFA)}, + {build_dfa([D], 1, [], NFA),0}. + +%% build_dfa([UnMarked], NextState, [Marked], NFA) -> DFA. +%% Traverse the unmarked states. Temporarily add the current unmarked +%% state to the marked list before calculating translation, this is +%% to avoid adding too many duplicate states. Add it properly to the +%% marked list afterwards with correct translations. + +build_dfa([U|Us0], N0, Ms, NFA) -> + {Ts,Us1,N1} = build_dfa(255, U#dfa_state.nfa, Us0, N0, [], [U|Ms], NFA), + M = U#dfa_state{trans=Ts,accept=accept(U#dfa_state.nfa, NFA)}, + build_dfa(Us1, N1, [M|Ms], NFA); +build_dfa([], N, Ms, NFA) -> Ms. + +%% build_dfa(Char, [NfaState], [Unmarked], NextState, [Transition], [Marked], NFA) -> +%% [Marked]. +%% Foreach NFA state set calculate the legal translations. N.B. must +%% search *BOTH* the unmarked and marked lists to check if DFA state +%% already exists. By test characters downwards and prepending +%% transitions we get the transition lists in ascending order. + +build_dfa(C, Set, Us, N, Ts, Ms, NFA) when C >= 0 -> + case eclosure(move(Set, C, NFA), NFA) of + S when S /= [] -> + case keysearch(S, #dfa_state.nfa, Us) of + {value,#dfa_state{no=T}} -> + build_dfa(C-1, Set, Us, N, [{C,T}|Ts], Ms, NFA); + false -> + case keysearch(S, #dfa_state.nfa, Ms) of + {value,#dfa_state{no=T}} -> + build_dfa(C-1, Set, Us, N, [{C,T}|Ts], Ms, NFA); + false -> + U = #dfa_state{no=N,nfa=S}, + build_dfa(C-1, Set, [U|Us], N+1, [{C,N}|Ts], Ms, NFA) + end + end; + [] -> + build_dfa(C-1, Set, Us, N, Ts, Ms, NFA) + end; +build_dfa(-1, Set, Us, N, Ts, Ms, NFA) -> + {Ts,Us,N}. + +%% eclosure([State], NFA) -> [State]. +%% move([State], Char, NFA) -> [State]. +%% These are straight out of the book. As eclosure uses ordsets then +%% the generated state sets are in ascending order. + +eclosure(Sts, NFA) -> eclosure(Sts, NFA, []). + +eclosure([St|Sts], NFA, Ec) -> + #nfa_state{edges=Es} = element(St, NFA), + eclosure([ N || {epsilon,N} <- Es, + nnot(is_element(N, Ec)) ] ++ Sts, + NFA, add_element(St, Ec)); +eclosure([], NFA, Ec) -> Ec. + +nnot(true) -> false; +nnot(false) -> true. + +move(Sts, C, NFA) -> + [St || N <- Sts, + {C1,St} <- (element(N, NFA))#nfa_state.edges, + list(C1), + member(C, C1) ]. + +%% accept([State], NFA) -> {accept,A} | noaccept. +%% Scan down the state list until we find an accepting state. + +accept([St|Sts], NFA) -> + case element(St, NFA) of + #nfa_state{accept={accept,A}} -> {accept,A}; + #nfa_state{accept=noaccept} -> accept(Sts, NFA) + end; +accept([], NFA) -> noaccept. + +%% minimise_dfa(DFA, DfaFirst) -> {DFA,DfaFirst}. +%% Minimise the DFA by removing equivalent states. We consider a +%% state if both the transitions and the their accept state is the +%% same. First repeatedly run throught the DFA state list removing +%% equivalent states and updating remaining transitions with +%% remaining equivalent state numbers. When no more reductions are +%% possible then pack the remaining state numbers to get consecutive +%% states. + +minimise_dfa(DFA0, Df0) -> + case min_dfa(DFA0) of + {DFA1,[]} -> %No reduction! + {DFA2,Rs} = pack_dfa(DFA1), + {min_update(DFA2, Rs),min_use(Df0, Rs)}; + {DFA1,Rs} -> + minimise_dfa(min_update(DFA1, Rs), min_use(Df0, Rs)) + end. + +min_dfa(DFA) -> min_dfa(DFA, [], []). + +min_dfa([D|DFA0], Rs0, MDFA) -> + {DFA1,Rs1} = min_delete(DFA0, D#dfa_state.trans, D#dfa_state.accept, + D#dfa_state.no, Rs0, []), + min_dfa(DFA1, Rs1, [D|MDFA]); +min_dfa([], Rs, MDFA) -> {MDFA,Rs}. + +min_delete([#dfa_state{no=N,trans=T,accept=A}|DFA], T, A, NewN, Rs, MDFA) -> + min_delete(DFA, T, A, NewN, [{N,NewN}|Rs], MDFA); +min_delete([D|DFA], T, A, NewN, Rs, MDFA) -> + min_delete(DFA, T, A, NewN, Rs, [D|MDFA]); +min_delete([], T, A, NewN, Rs, MDFA) -> {MDFA,Rs}. + +min_update(DFA, Rs) -> + [ D#dfa_state{trans=min_update_trans(D#dfa_state.trans, Rs)} || D <- DFA ]. + +min_update_trans(Tr, Rs) -> + [ {C,min_use(S, Rs)} || {C,S} <- Tr ]. + +min_use(Old, [{Old,New}|Reds]) -> New; +min_use(Old, [R|Reds]) -> min_use(Old, Reds); +min_use(Old, []) -> Old. + +pack_dfa(DFA) -> pack_dfa(DFA, 0, [], []). + +pack_dfa([D|DFA], NewN, Rs, PDFA) -> + pack_dfa(DFA, NewN+1, [{D#dfa_state.no,NewN}|Rs], [D#dfa_state{no=NewN}|PDFA]); +pack_dfa([], NewN, Rs, PDFA) -> {PDFA,Rs}. + +%% out_file(FileName, Module, DFA, DfaStart, [Action], Code) -> ok | error. + +out_file(OutFile, Out, DFA, DF, Actions, Code) -> + io:fwrite("Writing file ~s, ", [OutFile]), + case file:path_open([".", [code:lib_dir(),"/tools/include"]], + "leex.hrl", read) of + {ok,Ifile,Iname} -> + case file:open(OutFile, write) of + {ok,Ofile} -> + out_file(Ifile, Ofile, Out, DFA, DF, Actions, Code), + file:close(Ifile), + file:close(Ofile), + io:fwrite("ok~n"), + ok; + {error,E} -> + file:close(Ifile), + io:fwrite("open error~n"), + error + end; + {error,R} -> + io:fwrite("open error~n"), + error + end. + +%% out_file(IncFile, OutFile, DFA, DfaStart, Actions, Code) -> ok. +%% Copy the include file line by line substituting special lines with +%% generated code. We cheat by only looking at the first 5 +%% characters. + +out_file(Ifile, Ofile, Out, DFA, DF, Actions, Code) -> + case io:get_line(Ifile, leex) of + eof -> ok; + Line -> + case string:substr(Line, 1, 5) of + "##mod" -> io:fwrite(Ofile, "-module(~w).~n", [Out]); + "##cod" -> io:put_chars(Ofile, Code); + "##dfa" -> out_dfa(Ofile, DFA, DF); + "##act" -> out_actions(Ofile, Actions); + Other -> io:put_chars(Ofile, Line) + end, + out_file(Ifile, Ofile, Out, DFA, DF, Actions, Code) + end. + +out_dfa(File, DFA, DF) -> + io:fwrite(File, "yystate() -> ~w.~n~n", [DF]), + foreach(fun (S) -> out_trans(File, S) end, DFA), + io:fwrite(File, "yystate(S, Ics, Line, Tlen, Action, Alen) ->~n", []), + io:fwrite(File, " {Action,Alen,Tlen,Ics,Line,S}.~n~n", []). + +out_trans(File, #dfa_state{no=N,trans=[],accept={accept,A}}) -> + io:fwrite(File, "yystate(~w, Ics, Line, Tlen, Action, Alen) ->~n", [N]), + io:fwrite(File, " {~w,Tlen,Ics,Line};~n", [A]); +out_trans(File, #dfa_state{no=N,trans=Tr,accept={accept,A}}) -> + foreach(fun (T) -> out_tran(File, N, A, T) end, pack_trans(Tr)), + io:fwrite(File, "yystate(~w, Ics, Line, Tlen, Action, Alen) ->~n", [N]), + io:fwrite(File, " {~w,Tlen,Ics,Line,~w};~n", [A,N]); +out_trans(File, #dfa_state{no=N,trans=Tr,accept=noaccept}) -> + foreach(fun (T) -> out_tran(File, N, T) end, pack_trans(Tr)), + io:fwrite(File, "yystate(~w, Ics, Line, Tlen, Action, Alen) ->~n", [N]), + io:fwrite(File, " {Action,Alen,Tlen,Ics,Line,~w};~n", [N]). + +out_tran(File, N, A, {{Cf,Cl},S}) -> + out_head(File, N, io_lib:write_char(Cf), io_lib:write_char(Cl)), + out_body(File, S, "Line", "C", A); +out_tran(File, N, A, {$\n,S}) -> + out_head(File, N, "$\\n"), + out_body(File, S, "Line+1", "$\\n", A); +out_tran(File, N, A, {C,S}) -> + Char = io_lib:write_char(C), + out_head(File, N, Char), + out_body(File, S, "Line", Char, A). + +out_tran(File, N, {{Cf,Cl},S}) -> + out_head(File, N, io_lib:write_char(Cf), io_lib:write_char(Cl)), + out_body(File, S, "Line", "C"); +out_tran(File, N, {$\n,S}) -> + out_head(File, N, "$\\n"), + out_body(File, S, "Line+1", "$\\n"); +out_tran(File, N, {C,S}) -> + Char = io_lib:write_char(C), + out_head(File, N, Char), + out_body(File, S, "Line", Char). + +out_head(File, State, Char) -> + io:fwrite(File, "yystate(~w, [~s|Ics], Line, Tlen, Action, Alen) ->\n", + [State,Char]). + +out_head(File, State, Min, Max) -> + io:fwrite(File, "yystate(~w, [C|Ics], Line, Tlen, Action, Alen) when C >= ~s, C =< ~s ->\n", + [State,Min,Max]). + +out_body(File, Next, Line, C, Action) -> + io:fwrite(File, " yystate(~w, Ics, ~s, Tlen+1, ~w, Tlen);\n", + [Next,Line,Action]). + +out_body(File, Next, Line, C) -> + io:fwrite(File, " yystate(~w, Ics, ~s, Tlen+1, Action, Alen);\n", + [Next,Line]). + +%% pack_tran([{Char,State}]) -> [{Crange,State}] when +%% Crange = {Char,Char} | Char. +%% Pack the translation table into something more suitable for +%% generating code. Ranges of characters with the same State are +%% packed together, while solitary characters are left "as is". We +%% KNOW how the pattern matching compiler works so solitary +%% characters are stored before ranges. We do this using ordsets for +%% for the packed table. Always break out $\n as solitary character. + +pack_trans([{C,S}|Tr]) -> pack_trans(Tr, C, C, S, []); +pack_trans([]) -> []. + +pack_trans([{$\n,S1}|Tr], Cf, Cl, S, Pt) -> + pack_trans(Cf, Cl, S, add_element({$\n,S1}, pack_trans(Tr))); +pack_trans([{C,S}|Tr], Cf, Cl, S, Pt) when C == Cl + 1 -> + pack_trans(Tr, Cf, C, S, Pt); +pack_trans([{C,S1}|Tr], Cf, Cl, S, Pt) -> + pack_trans(Tr, C, C, S1, pack_trans(Cf, Cl, S, Pt)); +pack_trans([], Cf, Cl, S, Pt) -> pack_trans(Cf, Cl, S, Pt). + +pack_trans(Cf, Cf, S, Pt) -> add_element({Cf,S}, Pt); +pack_trans(Cf, Cl, S, Pt) when Cl == Cf + 1 -> + add_element({Cf,S}, add_element({Cl,S}, Pt)); +pack_trans(Cf, Cl, S, Pt) -> add_element({{Cf,Cl},S}, Pt). + +out_actions(File, As) -> + foreach(fun (A) -> out_action(File, A) end, As), + io:fwrite(File, "yyaction(_, _, _, _) -> error.~n", []). + +out_action(File, {A,empty_action}) -> + io:fwrite(File, "yyaction(~w, YYlen, YYtcs, YYline) -> skip_token;~n", [A]); +out_action(File, {A,Code,YYtext}) -> + io:fwrite(File, "yyaction(~w, YYlen, YYtcs, YYline) ->~n", [A]), + if + YYtext == true -> + io:fwrite(File, " YYtext = yypre(YYtcs, YYlen),~n", []); + YYtext == false -> ok + end, + io:fwrite(File, " ~s;~n", [erl_pp:exprs(Code, 4, none)]). + + + + + + + + diff --git a/tests/erlang/examples-2.0/leex.hrl b/tests/erlang/examples-2.0/leex.hrl new file mode 100644 index 0000000..e83fe4b --- /dev/null +++ b/tests/erlang/examples-2.0/leex.hrl @@ -0,0 +1,200 @@ +%% THIS IS A PRE-RELEASE OF LEEX - RELEASED ONLY BECAUSE MANY PEOPLE +%% WANTED IT - THE OFFICIAL RELEASE WILL PROVIDE A DIFFERENT INCOMPATIBLE +%% AND BETTER INTERFACE - BE WARNED +%% PLEASE REPORT ALL BUGS TO THE AUTHOR. + +##module + +-export([string/1,string/2,token/2,token/3,tokens/2,tokens/3]). +-export([format_error/1]). + +%% User code. This is placed here to allow extra attributes. +##code + +format_error({illegal,S}) -> ["illegal characters ",io_lib:write_string(S)]; +format_error({user,S}) -> S. + +string(String) -> string(String, 1). + +string(String, Line) -> string(String, Line, String, []). + +%% string(InChars, Line, TokenChars, Tokens) -> +%% {ok,Tokens,Line} | {error,ErrorInfo,Line}. + +string([], L, [], Ts) -> %No partial tokens! + {ok,yyrev(Ts),L}; +string(Ics0, L0, Tcs, Ts) -> + case yystate(yystate(), Ics0, L0, 0, reject, 0) of + {A,Alen,Ics1,L1} -> %Accepting end state + string_cont(Ics1, L1, yyaction(A, Alen, Tcs, L1), Ts); + {A,Alen,Ics1,L1,S1} -> %After an accepting state + string_cont(Ics1, L1, yyaction(A, Alen, Tcs, L1), Ts); + {reject,Alen,Tlen,Ics1,L1,S1} -> + {error,{L1,?MODULE,{illegal,yypre(Tcs, Tlen+1)}},L1}; + {A,Alen,Tlen,Ics1,L1,S1} -> + string_cont(yysuf(Tcs, Alen), L1, yyaction(A, Alen, Tcs, L1), Ts) + end. + +%% string_cont(RestChars, Line, Token, Tokens) +%% Test for and remove the end token wrapper. + +string_cont(Rest, Line, {token,T}, Ts) -> + string(Rest, Line, Rest, [T|Ts]); +string_cont(Rest, Line, {end_token,T}, Ts) -> + string(Rest, Line, Rest, [T|Ts]); +string_cont(Rest, Line, skip_token, Ts) -> + string(Rest, Line, Rest, Ts); +string_cont(Rest, Line, {error,S}, Ts) -> + {error,{Line,?MODULE,{user,S}},Line}. + +%% token(Continuation, Chars, Line) -> +%% {more,Continuation} | {done,ReturnVal,RestChars}. +%% Must be careful when re-entering to append the latest characters to the +%% after characters in an accept. + +token(Cont, Chars) -> token(Cont, Chars, 1). + +token([], Chars, Line) -> + token(Chars, Line, yystate(), Chars, 0, reject, 0); +token({Line,State,Tcs,Tlen,Action,Alen}, Chars, _) -> + token(Chars, Line, State, Tcs ++ Chars, Tlen, Action, Alen). + +%% token(InChars, Line, State, TokenChars, TokenLen, Accept) -> +%% {more,Continuation} | {done,ReturnVal,RestChars}. + +token(Ics0, L0, S0, Tcs, Tlen0, A0, Alen0) -> + case yystate(S0, Ics0, L0, Tlen0, A0, Alen0) of + {A1,Alen1,Ics1,L1} -> %Accepting end state + token_cont(Ics1, L1, yyaction(A1, Alen1, Tcs, L1)); + {A1,Alen1,[],L1,S1} -> %After an accepting state + {more,{L1,S1,Tcs,Alen1,A1,Alen1}}; + {A1,Alen1,Ics1,L1,S1} -> + token_cont(Ics1, L1, yyaction(A1, Alen1, Tcs, L1)); + {A1,Alen1,Tlen1,[],L1,S1} -> %After a non-accepting state + {more,{L1,S1,Tcs,Tlen1,A1,Alen1}}; + {reject,Alen1,Tlen1,eof,L1,S1} -> + {done,{eof,L1},[]}; + {reject,Alen1,Tlen1,Ics1,L1,S1} -> + {done,{error,{L1,?MODULE,{illegal,yypre(Tcs, Tlen1+1)}},L1},Ics1}; + {A1,Alen1,Tlen1,Ics1,L1,S1} -> + token_cont(yysuf(Tcs, Alen1), L1, yyaction(A1, Alen1, Tcs, L1)) + end. + +%% tokens_cont(RestChars, Line, Token) +%% Test if we have detected the end token, if so return done else continue. + +token_cont(Rest, Line, {token,T}) -> + {done,{ok,T,Line},Rest}; +token_cont(Rest, Line, {end_token,T}) -> + {done,{ok,T,Line},Rest}; +token_cont(Rest, Line, skip_token) -> + token(Rest, Line, yystate(), Rest, 0, reject, 0); +token_cont(Rest, Line, {error,S}) -> + {done,{error,{Line,?MODULE,{user,S}},Line},Rest}. + +%% tokens(Continuation, Chars, Line) -> +%% {more,Continuation} | {done,ReturnVal,RestChars}. +%% Must be careful when re-entering to append the latest characters to the +%% after characters in an accept. + +tokens(Cont, Chars) -> tokens(Cont, Chars, 1). + +tokens([], Chars, Line) -> + tokens(Chars, Line, yystate(), Chars, 0, [], reject, 0); +tokens({tokens,Line,State,Tcs,Tlen,Ts,Action,Alen}, Chars, _) -> + tokens(Chars, Line, State, Tcs ++ Chars, Tlen, Ts, Action, Alen); +tokens({skip_tokens,Line,State,Tcs,Tlen,Error,Action,Alen}, Chars, _) -> + skip_tokens(Chars, Line, State, Tcs ++ Chars, Tlen, Error, Action, Alen). + +%% tokens(InChars, Line, State, TokenChars, TokenLen, Tokens, Accept) -> +%% {more,Continuation} | {done,ReturnVal,RestChars}. + +tokens(Ics0, L0, S0, Tcs, Tlen0, Ts, A0, Alen0) -> + case yystate(S0, Ics0, L0, Tlen0, A0, Alen0) of + {A1,Alen1,Ics1,L1} -> %Accepting end state + tokens_cont(Ics1, L1, yyaction(A1, Alen1, Tcs, L1), Ts); + {A1,Alen1,[],L1,S1} -> %After an accepting state + {more,{tokens,L1,S1,Tcs,Alen1,Ts,A1,Alen1}}; + {A1,Alen1,Ics1,L1,S1} -> + tokens_cont(Ics1, L1, yyaction(A1, Alen1, Tcs, L1), Ts); + {A1,Alen1,Tlen1,[],L1,S1} -> %After a non-accepting state + {more,{tokens,L1,S1,Tcs,Tlen1,Ts,A1,Alen1}}; + {reject,Alen1,Tlen1,eof,L1,S1} -> + {done,if Ts == [] -> {eof,L1}; + true -> {ok,yyrev(Ts),L1} end,[]}; + {reject,Alen1,Tlen1,Ics1,L1,S1} -> + skip_tokens(yysuf(Tcs, Tlen1+1), L1, + {L1,?MODULE,{illegal,yypre(Tcs, Tlen1+1)}}); + {A1,Alen1,Tlen1,Ics1,L1,S1} -> + tokens_cont(yysuf(Tcs, Alen1), L1, yyaction(A1, Alen1, Tcs, L1), Ts) + end. + +%% tokens_cont(RestChars, Line, Token, Tokens) +%% Test if we have detected the end token, if so return done else continue. + +tokens_cont(Rest, Line, {token,T}, Ts) -> + tokens(Rest, Line, yystate(), Rest, 0, [T|Ts], reject, 0); +tokens_cont(Rest, Line, {end_token,T}, Ts) -> + {done,{ok,yyrev(Ts, [T]),Line},Rest}; +tokens_cont(Rest, Line, skip_token, Ts) -> + tokens(Rest, Line, yystate(), Rest, 0, Ts, reject, 0); +tokens_cont(Rest, Line, {error,S}, Ts) -> + skip_tokens(Rest, Line, {Line,?MODULE,{user,S}}). + +%% token_skip(InChars, Line, Error) -> {done,ReturnVal,RestChars}. +%% Skip tokens until an end token, junk everything and return the error. + +%%skip_tokens(Ics, Line, Error) -> {done,{error,Error,Line},Ics}. + +skip_tokens(Ics, Line, Error) -> + skip_tokens(Ics, Line, yystate(), Ics, 0, Error, reject, 0). + +%% skip_tokens(InChars, Line, State, TokenChars, TokenLen, Tokens, Accept) -> +%% {more,Continuation} | {done,ReturnVal,RestChars}. + +skip_tokens(Ics0, L0, S0, Tcs, Tlen0, Error, A0, Alen0) -> + case yystate(S0, Ics0, L0, Tlen0, A0, Alen0) of + {A1,Alen1,Ics1,L1} -> %Accepting end state + skip_cont(Ics1, L1, yyaction(A1, Alen1, Tcs, L1), Error); + {A1,Alen1,[],L1,S1} -> %After an accepting state + {more,{skip_tokens,L1,S1,Tcs,Alen1,Error,A1,Alen1}}; + {A1,Alen1,Ics1,L1,S1} -> + skip_cont(Ics1, L1, yyaction(A1, Alen1, Tcs, L1), Error); + {A1,Alen1,Tlen1,[],L1,S1} -> %After a non-accepting state + {more,{skip_tokens,L1,S1,Tcs,Tlen1,Error,A1,Alen1}}; + {reject,Alen1,Tlen1,eof,L1,S1} -> + {done,{error,Error,L1},[]}; + {reject,Alen1,Tlen1,Ics1,L1,S1} -> + skip_tokens(yysuf(Tcs, Tlen1), L1, Error); + {A1,Alen1,Tlen1,Ics1,L1,S1} -> + skip_cont(yysuf(Tcs, Alen1), L1, yyaction(A1, Alen1, Tcs, L1), Error) + end. + +%% skip_cont(RestChars, Line, Token, Error) +%% Test if we have detected the end token, if so return done else continue. + +skip_cont(Rest, Line, {token,T}, Error) -> + skip_tokens(Rest, Line, yystate(), Rest, 0, Error, reject, 0); +skip_cont(Rest, Line, {end_token,T}, Error) -> + {done,{error,Error,Line},Rest}; +skip_cont(Rest, Line, {error,S}, Error) -> + skip_tokens(Rest, Line, yystate(), Rest, 0, Error, reject, 0); +skip_cont(Rest, Line, skip_token, Error) -> + skip_tokens(Rest, Line, yystate(), Rest, 0, Error, reject, 0). + +yyrev(L) -> yyrev(L, []). + +yyrev([H|T], Acc) -> yyrev(T, [H|Acc]); +yyrev([], Acc) -> Acc. + +yypre([H|T], N) when N > 0 -> [H|yypre(T, N-1)]; +yypre(L, N) -> []. + +yysuf([H|T], N) when N > 0 -> yysuf(T, N-1); +yysuf(L, 0) -> L. + +%% Generated state transition function. +##dfa + +%% Generated action function. +##actions diff --git a/tests/erlang/examples-2.0/lin.erl b/tests/erlang/examples-2.0/lin.erl new file mode 100644 index 0000000..d3a7d45 --- /dev/null +++ b/tests/erlang/examples-2.0/lin.erl @@ -0,0 +1,95 @@ +-module(lin). + +-doc([{author,'Joe Armstrong'}, + {title,"Linear algebra utilities."}, + {keywords, [linear,algebra]}, + {date,981103}]). + +-export([pow/3, inv/2, solve/2, str2int/1, int2str/1, gcd/2]). + +%% pow(A, B, M) => (A^B) mod M +%% examples pow(9726,3533,11413) = 5761 +%% pow(5971,6597,11413) = 9726 + +pow(A, 1, M) -> + A rem M; +pow(A, 2, M) -> + A*A rem M; +pow(A, B, M) -> + B1 = B div 2, + B2 = B - B1, + %% B2 = B1 or B1+1 + P = pow(A, B1, M), + case B2 of + B1 -> (P*P) rem M; + _ -> (P*P*A) rem M + end. + +%% inv(A, B) = C | no_inverse +%% computes C such that +%% A*C mod B = 1 +%% computes A^-1 mod B +%% examples inv(28, 75) = 67. +%% inv(3533, 11200) = 6597 +%% inv(6597, 11200) = 3533 + +inv(A, B) -> + case solve(A, B) of + {X, Y} -> + if X < 0 -> X + B; + true -> X + end; + _ -> + no_inverse + end. + +%% solve(A, B) => {X, Y} | insoluble +%% solve the linear congruence +%% A * X - B * Y = 1 + +%S tag1 +solve(A, B) -> + case catch s(A,B) of + insoluble -> insoluble; + {X, Y} -> + case A * X - B * Y of + 1 -> {X, Y}; + Other -> error + end + end. + +s(A, 0) -> throw(insoluble); +s(A, 1) -> {0, -1}; +s(A, -1) -> {0, 1}; +s(A, B) -> + K1 = A div B, + K2 = A - K1*B, + {Tmp, X} = s(B, -K2), + {X, K1 * X - Tmp}. +%E tag1 + + +%% converts a string to a base 256 integer +%% converts a base 256 integer to a string + +%S tag2 +str2int(Str) -> str2int(Str, 0). + +str2int([H|T], N) -> str2int(T, N*256+H); +str2int([], N) -> N. + +int2str(N) -> int2str(N, []). + +int2str(N, L) when N =< 0 -> L; +int2str(N, L) -> + N1 = N div 256, + H = N - N1 * 256, + int2str(N1, [H|L]). +%E tag2 + +%% greatest common denominator + +gcd(A, B) when A < B -> gcd(B, A); +gcd(A, 0) -> A; +gcd(A, B) -> + gcd(B, A rem B). diff --git a/tests/erlang/examples-2.0/primes.erl b/tests/erlang/examples-2.0/primes.erl new file mode 100644 index 0000000..0af3c4e --- /dev/null +++ b/tests/erlang/examples-2.0/primes.erl @@ -0,0 +1,92 @@ +-module(primes). + +-doc([{author,'Joe Armstrong'}, + {title,"Prime number utilities."}, + {keywords, [prime, numbers]}, + {date,981103}]). + +-export([make/1, make_prime/1, is_prime/1]). +-compile(export_all). + +%% make a prime with at least K decimal digits +%% Here we use 'Bertrand's postulate, is that for every N > 3, +%% there is a prime P satisfying N < P < 2N - 2 +%% This was proved by Tchebychef in 1850 (Erdos improved this proof +%% in 1932) + +%S tag4 + +make_prime(K) when K > 0 -> + new_seed(), + N = make(K), + if N > 3 -> + io:format("Generating a ~w digit prime ",[K]), + MaxTries = N - 3, + P1 = make_prime(MaxTries, N+1), + io:format("~n",[]), + P1; + true -> + make_prime(K) + end. + +make_prime(0, _) -> + exit(impossible); +make_prime(K, P) -> + io:format(".",[]), + case is_prime(P) of + true -> P; + false -> make_prime(K-1, P+1) + end. +%E tag4 + +%% make(N) -> a random integer with N digits. + +%S tag1 +make(N) -> new_seed(), make(N, 0). + +make(0, D) -> D; +make(N, D) -> + make(N-1, D*10 + (random:uniform(10)-1)). +%E tag1 + +%% Fermat's little theorem says that if +%% N is a prime and if A < N then +%% A^N mod N = A + +%S tag3 +is_prime(D) -> + new_seed(), + is_prime(D, 100). + +is_prime(D, Ntests) -> + N = length(integer_to_list(D)) -1, + is_prime(Ntests, D, N). + +is_prime(0, _, _) -> true; +is_prime(Ntest, N, Len) -> + K = random:uniform(Len), + %% A is a random number less than N + A = make(K), + if + A < N -> + case lin:pow(A,N,N) of + A -> is_prime(Ntest-1,N,Len); + _ -> false + end; + true -> + is_prime(Ntest, N, Len) + end. +%E tag3 + +new_seed() -> + {_,_,X} = erlang:now(), + {H,M,S} = time(), + H1 = H * X rem 32767, + M1 = M * X rem 32767, + S1 = S * X rem 32767, + put(random_seed, {H1,M1,S1}). + + + + + diff --git a/tests/erlang/examples-2.0/rsa_key.erl b/tests/erlang/examples-2.0/rsa_key.erl new file mode 100644 index 0000000..3c72c07 --- /dev/null +++ b/tests/erlang/examples-2.0/rsa_key.erl @@ -0,0 +1,56 @@ +-module(rsa_key). + +-export([make_sig/1, make_sig/2]). + +make_sig(Who, Len) when Len > 79 -> + {Public, Private} = make_sig(Len), + file:write_file(Who ++ ".pub", term_to_binary(Public)), + file:write_file(Who ++ ".pri", term_to_binary(Private)), + {keyfilecreated,for,Who}. + +%% The "book" says ... +%% 1. Bob generates two primes p and q +%% 2. Bob computes n = pq and phi(n) = (p-1)*(q-1) +%% 3. Bob chooses a random b(0 < b < phi(n)) such that +%% gcd(b, phi(n)) = 1 +%% 4. Bob computes a = b^(-1) mod phi(n) using the Euclidean algorithm +%% 5. Bob publishes n and b in a directory as his public key. + +%S tag1 +make_sig(Len) -> + %% generate two digit prime numbers + P = primes:make_prime(Len), + io:format("P = ~p~n", [P]), + Q = primes:make_prime(Len), + io:format("Q = ~p~n", [Q]), + N = P*Q, + io:format("N = ~p~n", [N]), + Phi = (P-1)*(Q-1), + %% now make B such that B < Phi and gcd(B, Phi) = 1 + B = b(Phi), + io:format("Public key (B) = ~p~n", [B]), + A = lin:inv(B, Phi), + io:format("Private key (A) = ~p~n", [A]), + {{B,N},{A,N}}. + +b(Phi) -> + io:format("Generating a public key B "), + K = length(integer_to_list(Phi)) - 1, + B = b(1, K, Phi), + io:format("~n", []), + B. + +b(Try, K, Phi) -> + io:format("."), + B = primes:make(K), + if + B < Phi -> + case lin:gcd(B, Phi) of + 1 -> B; + _ -> b(Try+1, K, Phi) + end; + true -> + b(Try, K, Phi) + end. +%E tag1 + diff --git a/tests/erlang/examples-2.0/sos b/tests/erlang/examples-2.0/sos new file mode 100755 index 0000000..31e817e --- /dev/null +++ b/tests/erlang/examples-2.0/sos @@ -0,0 +1,2 @@ +#!/bin/sh +erl -boot /home/joe/erl/example_programs-2.0/examples-2.0/sos -environment `printenv` -load $1 diff --git a/tests/erlang/examples-2.0/sos.erl b/tests/erlang/examples-2.0/sos.erl new file mode 100644 index 0000000..0d2bbc3 --- /dev/null +++ b/tests/erlang/examples-2.0/sos.erl @@ -0,0 +1,344 @@ +-module(sos). + +-doc([{author,joe}, + {title,"Simple OS written entirely in Erlang"}, + {keywords, [os]}, + {htmldoc, "sos.html"}, + {date,981012}]). + +-export([main/0, % starts the system + load_module/1, % + log_error/1, + make_server/3, + cast/2, + rpc/2, + change_behaviour/2, + keep_alive/2, + make_global/2, + on_exit/2, + on_halt/1, + stop_system/1, + every/3, + spawn_fun/1, + spawn_link_fun/1, + lookup/2, + map/2, + reverse/1, + read/0, + write/1, + env/1, + make_scripts/0 + ]). + +-export([internal_call/1]). + +main() -> + make_server(io, + fun start_io/0, fun handle_io/2), + make_server(code, + const([init,erl_prim_loader]), + fun handle_code/2), + make_server(error_logger, + const(0), fun handle_error_logger/2), + make_server(halt_demon, + const([]), fun handle_halt_demon/2), + make_server(env, + fun start_env/0, fun handle_env/2), + load_module(error_handler), + Mod = get_module_name(), + load_module(Mod), + run(Mod). + +run(Mod) -> + Pid = spawn_link(Mod, main, []), + on_exit(Pid, fun(Why) -> stop_system(Why) end). + +load_module(Mod) -> rpc(code, {load_module, Mod}). + +handle_code({load_module, Mod}, Mods) -> + case member(Mod, Mods) of + true -> + {already_loaded, Mods}; + false -> + case primLoad(Mod) of + ok -> + {{ok,Mod}, [Mod|Mods]}; + Error -> + {Error, Mods} + end + end. + +primLoad(Module) -> + Str = atom_to_list(Module), + case erl_prim_loader:get_file(Str ++ ".jam") of + {ok, Bin, FullName} -> + case erlang:load_module(Module, Bin) of + {module, Module} -> + ok; + {module, _} -> + {error, wrong_module_in_binary}; + Other -> + {error, {bad_object_code, Module}} + end; + error -> + {error, {cannot_locate, Module}} + + end. + +log_error(Error) -> cast(error_logger, {log, Error}). + +handle_error_logger({log, Error}, N) -> + erlang:display({error, Error}), + {ok, N+1}. + +%S tag4 +on_halt(Fun) -> cast(halt_demon,{on_halt,Fun}). +stop_system(Why) -> cast(halt_demon,{stop_system,Why}). +%E tag4 + +handle_halt_demon({on_halt, Fun}, Funs) -> + {ok, [Fun|Funs]}; +handle_halt_demon({stop_system, Why}, Funs) -> + case Why of + normal -> true; + _ -> erlang:display({stopping_system,Why}) + end, + map(fun(F) -> F() end, Funs), + erlang:halt(), + {ok, []}. + +%S tag5 +read() -> rpc(io, read). +write(X) -> rpc(io, {write, X}). +%E tag5 + +start_io() -> + Port = open_port({fd,0,1}, [eof, binary]), + process_flag(trap_exit, true), + {false, Port}. + +handle_io(read, {true, Port}) -> + {eof, {true, Port}}; +handle_io(read, {false, Port}) -> + receive + {Port, {data, Bytes}} -> + {{ok, Bytes}, {false, Port}}; + {Port, eof} -> + {eof, {true,Port}}; + {'EXIT', Port, badsig} -> + handle_io(read, {false, Port}); + {'EXIT', Port, Why} -> + {eof, {true, Port}} + end; +handle_io({write,X}, {Flag,Port}) -> + Port ! {self(), {command, X}}, + {ok, {Flag, Port}}. + +env(Key) -> rpc(env, {lookup, Key}). + +handle_env({lookup, Key}, Dict) -> + {lookup(Key, Dict), Dict}. + +start_env() -> + Env = case init:get_argument(environment) of + {ok, [L]} -> + L; + error -> + fatal({missing, '-environment ...'}) + end, + map(fun split_env/1, Env). + +split_env(Str) -> split_env(Str, []). + +split_env([$=|T], L) -> {reverse(L), T}; +split_env([], L) -> {reverse(L), []}; +split_env([H|T], L) -> split_env(T, [H|L]). + +make_server(Name, FunD, FunH) -> + make_global(Name, + fun() -> + Data = FunD(), + server_loop(Name, Data, FunH) + end). + +server_loop(Name, Data, Fun) -> + receive + {rpc, Pid, Q} -> + case (catch Fun(Q, Data)) of + {'EXIT', Why} -> + Pid ! {Name, exit, Why}, + server_loop(Name, Data, Fun); + {Reply, Data1} -> + Pid ! {Name, Reply}, + server_loop(Name, Data1, Fun) + end; + {cast, Pid, Q} -> + case (catch Fun(Q, Data)) of + {'EXIT', Why} -> + exit(Pid, Why), + server_loop(Name, Data, Fun); + Data1 -> + server_loop(Name, Data1, Fun) + end; + {eval, Fun1} -> + server_loop(Name, Data, Fun1) + end. + +rpc(Name, Q) -> + Name ! {rpc, self(), Q}, + receive + {Name, Reply} -> + Reply; + {Name, exit, Why} -> + exit(Why) + end. + +cast(Name, Q) -> + Name ! {cast, self(), Q}. + +change_behaviour(Name, Fun) -> + Name ! {eval, Fun}. + +const(C) -> fun() -> C end. + +keep_alive(Name, Fun) -> + Pid = make_global(Name, Fun), + on_exit(Pid, + fun(Exit) -> keep_alive(Name, Fun) end). + +make_global(Name, Fun) -> + case whereis(Name) of + undefined -> + Self = self(), + Pid = spawn_fun(fun() -> + make_global(Self,Name,Fun) + end), + receive + {Pid, ack} -> + Pid + end; + Pid -> + Pid + end. + +make_global(Pid, Name, Fun) -> + case register(Name, self()) of + {'EXIT', _} -> + Pid ! {self(), ack}; + _ -> + Pid ! {self(), ack}, + Fun() + end. + +spawn_fun({'fun',Mod,Arity,Chksum,Env}) -> + spawn(?MODULE, internal_call, + [[Mod,Arity,Chksum,[],Env]]). + +spawn_link_fun({'fun',Mod,Arity,Chksum,Env}) -> + spawn(?MODULE, internal_call, + [[Mod,Arity,Chksum,[],Env]]). + +internal_call([Mod|Args]) -> + apply(Mod, module_lambdas, Args). + +on_exit(Pid, Fun) -> + spawn_fun(fun() -> + process_flag(trap_exit, true), + link(Pid), + receive + {'EXIT', Pid, Why} -> + Fun(Why) + end + end). + +every(Pid, Time, Fun) -> + spawn_fun(fun() -> + process_flag(trap_exit, true), + link(Pid), + every_loop(Pid, Time, Fun) + end). + +every_loop(Pid, Time, Fun) -> + receive + {'EXIT', Pid, Why} -> + true + after Time -> + Fun(), + every_loop(Pid, Time, Fun) + end. + +get_module_name() -> + case init:get_argument(load) of + {ok, [[Arg]]} -> + module_name(Arg); + error -> + fatal({missing, '-load Mod'}) + end. + +%S tag7 +lookup(Key, [{Key,Val}|_]) -> {found, Val}; +lookup(Key, [_|T]) -> lookup(Key, T); +lookup(Key, []) -> not_found. + +member(X, [X|_]) -> true; +member(H, [_|T]) -> member(H, T); +member(_, []) -> false. + +map(F, [H|T]) -> [F(H)|map(F, T)]; +map(F, []) -> []. + +reverse(X) -> reverse(X, []). + +reverse([H|T], L) -> reverse(T, [H|L]); +reverse([], L) -> L. + +module_name(Str) -> + case (catch list_to_atom(Str)) of + {'EXIT', _} -> + log_error({bad_module_name,Str}), + stop_system(bad_start_module); + Mod -> Mod + end. +%E tag7 + +fatal(Term) -> + log_error({fatal, Term}), + stop_system({fatal, Term}). + +%S tag8 +make_scripts() -> + {ok, Cwd} = file:get_cwd(), + Script = + {script,{"sos","1.0"}, + [{preLoaded,[init,erl_prim_loader]}, + {progress,preloaded}, + {path, + [".", + Cwd, + "$ROOT/lib/" ++ lib_location(kernel) ++ "/ebin", + "$ROOT/lib/" ++ lib_location(stdlib) ++ "/ebin"]}, + {primLoad, + [erl_open_port, + erlang, + error_handler, + sos + ]}, + {kernel_load_completed}, + {progress,kernel_load_completed}, + {progress,started}, + {apply,{sos,main,[]}} + ]}, + file:write_file("sos.boot", term_to_binary(Script)), + + {ok, Stream} = file:open("sos", write), + io:format(Stream, "#!/bin/sh~nerl -boot ~s/sos " + " -environment `printenv` -load $1~n", + [Cwd]), + file:close(Stream), + unix:cmd("chmod a+x sos"), + true. + +lib_location(Lib) -> + filename:basename(code:lib_dir(Lib)). +%E tag8 + diff --git a/tests/erlang/examples-2.0/sos_err1.erl b/tests/erlang/examples-2.0/sos_err1.erl new file mode 100644 index 0000000..382da01 --- /dev/null +++ b/tests/erlang/examples-2.0/sos_err1.erl @@ -0,0 +1,7 @@ +-module(sos_err1). +-doc(none). +-export([main/0]). + +main() -> + listsforeaack:abc(123), + sos:write("Hello world\n"). diff --git a/tests/erlang/examples-2.0/sos_err2.erl b/tests/erlang/examples-2.0/sos_err2.erl new file mode 100644 index 0000000..110f16f --- /dev/null +++ b/tests/erlang/examples-2.0/sos_err2.erl @@ -0,0 +1,7 @@ +-module(sos_err2). +-doc(none). +-export([main/0]). + +main() -> + lists:abc(123), + sos:write("Hello world\n"). diff --git a/tests/erlang/examples-2.0/sos_test1.erl b/tests/erlang/examples-2.0/sos_test1.erl new file mode 100644 index 0000000..0f3846c --- /dev/null +++ b/tests/erlang/examples-2.0/sos_test1.erl @@ -0,0 +1,6 @@ +-module(sos_test1). +-doc(none). +-export([main/0]). + +main() -> + sos:write("Hello world\n"). diff --git a/tests/erlang/examples-2.0/sos_test2.erl b/tests/erlang/examples-2.0/sos_test2.erl new file mode 100644 index 0000000..a0dfa7c --- /dev/null +++ b/tests/erlang/examples-2.0/sos_test2.erl @@ -0,0 +1,7 @@ +-module(sos_test2). +-doc(none). +-export([main/0]). + +main() -> + X = lists:reverse("Hellow world"), + sos:write([X,"\n"]). diff --git a/tests/erlang/examples-2.0/sos_test3.erl b/tests/erlang/examples-2.0/sos_test3.erl new file mode 100644 index 0000000..11e3c31 --- /dev/null +++ b/tests/erlang/examples-2.0/sos_test3.erl @@ -0,0 +1,16 @@ +-module(sos_test3). +-doc(none). +-export([main/0]). +-import(sos, [read/0, write/1]). + +main() -> + loop(). + +loop() -> + case read() of + eof -> + true; + {ok, X} -> + write([X]), + loop() + end. diff --git a/tests/erlang/examples-2.0/sos_test4.erl b/tests/erlang/examples-2.0/sos_test4.erl new file mode 100644 index 0000000..0806097 --- /dev/null +++ b/tests/erlang/examples-2.0/sos_test4.erl @@ -0,0 +1,8 @@ +-module(sos_test4). +-doc(none). +-export([main/0]). + +main() -> + sos:write("I will crash now\n"), + 1 = 2, + sos:write("This line will not be printed\n"). diff --git a/tests/erlang/examples-2.0/suffix_rules b/tests/erlang/examples-2.0/suffix_rules new file mode 100644 index 0000000..ea3743d --- /dev/null +++ b/tests/erlang/examples-2.0/suffix_rules @@ -0,0 +1,8 @@ +Suffix .erl.jam -> + c:c($>). + +Suffix .tex.dvi -> + unix:cmd("latex $>.tex"). + +Suffix .ehtml.html -> + ehtml:file("$>"). diff --git a/tests/erlang/examples-2.0/test1 b/tests/erlang/examples-2.0/test1 new file mode 100644 index 0000000..1bda09b --- /dev/null +++ b/tests/erlang/examples-2.0/test1 @@ -0,0 +1,17 @@ +%% This is a macro + +OBJS = a.jam, b.jam. + +OBJS += ,c.jam. + +include("suffix_rules"). + +all when a.dvi $(OBJS). + +a.dvi when a.tex -> + + io:format("touching a.dvi~n"), + unix:cmd("touch a.dvi"), + io:format("done~n"). + + diff --git a/tests/erlang/examples-2.0/test2 b/tests/erlang/examples-2.0/test2 new file mode 100644 index 0000000..87d5310 --- /dev/null +++ b/tests/erlang/examples-2.0/test2 @@ -0,0 +1,13 @@ +%% This is a macro + +OBJS = abc.jam, a.jam, b.jam. + +include("$(MAKEDIR)/suffix_rules"). + +all when $(OBJS) a.dvi. + +a.dvi when a.tex -> + unix:cmd("touch a.dvi"). + + + diff --git a/tests/erlang/examples-2.0/test3 b/tests/erlang/examples-2.0/test3 new file mode 100644 index 0000000..4e71484 --- /dev/null +++ b/tests/erlang/examples-2.0/test3 @@ -0,0 +1,52 @@ +%% +%% Make the tk library +%% + +Jamdir=../ebin. + +CC = gcc. + +tk when $(Jamdir)/tk.jam, $(Jamdir)/tkfocus.jam, + tkbutton.jam, tkentry.jam, + tkscrlbar.jam, tkdialog.jam, tklib.jam -> + X = 12, + Y = 14, + tk:stop(X, Y, X+Y). + +$(Jamdir)/tk.jam when tk.erl, tk.hrl -> + c:c(tk, [{outdir, "$(Jamdir)"}]). + +$(Jamdir)/tkfocus.jam when tkfocus.erl, tk.hrl -> + c:c(tkfocus, [{outdir, "$(Jamdir)"}]). + +$(Jamdir)/tkbutton.jam when tkbutton.erl, tk.hrl -> + c:c(tkbutton, [{outdir, "$(Jamdir)"}]). + +$(Jamdir)/tkentry.jam when tkentry.erl, tk.hrl -> + c:c(tkentry, [{outdir, "$(Jamdir)"}]). + +tkscrlbar.jam when tkscrlbar.erl, tk.hrl -> + c:c(tkscrlbar, [{outdir, "$(Jamdir)"}]). + +tkdialog.jam when tkdialog.erl -> + c:c(tkdialog, [{outdir, "$(Jamdir)"}]). + +tklib.jam when tklib.erl -> + io:format("compiling: $@~n"), + c:c(tklib, [{outdir, "$(Jamdir)"}]). + +foo.o when foo.c -> + unix:cmd("$(CC) -o $@ -c foo.c"). + +foo when bar -> + X = case Jamdir of + "../ebin" -> 1; + ".." -> 2; + "." -> 3 + end, + io:format("this = ~w", [X]). + + + + + diff --git a/tests/erlang/examples-2.0/test4 b/tests/erlang/examples-2.0/test4 new file mode 100644 index 0000000..c1cda9f --- /dev/null +++ b/tests/erlang/examples-2.0/test4 @@ -0,0 +1,7 @@ +all when a.jam -> + p,q,r. + +all -> a,b,c. + +all,b,c when a,b,c. + diff --git a/tests/erlang/examples-2.0/title_page.tex b/tests/erlang/examples-2.0/title_page.tex new file mode 100644 index 0000000..c4589b5 --- /dev/null +++ b/tests/erlang/examples-2.0/title_page.tex @@ -0,0 +1,22 @@ +\begin{titlepage} +\begin{center} +\vspace*{1.0cm} +{\Huge\bf +The Erlang Cookbook\\ +} + +\vskip 1.0 true cm + + +\vskip 3.0 true cm + +{\large\bf +Joe Armstrong\\ +} + +\vskip 1.0 true cm + +\end{center} +\eject +\end{titlepage} + diff --git a/tests/erlang/examples-2.0/topological_sort.erl b/tests/erlang/examples-2.0/topological_sort.erl new file mode 100644 index 0000000..247f7a5 --- /dev/null +++ b/tests/erlang/examples-2.0/topological_sort.erl @@ -0,0 +1,66 @@ +-module(topological_sort). + +%% Copyright (C) 1998, Ericsson Computer Science Laboratory + +-doc([{author,'Joe Armstrong'}, + {title,"Topological sort of a partial order."}, + {keywords, [topological,sort,partial,order]}, + {date,981102}]). + + +-export([sort/1, test/0]). + +-import(lists, [map/2, member/2, filter/2]). + +%% -type([{X, X}]) -> {ok, [{X,Y}]} | {cycle, [{X,Y}]} +%% topological_sort:pairs(L) + +%% A partial order on the set S is a set of pairs {Xi,Xj} such that +%% some relation between Xi and Xj is obeyed. + +%% A topological sort of a partial order is a sequence of elements +%% [X1, X2, X3 ...] such that if whenever {Xi, Xj} is in the partial +%% order i < j + +test() -> + Pairs = [{1,2},{2,4},{4,6},{2,10},{4,8},{6,3},{1,3}, + {3,5},{5,8},{7,5},{7,9},{9,4},{9,10}], + sort(Pairs). + +%% [7,1,9,2,4,6,3,5,8,10] +%S tag1 +sort(Pairs) -> + iterate(Pairs, [], all(Pairs)). + +iterate([], L, All) -> + {ok, remove_duplicates(L ++ subtract(All, L))}; +iterate(Pairs, L, All) -> + case subtract(lhs(Pairs), rhs(Pairs)) of + [] -> + {cycle, Pairs}; + Lhs -> + iterate(remove_pairs(Lhs, Pairs), L ++ Lhs, All) + end. + +all(L) -> lhs(L) ++ rhs(L). +lhs(L) -> map(fun({X,_}) -> X end, L). +rhs(L) -> map(fun({_,Y}) -> Y end, L). + +%% subtract(L1, L2) -> all the elements in L1 which are not in L2 + +subtract(L1, L2) -> filter(fun(X) -> not member(X, L2) end, L1). + +remove_duplicates([H|T]) -> + case member(H, T) of + true -> remove_duplicates(T); + false -> [H|remove_duplicates(T)] + end; +remove_duplicates([]) -> + []. + +%% remove_pairs(L1, L2) -> L2' L1 = [X] L2 = [{X,Y}] +%% removes all pairs from L2 where the first element +%% of each pair is a member of L1 + +remove_pairs(L1, L2) -> filter(fun({X,Y}) -> not member(X, L1) end, L2). +%E tag1 diff --git a/tests/erlang/examples-2.0/transitive.erl b/tests/erlang/examples-2.0/transitive.erl new file mode 100644 index 0000000..9ececb2 --- /dev/null +++ b/tests/erlang/examples-2.0/transitive.erl @@ -0,0 +1,39 @@ +-module(transitive). +%% Copyright (C) 1997, Ericsson Telecom AB + +-doc([{author,'Joe Armstrong'}, + {title,"Transitive closure of a graph."}, + {keywords, [transitive,closure]}, + {date,981102}]). + +%% warning slow on big graphs + +-export([closure/2]). + +%S tag1 +closure(RootSet, Pairs) -> + closure_list(RootSet, Pairs, RootSet). + +closure(Start, [], L) -> + L; +closure(Start, Pairs, Reachable) -> + {Next, Rest} = next(Start, Pairs), + closure_list(Next, Rest, Next ++ Reachable). + +closure_list([], Pairs, Reachable) -> + Reachable; +closure_list([H|T], Pairs, Reachable) -> + Reachable1 = closure(H, Pairs, Reachable), + closure_list(T, Pairs, Reachable1). + +next(Start, Pairs) -> + next(Start, Pairs, [], []). + +next(Start, [], Reach, NoReach) -> + {Reach, NoReach}; +next(Start, [{Start,Next}|T], Reach, NoReach) -> + next(Start, T, [Next|Reach], NoReach); +next(Start, [H|T], Reach, NoReach) -> + next(Start, T, Reach, [H|NoReach]). +%E tag1 + diff --git a/tests/erlang/examples-2.0/users b/tests/erlang/examples-2.0/users new file mode 100644 index 0000000..b11ba0b --- /dev/null +++ b/tests/erlang/examples-2.0/users @@ -0,0 +1,3 @@ +{"joe", "zapme"}. +{"ftp","ftp"}. +{"jane","hospan"}. diff --git a/tests/fortran/block.f b/tests/fortran/block.f new file mode 100644 index 0000000..94b3a11 --- /dev/null +++ b/tests/fortran/block.f @@ -0,0 +1,7 @@ + block data + integer nmax + parameter (nmax=20) + real v(nmax), alpha, beta + common /vector/v,alpha,beta + data v/20*100.0/, alpha/3.14/, beta/2.71/ + end diff --git a/tests/fortran/fall1.f b/tests/fortran/fall1.f new file mode 100644 index 0000000..f37d3ce --- /dev/null +++ b/tests/fortran/fall1.f @@ -0,0 +1,150 @@ +c +c +c
+      module constants
+         integer, parameter :: np=2000, dbl=selected_real_kind(14,100)
+         real(dbl) :: g=9.807,dtmin=.001
+      end module constants
+c
+      program fall
+      use constants
+      implicit none
+c
+c   Program to calculate the dynamics of a falling body
+c
+c   John Mahaffy    4/15/95
+c
+c   Arrays:
+c     v   -   velocity at each time integration step
+c     z   -   height at  each time integration step
+c     t   -   time for each corresponding v and z
+c     zreal - Actual height at time t(i) for comparison with computed z
+c
+c   In this program, I am using allocatate just to save space in the
+c   executable program file (a.out).  No attempt is made to estimate a size.
+c   Module "constants" communicates between subroutines.
+c
+c
+      real(dbl), allocatable :: v(:),z(:),t(:), zreal(:)
+c
+      real(dbl) dt
+      integer nsteps
+c
+      allocate (v(np),z(np),t(np),zreal(np))
+c
+      call input(z,dt)
+      call odesolve(v,z,t,dt,nsteps)
+      call realans(t,z,nsteps,zreal)
+      call output (t,z,zreal,v,nsteps)
+      stop
+      end
+c
+      subroutine input (z,dt)
+      use constants
+      implicit none
+c
+c   Obtain user input for initial height and time step
+c
+c   John Mahaffy    4/15/95
+c
+c  Output Arguments:
+c     z(1)   -  initial height
+c     dt     -  integration time step
+c
+      real(dbl) z(*),dt
+c
+      write(6,'(a)',advance='no') ' Initial height (meters): '
+      read *, z(1)
+      write(6,'(a)',advance='no') 'Time step size (seconds): '
+      read *, dt
+      if(dt.le.0.) dt=dtmin
+      return
+      end
+c
+      subroutine odesolve(v,z,t,dt,nsteps)
+      use constants
+c
+c   Solve the Ordinary Differential Equation of motion for the body
+c
+c   John Mahaffy    4/15/95
+c
+c   Arguments:
+c   Input
+c     dt     -   timestep size
+c   Output:
+c     v    -   velocity
+c     z    -   height
+c     t    -   time
+c     nsteps - last step in the integration
+c
+      real (dbl) v(*),z(*),t(*),dt
+      integer i, nsteps
+c
+c   Solve the equation for a falling body
+c
+c     d v                    d z
+c     ---    =  - g          ---   =  v
+c     d t                    d t
+c
+c   Set remaining initial conditions:
+c
+      t(1)=0.
+      v(1)=0.
+c
+c     Now loop through time steps until z goes negative or we run out of space
+c
+      do 100 i=2,np
+         v(i)= v(i-1)-dt*g
+         z(i)= z(i-1)+dt*.5*(v(i)+v(i-1))
+         t(i)=t(i-1)+dt
+         if(z(i).lt.0.) go to 200
+c
+ 100     continue
+c
+      write(6,*) 'Ran out of space to continue integration'
+      write(6,*) ' Last height was ',z(np),' meters'
+      i=np
+ 200  nsteps=i
+c     return
+      end
+c
+      subroutine realans(t,z,nsteps,zreal)
+      use constants
+c
+c     Get the values of the analytic solution to the differential equation
+c     for each time point to check the numerical accuracy.
+c
+c   John Mahaffy    4/15/95
+c
+      real(dbl) t(*),z(*),zreal(*)
+      integer i,nsteps
+c
+      do 10 i=1,nsteps
+  10  zreal(i)=z(1)-.5*g*t(i)**2
+      return
+      end
+c
+      subroutine output(t,z,zreal,v,nsteps)
+      use constants, only : dbl
+      implicit none
+c
+c   Outputs the full results of the time integration
+c
+c   John Mahaffy    4/15/95
+c
+      real(dbl) v(*),z(*),t(*), zreal(*)
+      integer nsteps,i
+      print *, 'Results are in fall.output'
+      open (11,file='fall.output')
+      write(11,2000)
+      do 300 i=1,nsteps
+      write(11,2001) t(i),v(i),z(i),zreal(i)
+ 300  continue
+ 2000 format (33x,'Computed',8x,'Actual',/,
+     &        6x,'Time',9x,'Velocity', 8x,'Height',8x,'Height')
+ 2001 format (1x,1p,4e15.7)
+      return
+      end
+c
+c +c diff --git a/tests/fortran/htcoef.f b/tests/fortran/htcoef.f new file mode 100644 index 0000000..7e15598 --- /dev/null +++ b/tests/fortran/htcoef.f @@ -0,0 +1,55 @@ + program htcoef +c +c John Mahaffy, Penn State University, CmpSc 201 Example +c 1/26/96 +c + implicit none + real k,D,Pr,h,Nulam,Nuturb + real Re1,Re2,Re3,Re4 +c +c Calculate an approximation for heat transfer coefficients +c in a 1 inch pipe for several different Reynolds numbers +c +c An example of why you should learn to use subprograms +c +c h - heat transfer coefficient ( w/m**2/K)' +c Nulam - laminar Nusselt number +c Nuturb - Turbulent Nusselt number (Dittus-Boelter correlation) +c k - conductivity ( w/m/K)' +c D - hydraulic diameter (m) +c Re - Reynolds number +c Pr - Prandl number +c + data k,D,Pr/0.617,0.0254,1.0/, Nulam/4.0/ +c +c Each of the following blocks assigns a Reynolds number, calculates +c an associated Turbulent Nusselt number and calculates the heat +c transfer coefficient based on the maximum of Turbulent and Laminar +c Nusselt Numbers +c + Re1=10. + Nuturb=0.023*Re1**0.8*Pr**0.4 + h=k/D*max(Nulam,Nuturb) + print *, 'For Reynolds Number = ',Re1 + print *, 'Heat Transfer Coefficient is ',h,' w/m**2/K' +c + Re2=100. + Nuturb=0.023*Re2**0.8*Pr**0.4 + h=k/D*max(Nulam,Nuturb) + print *, 'For Reynolds Number = ',Re2 + print *, 'Heat Transfer Coefficient is ',h,' w/m**2/K' +c + Re3=1000. + Nuturb=0.023*Re3**0.8*Pr**0.4 + h=k/D*max(Nulam,Nuturb) + print *, 'For Reynolds Number = ',Re3 + print *, 'Heat Transfer Coefficient is ',h,' w/m**2/K' +c + Re4=10000. + Nuturb=0.023*Re4**0.8*Pr**0.4 + h=k/D*max(Nulam,Nuturb) + print *, 'For Reynolds Number = ',Re4 + print *, 'Heat Transfer Coefficient is ',h,' w/m**2/K' +c + stop + end diff --git a/tests/fortran/linint1.f b/tests/fortran/linint1.f new file mode 100644 index 0000000..c565643 --- /dev/null +++ b/tests/fortran/linint1.f @@ -0,0 +1,22 @@ + subroutine linint(x,y) +c +c Given a value of x return a value of y based on interpolation +c within a table of y values (ytab) evaluated at evenly spaced +c x points between xmin and xmax. +c +c John Mahaffy 2/12/95 +c + real ytab(11) + parameter (xmin=300.,dx=100.,xmax=xmin+10.*dx,rdx=1./dx) + data ytab/1.0,2.1,3.2,4.4,5.7,7.1,8.6,10.2,11.9,13.7,15.8/ + if(x.ge.xmin.and.x.le.xmax) then + i1= int((x-xmin)*rdx)+1 + x1=xmin+(i1-1)*dx + wx=(x-x1)*rdx + y=(1-wx)*ytab(i1)+wx*ytab(i1+1) + else + write(6,*) 'x = ', x, ' is out of table range' + stop + endif + return + end diff --git a/tests/fortran/plot2.f b/tests/fortran/plot2.f new file mode 100644 index 0000000..e57988d --- /dev/null +++ b/tests/fortran/plot2.f @@ -0,0 +1,209 @@ +c +cplot2.f +c +c
+
+      program plotit
+c
+c    Program to provide plots of Sin(x)
+c    Ascii Character plots go to terminal and file 'pplot.out'
+c
+c     John Mahaffy   2/1/95
+c
+      implicit none
+c
+c    The following line tells Fortran that "func" is a function or subroutine
+c    and not a variable
+c
+c
+      external func
+c
+c
+c   The next line is necessary to pass the location of the intrinsic sin
+c   function as an argument to a subprogram
+c
+c
+      intrinsic sin
+c
+c
+      call pplot(func,0.,6.)
+      print *, 'Hit the Enter key to continue'
+c
+      pause
+c
+      call gnuplot('x','y','sin(x)',func,0.0,6.0)
+      call gnuplot('x','y','Intrinsic sin(x)',sin,0.0,6.0)
+      stop
+      end
+      subroutine pplot(f,xmin,xmax)
+c
+c   Generate ASCII character plot to screen and file 'pplot.out'
+c
+      implicit none
+      character line*72
+      real x, f , xmin , xmax
+      integer ip,i,imax
+c
+c    The following line tells Fortran that f is a function or subroutine
+c    and not a variable
+c
+      external f
+c
+c   INPUT Arguments
+c
+c   f       -   function to be ploted
+c   xmin    -   minimum x value
+c   xmax    -   maximum x value
+c
+c   OTHER key variables
+c
+c   line    -   Character string loaded with a line of output
+c   ip      -   Position in line for a function value
+c
+      open (11,file='pplot.out')
+c
+c   Label values of the y axis
+c
+      line=' '
+      line(14:15)='-1'
+      line(65:65)='1'
+      write(*,*) line
+      write(11,*) line
+      line=' '
+      write(line(10:13),'(f4.1)') xmin
+c
+c   Draw the y axis
+c
+      line(15:40)='+----+----+----+----+----+'
+      line(41:65)=line(16:40)
+c
+c   Plot the value at x=0
+c
+      ip= nint(25*f(0.0))+40
+      line(ip:ip)='*'
+      write(*,*) line
+      write(11,*) line
+      line=' '
+      imax=nint((xmax-xmin)*10)
+c
+c     Limit output
+c
+      imax=min(1000,imax)
+c
+c    Loop through and plot points for other x values
+c
+      do 50 i=1,imax
+         x=.1*i
+         ip=nint(25*f(x))+40
+c 
+         if(mod(i,10).eq.0) then
+            write(line(10:13),'(f4.1)') x
+            line(40:40)='+'
+c
+         else
+c
+            line(10:13)=' '
+            line(40:40)='|'
+         endif
+         line(ip:ip)='*'
+         write(*,*) line
+         write(11,*) line
+ 50      line(ip:ip)=' '
+c
+      close (11)
+c
+      return
+      end
+      subroutine gnuplot(xlabel,ylabel,title,f,xmin,xmax)
+c
+c   Ship data to the public domain program "gnuplot" for plotting
+c
+      implicit none
+      character line*72,sq*1
+      real x,f,xmin,xmax,fx
+      character*(*) xlabel,ylabel,title
+      integer i,imax,lc
+      external f
+      data sq/''''/
+c
+c   INPUT Arguments
+c
+c   f       -   function to be ploted
+c   xmin    -   minimum x value
+c   xmax    -   maximum x value
+c   xlabel  -   Contains a label for the x-axis
+c   ylabel  -   Contains a label for the y-axis
+c   title   -   Contains a title for the plot
+c
+c   OTHER key variables
+c
+c   line   -   Contains a line of character data
+c
+c   Drive a separate true graphics program (gnuplot)
+c
+c   First set up the command file for gnuplot
+c   Run gnuplot interactively and use the "help" command to learn more
+c   about what I am doing.
+c
+      open (12,file='gnuxy')
+c
+c  UnComment the next line if you are on a NCSA/BYU Telnet Session
+c
+c     write(12,*) 'set terminal tek40xx'
+c
+      write(12,*) 'set data style lines'
+c     
+      lc=len(xlabel)
+c     
+      line='set xlabel '''//xlabel(1:lc)//sq
+      write(12,*)line
+c
+c   You don't really need to calculate the character variable length
+c   here.  The following works just fine because of the character*(*)
+c
+      line='set ylabel '''//ylabel//sq
+      write(12,*)line
+      line='set title '''//title//sq
+      write(12,*)line
+      write(12,*)'set nokey'
+      write(12,2000) xmin,xmax
+ 2000 format('set xrange [',f3.0,':',f3.0,']')
+      write(12,*) 'plot ''dataxy'' using 1:2'
+      write(12,*) 'pause 10'
+      close(12)
+c
+c   Generate x-y pairs for the graph
+c
+      open (12,file='dataxy')
+      imax=nint((xmax-xmin)*10)
+c
+c     Limit output
+c
+      imax=min(1000,imax)
+c
+      do 100 i=0,imax
+         x=.1*i
+         fx=f(x)
+         write(12,*) x,fx
+  100 continue
+      close(12)
+c
+c   Tell the system to run the program gnuplot
+c   This call works on either IBM RS6000 or Sun, but is not part of
+c   the Fortran standard.
+c   Comment out the line if you aren't at a terminal with graphics
+c
+      call system('gnuplot gnuxy')
+c
+      return
+c
+      end
+c
+      real function func(x)
+c
+      func=sin(x)
+      return
+      end
+c
+c +c diff --git a/tests/fortran/thcl.f b/tests/fortran/thcl.f new file mode 100644 index 0000000..72f0438 --- /dev/null +++ b/tests/fortran/thcl.f @@ -0,0 +1,73 @@ + function thcl(temp) +c +c function thcl evaluates the freon liquid thermal conductivity +c as a function of liquid enthalpy +c +c liquid temperature temp in (j/kg) +c thermal conductivity thcl in (w/m/k) +c +c +c + dimension tabl(4,26) + save ilast + data ilast/15/,ntab/26/ + data tabl/ + & 1.99826700E+02, 1.15267000E-01,-3.03660304E-04, 6.96601393E-07, + & 2.10937800E+02, 1.11979000E-01,-2.88180288E-04, 6.96601393E-07, + & 2.22048900E+02, 1.08863000E-01,-2.72700273E-04,-6.88501377E-07, + & 2.33160000E+02, 1.05748000E-01,-2.88000288E-04, 6.88501377E-07, + & 2.44271100E+02, 1.02633000E-01,-2.72700273E-04,-6.96601393E-07, + & 2.55382200E+02, 9.95170000E-02,-2.88180288E-04, 7.04701409E-07, + & 2.66493300E+02, 9.64020000E-02,-2.72520273E-04,-7.04701409E-07, + & 2.77604400E+02, 9.32870000E-02,-2.88180288E-04, 6.96822277E-07, + & 2.88715600E+02, 9.01710000E-02,-2.72695225E-04,-6.88955687E-07, + & 2.99826700E+02, 8.70560000E-02,-2.88005336E-04, 6.88955687E-07, + & 3.10937800E+02, 8.39410000E-02,-2.72695225E-04,-6.97055703E-07, + & 3.22048900E+02, 8.08250000E-02,-2.88185336E-04, 7.05155719E-07, + & 3.33160000E+02, 7.77100000E-02,-2.72515225E-04,-7.05155719E-07, + & 3.44271100E+02, 7.45950000E-02,-2.88185336E-04, 6.97055703E-07, + & 3.55382200E+02, 7.14790000E-02,-2.72695225E-04,-6.88955687E-07, + & 3.66493300E+02, 6.83640000E-02,-2.88005336E-04, 6.88955687E-07, + & 3.77604400E+02, 6.52490000E-02,-2.72695225E-04,-6.96822277E-07, + & 3.88715600E+02, 6.21330000E-02,-2.88180288E-04, 7.04701409E-07, + & 3.99826700E+02, 5.90180000E-02,-2.72520273E-04,-7.04701409E-07, + & 4.10937800E+02, 5.59030000E-02,-2.88180288E-04, 6.96601393E-07, + & 4.22048900E+02, 5.27870000E-02,-2.72700273E-04,-4.89240978E-06, + & 4.33160000E+02, 4.91530000E-02,-3.81420381E-04, 6.80401361E-07, + & 4.44271100E+02, 4.49990000E-02,-3.66300366E-04,-6.28561257E-06, + & 4.55382200E+02, 4.01530000E-02,-5.05980506E-04,-2.45592491E-05, + & 4.66493300E+02, 3.14990000E-02,-1.05174105E-03,-2.18924207E-04, + & 4.70937800E+02, 2.25000000E-02, 0.00000000E+00, 0.00000000E+00/ + x=temp +c Start the search from the last point of table use index +c + if (x.le.tabl(1,ilast+1)) then +c +c Search down the table from point of last use +c + do 20 i1=ilast,1,-1 + if(x.ge.tabl(1,i1)) go to 60 + 20 continue +c write(6,*) 'x = ', x, ' is below the table range' + i1=1 + go to 60 + else +c +c Search up the table from point of last use +c + do 40 i1=ilast+1,ntab-1 + if(x.le.tabl(1,i1+1)) go to 60 + 40 continue +c write(6,*) 'x = ', x, ' is above the table range' + i1=ntab-1 + go to 60 + endif +c +c Bounding points found, interpolate +c + 60 dx=(x-tabl(1,i1)) + thcl=tabl(2,i1)+tabl(3,i1)*dx+tabl(4,i1)*dx**2 + ilast=i1 + 120 continue + return + end diff --git a/tests/java/FlowSet.java b/tests/java/FlowSet.java new file mode 100644 index 0000000..f185522 --- /dev/null +++ b/tests/java/FlowSet.java @@ -0,0 +1,46 @@ +// This file is part of the Java Compiler Kit (JKit) +// +// The Java Compiler Kit is free software; you can +// redistribute it and/or modify it under the terms of the +// GNU General Public License as published by the Free Software +// Foundation; either version 2 of the License, or (at your +// option) any later version. +// +// The Java Compiler Kit is distributed in the hope +// that it will be useful, but WITHOUT ANY WARRANTY; without +// even the implied warranty of MERCHANTABILITY or FITNESS FOR +// A PARTICULAR PURPOSE. See the GNU General Public License +// for more details. +// +// You should have received a copy of the GNU General Public +// License along with the Java Compiler Kit; if not, +// write to the Free Software Foundation, Inc., 59 Temple Place, +// Suite 330, Boston, MA 02111-1307 USA +// +// (C) David James Pearce, 2009. + +package jkit.jil.dfa; + +public interface FlowSet { + + /** + * FlowSets must be cloneable to facilitate multiple flows of execution + * from conditionals + * + * @return A Clone of the current FlowSet + */ + public Object clone(); + + /** + * Computes the least upper bound of this flowset and that provided. NOTE + * the join operation has a subtle, yet important, requirement. If the + * result of the join must be equivalent to *this* flowset, then it must be + * the *same* flowset. + * + * @param s + * Another FlowSet to join with this + * @return true if this FlowSet has changed due to the computation, false + * otherwise + */ + public FlowSet join(FlowSet s); +} diff --git a/tests/java/JilBuilder.java b/tests/java/JilBuilder.java new file mode 100644 index 0000000..214ca12 --- /dev/null +++ b/tests/java/JilBuilder.java @@ -0,0 +1,1933 @@ +// This file is part of the Java Compiler Kit (JKit) +// +// The Java Compiler Kit is free software; you can +// redistribute it and/or modify it under the terms of the +// GNU General Public License as published by the Free Software +// Foundation; either version 2 of the License, or (at your +// option) any later version. +// +// The Java Compiler Kit is distributed in the hope +// that it will be useful, but WITHOUT ANY WARRANTY; without +// even the implied warranty of MERCHANTABILITY or FITNESS FOR +// A PARTICULAR PURPOSE. See the GNU General Public License +// for more details. +// +// You should have received a copy of the GNU General Public +// License along with the Java Compiler Kit; if not, +// write to the Free Software Foundation, Inc., 59 Temple Place, +// Suite 330, Boston, MA 02111-1307 USA +// +// (C) David James Pearce, 2009. + +package jkit.java.stages; + +import java.util.*; + +import jkit.compiler.FieldNotFoundException; +import static jkit.compiler.SyntaxError.*; +import static jkit.jil.util.Exprs.*; +import static jkit.jil.util.Types.*; +import jkit.compiler.ClassLoader; +import jkit.compiler.Clazz; +import jkit.compiler.SyntacticAttribute; +import jkit.java.io.JavaFile; +import jkit.java.tree.*; +import jkit.jil.tree.*; +import jkit.jil.tree.Type; +import jkit.jil.util.Types; +import jkit.util.Pair; +import jkit.util.Triple; + +/** + *

+ * The aim of this stage is to generate Jil code representing this class. + *

+ * + * @author djp + * + */ +public class JilBuilder { + private ClassLoader loader = null; + private TypeSystem types = null; + + /** + * This class is used to annotate an invoke expression with the list of + * checked (and possibly unchecked) exceptions it has declared to throw, as + * well as the type of the method to be invoked. It's sole purpose is to + * prevent us from having to re-traverse the class heirarchy during code + * generation. + * + * @author djp + * + */ + public static class MethodInfo implements SyntacticAttribute { + public final ArrayList exceptions; + public Type.Function type; + + public MethodInfo(List e, Type.Function type) { + exceptions = new ArrayList(e); + this.type = type; + } + } + + private static class Scope {} + + private static class LabelScope extends Scope { + public String label; + public LabelScope(String lab) { + label = lab; + } + } + + private static class SwitchScope extends Scope { + public String exitLab; + public SwitchScope(String el) { + exitLab = el; + } + } + + private static class LoopScope extends SwitchScope { + public String continueLab; + public LoopScope(String cl, String el) { + super(el); + continueLab = cl; + } + } + + private static class ClassScope extends Scope { + public Type.Clazz type; + + public ClassScope(Type.Clazz type) { + this.type = type; + } + } + + private final Stack scopes = new Stack(); + + public JilBuilder(ClassLoader loader, TypeSystem types) { + this.loader = loader; + this.types = types; + } + + public void apply(JavaFile file) { + // Now, traverse the declarations + for(Decl d : file.declarations()) { + doDeclaration(d, null); + } + } + + protected void doDeclaration(Decl d, JilClass parent) { + try { + if(d instanceof Decl.JavaInterface) { + doInterface((Decl.JavaInterface)d); + } else if(d instanceof Decl.JavaEnum) { + doEnum((Decl.JavaEnum)d); + } else if(d instanceof Decl.JavaClass) { + doClass((Decl.JavaClass)d); + } else if(d instanceof Decl.JavaMethod) { + doMethod((Decl.JavaMethod)d, parent); + } else if(d instanceof Decl.JavaField) { + doField((Decl.JavaField)d, parent); + } else if (d instanceof Decl.InitialiserBlock) { + doInitialiserBlock((Decl.InitialiserBlock)d , parent); + } else if (d instanceof Decl.StaticInitialiserBlock) { + doStaticInitialiserBlock((Decl.StaticInitialiserBlock) d, parent); + } else { + syntax_error("internal failure (unknown declaration \"" + d + + "\" encountered)", d); + } + } catch(Exception ex) { + internal_error(d,ex); + } + } + + protected void doInterface(Decl.JavaInterface d) throws ClassNotFoundException { + doClass(d); + } + + protected void doEnum(Decl.JavaEnum en) throws ClassNotFoundException { + doClass(en); + } + + protected void doClass(Decl.JavaClass c) throws ClassNotFoundException { + Type.Clazz type = c.attribute(Type.Clazz.class); + scopes.push(new ClassScope(type)); + // We, need to update the skeleton so that any methods and fields + // discovered below this are attributed to this class! + JilClass skeleton = (JilClass) loader.loadClass(type); + + // I do fields after everything else, so as to simplify the process + // of adding field initialisers to constructors. This is because it + // means I can be sure that the constructor has been otherwise + // completely generated, so all I need is to add initialisers at + // beginning, after super call (if there is one). + ArrayList fields = new ArrayList(); + for(Decl d : c.declarations()) { + if (!(d instanceof Decl.JavaField) + && !(d instanceof Decl.InitialiserBlock) + && !(d instanceof Decl.StaticInitialiserBlock)) { + doDeclaration(d, skeleton); + } else { + fields.add(d); + } + } + + // Note, I iterate the field declarations in reverse order to ensure + // that field initialisers are added to constructors in the right + // order. + for(int i=fields.size();i>0;--i) { + Decl d = fields.get(i-1); + doDeclaration(d, skeleton); + } + + scopes.pop(); + } + + protected void doMethod(Decl.JavaMethod d, JilClass parent) { + Type.Function type = d.attribute(Type.Function.class); + List stmts = doStatement(d.body()); + + // simple hack here, for case when no return statement is provided. + if (type.returnType() instanceof Type.Void + && (stmts.size() == 0 || !(stmts.get(stmts.size() - 1) instanceof JilStmt.Return))) { + stmts.add(new JilStmt.Return(null)); + } + + // First, off. If this is a constructor, then check whether there is an + // explicit super constructor call or not. If not, then add one. + if (d instanceof Decl.JavaConstructor) { + if(findSuperCall(stmts) == -1) { + stmts.add(0, new JilExpr.SpecialInvoke(new JilExpr.Variable("super", parent + .superClass()), "super", new ArrayList(), + new Type.Function(T_VOID), T_VOID)); + } + } + + // Now, add this statement list to the jil method representing this java + // method. + String name = d instanceof Decl.JavaConstructor ? parent.name() : d.name(); + for (JilMethod m : parent.methods()) { + if (m.name().equals(name) && m.type().equals(type)) { + m.body().addAll(stmts); + } + } + } + + protected void doField(Decl.JavaField d, JilClass parent) { + Pair> tmp = doExpression(d.initialiser()); + Type fieldT = d.type().attribute(Type.class); + boolean isStatic = d.isStatic(); + + if(tmp != null) { + if(d.isStatic()) { + if(!(d.isFinal() && d.isConstant())) { + // This is a static field with an non-constant initialiser. + // Therefore, we need to add it to the static initialiser. + JilMethod staticInit = createStaticInitialiser(parent); + JilExpr.Deref df = new JilExpr.Deref(new JilExpr.ClassVariable( + parent.type()), d.name(), isStatic, fieldT, d + .attributes()); + JilStmt.Assign ae = new JilStmt.Assign(df, tmp.first(), d + .attributes()); + // add them at the beginning to get the right ordering. + staticInit.body().add(0,ae); + staticInit.body().addAll(0,tmp.second()); + } + } else { + // This is a non-static field with an initialiser. Therefore, we + // need to add it to the beginning of all constructors. One issue is + // that, if the first statement of a constructor is a super call + // (which is should normally be), then we need to put the statements + // after that. + for(JilMethod m : parent.methods()) { + if(m.name().equals(parent.name())) { + List body = m.body(); + JilExpr.Deref df = new JilExpr.Deref(new JilExpr.Variable("this", + parent.type()), d.name(), isStatic, fieldT, d + .attributes()); + JilStmt.Assign ae = new JilStmt.Assign(df, tmp.first(), d + .attributes()); + int sc = findSuperCall(body)+1; + body.add(sc,ae); + body.addAll(sc,tmp.second()); + } + } + } + } + } + + protected void doInitialiserBlock(Decl.InitialiserBlock d, JilClass parent) { + ArrayList stmts = new ArrayList(); + + for (Stmt s : d.statements()) { + stmts.addAll(doStatement(s)); + } + + // This is a non-static initialiser block. Therefore, we + // need to add it to the beginning of all constructors. One issue is + // that, if the first statement of a constructor is a super call + // (which is should normally be), then we need to put the statements + // after that. + for(JilMethod m : parent.methods()) { + if(m.name().equals(parent.name())) { + List body = m.body(); + body.addAll(findSuperCall(body)+1,stmts); + } + } + } + + protected void doStaticInitialiserBlock(Decl.StaticInitialiserBlock d, JilClass parent) { + ArrayList stmts = new ArrayList(); + for (Stmt s : d.statements()) { + stmts.addAll(doStatement(s)); + } + + JilMethod m = createStaticInitialiser(parent); + // Again, add at beginning to ensure the right order. + m.body().addAll(0,stmts); + } + + protected List doStatement(Stmt e) { + try { + if(e instanceof Stmt.SynchronisedBlock) { + return doSynchronisedBlock((Stmt.SynchronisedBlock)e); + } else if(e instanceof Stmt.TryCatchBlock) { + return doTryCatchBlock((Stmt.TryCatchBlock)e); + } else if(e instanceof Stmt.Block) { + return doBlock((Stmt.Block)e); + } else if(e instanceof Stmt.VarDef) { + return doVarDef((Stmt.VarDef) e); + } else if(e instanceof Stmt.AssignmentOp) { + return doAssignmentOp((Stmt.AssignmentOp) e).second(); + } else if(e instanceof Stmt.Assignment) { + return doAssignment((Stmt.Assignment) e).second(); + } else if(e instanceof Stmt.Return) { + return doReturn((Stmt.Return) e); + } else if(e instanceof Stmt.Throw) { + return doThrow((Stmt.Throw) e); + } else if(e instanceof Stmt.Assert) { + return doAssert((Stmt.Assert) e); + } else if(e instanceof Stmt.Break) { + return doBreak((Stmt.Break) e); + } else if(e instanceof Stmt.Continue) { + return doContinue((Stmt.Continue) e); + } else if(e instanceof Stmt.Label) { + return doLabel((Stmt.Label) e); + } else if(e instanceof Stmt.If) { + return doIf((Stmt.If) e); + } else if(e instanceof Stmt.For) { + return doFor((Stmt.For) e); + } else if(e instanceof Stmt.ForEach) { + return doForEach((Stmt.ForEach) e); + } else if(e instanceof Stmt.While) { + return doWhile((Stmt.While) e); + } else if(e instanceof Stmt.DoWhile) { + return doDoWhile((Stmt.DoWhile) e); + } else if(e instanceof Stmt.Switch) { + return doSwitch((Stmt.Switch) e); + } else if(e instanceof Expr.Invoke) { + Pair> r = doInvoke((Expr.Invoke) e); + r.second().add((JilExpr.Invoke) r.first()); + return r.second(); + } else if(e instanceof Expr.New) { + Pair> r = doNew((Expr.New) e); + r.second().add((JilExpr.New) r.first()); + return r.second(); + } else if(e instanceof Decl.JavaClass) { + doClass((Decl.JavaClass)e); + return new ArrayList(); + } else if(e instanceof Stmt.PrePostIncDec) { + Pair> r = doExpression((Stmt.PrePostIncDec) e); + return r.second(); + } + } catch(Exception ex) { + internal_error(e,ex); + } + + if (e != null) { + syntax_error("Invalid statement encountered: " + e.getClass(), e); + } + + return new ArrayList(); + } + + protected List doBlock(Stmt.Block block) { + ArrayList r = new ArrayList(); + if(block != null) { + // now process every statement in this block. + for(Stmt s : block.statements()) { + r.addAll(doStatement(s)); + } + } + return r; + } + + protected List doCatchBlock(Stmt.CatchBlock block) { + ArrayList r = new ArrayList(); + if(block != null) { + // now process every statement in this block. + for(Stmt s : block.statements()) { + r.addAll(doStatement(s)); + } + } + return r; + } + + protected List doSynchronisedBlock(Stmt.SynchronisedBlock block) { + ArrayList r = new ArrayList(); + r.addAll(doBlock(block)); + doExpression(block.expr()); + // need to add synch enter and leave here ? + return r; + } + + protected int tryexit_label = 0; + protected int tryhandler_label = 0; + + protected List doTryCatchBlock(Stmt.TryCatchBlock block) { + String exitLab = "tryexit" + tryexit_label++; + ArrayList r = new ArrayList(); + r.addAll(doBlock(block)); + + int i = tryhandler_label; + for(Stmt.CatchBlock cb : block.handlers()) { + String handlerLab = "tryhandler" + i++; + for(int j=0;j!=r.size();++j) { + JilStmt s = r.get(j); + Type.Clazz ct = cb.type().attribute(Type.Clazz.class); + if(!(s instanceof JilStmt.Goto || s instanceof JilStmt.Label)) { + // This is very pedantic. Almost certainly, we could rule out + // certain kinds of exception branches. However, it's difficult + // to do this properly and, therefore, we remain quite + // conservative at this point. See the following paper for an + // excellent discussion of this: + // + // "Improving the precision and correctness of exception + // analysis in Soot", John Jorgensen, 2003. + r.set(j,s.addException(ct, handlerLab)); + } + } + } + + r.add(new JilStmt.Goto(exitLab,block.attributes())); + + for(Stmt.CatchBlock cb : block.handlers()) { + String handlerLab = "tryhandler" + tryhandler_label++; + Type.Clazz ct = cb.type().attribute(Type.Clazz.class); + r.add(new JilStmt.Label(handlerLab,cb.attributes())); + r.add(new JilStmt.Assign(new JilExpr.Variable(cb.variable(), ct, cb + .attributes()), new JilExpr.Variable("$", ct, + cb.attributes()))); + // At this point, we have a slightly interesting problem. We need to + // search + r.addAll(doCatchBlock(cb)); + r.add(new JilStmt.Goto(exitLab,cb.attributes())); + } + + r.add(new JilStmt.Label(exitLab,block.attributes())); + + List finallyBlock = doBlock(block.finaly()); + if(!finallyBlock.isEmpty()) { + // Now, we add the finally block. This is done in a separate method + // because it's actually quite challenging. + addFinallyBlock(r,finallyBlock); + } + + return r; + } + + protected static int finallyex_label = 0; + + protected void addFinallyBlock(List block, List finallyBlk) { + // So, to add the finally block properly, we need to iterate through the + // block and find any situations where we exit the block. This includes + // return statements, branches to labels which are not in the block, + // exceptional flow, and simply executing the last statement of the + // block. + + // First, collect all local labels, so we can distinguish local from + // non-local branches. + HashSet labels = new HashSet(); + for(JilStmt s : block) { + if(s instanceof JilStmt.Label) { + JilStmt.Label l = (JilStmt.Label) s; + labels.add(l.label()); + } + } + + String exceptionLabel = "finally" + finallyex_label++; + + // Now, iterate the block looking for non-local branches. + boolean lastNonBranch = false; + for(int i=0;i!=block.size();++i) { + lastNonBranch = true; + JilStmt stmt = block.get(i); + if(stmt instanceof JilStmt.Return) { + block.addAll(i,copyBlock(finallyBlk)); + i += finallyBlk.size(); + lastNonBranch = false; + } else if(stmt instanceof JilStmt.Goto) { + JilStmt.Goto g = (JilStmt.Goto) stmt; + if(!labels.contains(g.label())) { + // this is a non-local branch statement. + block.addAll(i,copyBlock(finallyBlk)); + i += finallyBlk.size(); + } + lastNonBranch = false; + } else if(stmt instanceof JilStmt.IfGoto) { + JilStmt.IfGoto g = (JilStmt.IfGoto) stmt; + + if(!labels.contains(g.label())) { + // houston, we have a problem. I'm not really sure how we + // can actually get here though. + throw new RuntimeException( + "An internal failure has occurred in JilBuilder. Please report it, along with the generating code!"); + } + } else if(!(stmt instanceof JilStmt.Label)){ + // Add the default exceptional edge for exceptional flow. + stmt = stmt.addException(Types.JAVA_LANG_THROWABLE, exceptionLabel); + block.set(i, stmt); + + // just for the (unlikely) case when last statement is a throw. + if(stmt instanceof JilStmt.Throw) { lastNonBranch = false; } + } + } + + String exitLabel = "finallyexit" + (finallyex_label-1); + if(lastNonBranch) { + block.addAll(finallyBlk); + block.add(new JilStmt.Goto(exitLabel)); + } + + // Now, process exceptional exit + block.add(new JilStmt.Label(exceptionLabel)); + block.add(new JilStmt.Assign(new JilExpr.Variable(exceptionLabel + "$", + Types.JAVA_LANG_THROWABLE), new JilExpr.Variable("$", + Types.JAVA_LANG_THROWABLE))); + block.addAll(copyBlock(finallyBlk)); + block.add(new JilStmt.Throw(new JilExpr.Variable(exceptionLabel + "$", + Types.JAVA_LANG_THROWABLE))); + + if(lastNonBranch) { + block.add(new JilStmt.Label(exitLabel)); + } + } + + protected static int copy_label = 0; + protected List copyBlock(List block) { + // The purpose of this method is to create a copy of the block. + // In particular, labels within the block must be copied. + + HashSet labels = new HashSet(); + for(JilStmt stmt : block) { + if(stmt instanceof JilStmt.Label) { + JilStmt.Label lab = (JilStmt.Label) stmt; + labels.add(lab.label()); + } + } + + ArrayList nblock = new ArrayList(); + for(JilStmt stmt : block) { + ArrayList> nexceptions = new ArrayList(); + + for(Pair p : stmt.exceptions()) { + String target = p.second(); + if(labels.contains(target)) { + target = target + "$copy" + copy_label; + } + nexceptions.add(new Pair(p.first(),target)); + } + + if(stmt instanceof JilStmt.Goto) { + JilStmt.Goto gto = (JilStmt.Goto) stmt; + String target = gto.label(); + if(labels.contains(target)) { + target = target + "$copy" + copy_label; + } + nblock.add(new JilStmt.Goto(target, nexceptions, + new ArrayList(stmt.attributes()))); + } else if(stmt instanceof JilStmt.IfGoto) { + JilStmt.IfGoto igto = (JilStmt.IfGoto) stmt; + String target = igto.label(); + if(labels.contains(target)) { + target = target + "$copy" + copy_label; + } + nblock.add(new JilStmt.IfGoto(igto.condition(), target, + nexceptions, + new ArrayList(stmt.attributes()))); + } else if(stmt instanceof JilStmt.Switch) { + JilStmt.Switch swt = (JilStmt.Switch) stmt; + ArrayList> ncases = new ArrayList(); + for(Pair c : swt.cases()) { + String target = c.second(); + if(labels.contains(target)) { + target = target + "$copy" + copy_label; + } + ncases.add(new Pair(c.first(),target)); + } + // And, don't forget the default label! + String deftarget = swt.defaultLabel(); + if(labels.contains(deftarget)) { + deftarget = deftarget + "$copy" + copy_label; + } + nblock.add(new JilStmt.Switch(swt.condition(), ncases, + deftarget, new ArrayList(swt.attributes()))); + } else if(stmt instanceof JilStmt.Label) { + JilStmt.Label lab = (JilStmt.Label) stmt; + String target = lab.label(); + if(labels.contains(target)) { + target = target + "$copy" + copy_label; + } + nblock.add(new JilStmt.Label(target, + new ArrayList(stmt.attributes()))); + } else { + // there is a bug relating to switch statements. + JilStmt nstmt = stmt.clearAddExceptions(nexceptions); + nblock.add(nstmt); + } + } + + copy_label = copy_label + 1; + + return nblock; + } + + protected List doVarDef(Stmt.VarDef def) { + Type type = def.type().attribute(Type.class); + List> defs = def.definitions(); + ArrayList r = new ArrayList(); + for(int i=0;i!=defs.size();++i) { + Triple d = defs.get(i); + Type nt = type; + + for(int j=0;j!=d.second();++j) { + nt = new Type.Array(nt); + } + + if(d.third() != null) { + Pair> e = doExpression(d.third()); + r.addAll(e.second()); + JilExpr lhs = new JilExpr.Variable(d.first(), nt, def + .attributes()); + r.add(new JilStmt.Assign(lhs, e.first())); + } + } + + return r; + } + + protected Pair> doAssignmentOp(Stmt.AssignmentOp def) { + ArrayList r = new ArrayList(); + + Pair> lhs = doExpression(def.lhs()); + Pair> rhs = doExpression(new Expr.BinOp( + def.op(), def.lhs(), def.rhs(), def.attributes())); + + Type lhs_t = lhs.first().type(); + + if (rhs.second().isEmpty() || lhs.first() instanceof JilExpr.Variable) { + JilExpr tmpVar = new JilExpr.Variable(getTempVar(), lhs_t, def + .attributes()); + r.add(new JilStmt.Assign(tmpVar, lhs.first(), def.attributes())); + r.addAll(rhs.second()); + r.add(new JilStmt.Assign(lhs.first(), rhs.first(), def + .attributes())); + return new Pair(lhs.first(), r); + } else if(lhs.first() instanceof JilExpr.Deref) { + JilExpr.Deref deref1 = (JilExpr.Deref) lhs.first(); + + if(deref1.target() instanceof JilExpr.ClassVariable) { + // Slightly awkward case, since this corresponds to a static + // class access and, hence, there are no possible side-effects. + // However, we cannot assign the target to a tmp variable, since + // it will not compile down to anything in practice. + r.addAll(rhs.second()); + r.add(new JilStmt.Assign(lhs.first(), rhs.first(), def + .attributes())); + return new Pair(lhs.first(), r); + } else { + + JilExpr tmpVar = new JilExpr.Variable(getTempVar(), deref1.target() + .type(), def.attributes()); + r.add(new JilStmt.Assign(tmpVar, deref1.target(), def.attributes())); + r.addAll(rhs.second()); + JilExpr.Deref deref2 = new JilExpr.Deref(tmpVar, deref1.name(), + deref1.isStatic(), deref1.type(), deref1.attributes()); + r + .add(new JilStmt.Assign(deref2, rhs.first(), def + .attributes())); + return new Pair(deref2, r); + } + } else if(lhs.first() instanceof JilExpr.ArrayIndex) { + JilExpr.ArrayIndex aindex1 = (JilExpr.ArrayIndex) lhs.first(); + + JilExpr targetVar = new JilExpr.Variable(getTempVar(), aindex1 + .target().type(), def.attributes()); + JilExpr indexVar = new JilExpr.Variable(getTempVar(), aindex1 + .index().type(), def.attributes()); + r.add(new JilStmt.Assign(targetVar, aindex1.target(), def + .attributes())); + r.add(new JilStmt.Assign(indexVar, aindex1.index(), def + .attributes())); + r.addAll(rhs.second()); + JilExpr.ArrayIndex aindex2 = new JilExpr.ArrayIndex(targetVar, + indexVar, aindex1.type(), aindex1.attributes()); + r + .add(new JilStmt.Assign(aindex2, rhs.first(), def + .attributes())); + return new Pair(aindex2, r); + } else { + syntax_error( + "unknown l-value encountered on assignment with side-effects", + def); + return null; // unreachable. + } + } + + protected Pair> doAssignment(Stmt.Assignment def) { + ArrayList r = new ArrayList(); + Pair> lhs = doExpression(def.lhs()); + Pair> rhs = doExpression(def.rhs()); + r.addAll(lhs.second()); + + if (rhs.second().isEmpty() || lhs.first() instanceof JilExpr.Variable) { + // easy case, no side-effects in rhs or trivial assignment. + r.addAll(rhs.second()); + r.add(new JilStmt.Assign(lhs.first(), rhs.first(), def + .attributes())); + return new Pair(lhs.first(), r); + } else if(lhs.first() instanceof JilExpr.Deref) { + + JilExpr.Deref deref1 = (JilExpr.Deref) lhs.first(); + + if(deref1.target() instanceof JilExpr.ClassVariable) { + // Slightly awkward case, since this corresponds to a static + // class access and, hence, there are no possible side-effects. + // However, we cannot assign the target to a tmp variable, since + // it will not compile down to anything in practice. + r.addAll(rhs.second()); + r.add(new JilStmt.Assign(lhs.first(), rhs.first(), def + .attributes())); + return new Pair(lhs.first(), r); + } else { + JilExpr tmpVar = new JilExpr.Variable(getTempVar(), deref1.target() + .type(), def.attributes()); + r.add(new JilStmt.Assign(tmpVar, deref1.target(), def.attributes())); + r.addAll(rhs.second()); + JilExpr.Deref deref2 = new JilExpr.Deref(tmpVar, deref1.name(), + deref1.isStatic(), deref1.type(), deref1.attributes()); + r.add(new JilStmt.Assign(deref2, rhs.first(), def + .attributes())); + return new Pair(deref2, r); + } + } else if(lhs.first() instanceof JilExpr.ArrayIndex) { + JilExpr.ArrayIndex aindex1 = (JilExpr.ArrayIndex) lhs.first(); + + JilExpr targetVar = new JilExpr.Variable(getTempVar(), aindex1 + .target().type(), def.attributes()); + JilExpr indexVar = new JilExpr.Variable(getTempVar(), aindex1 + .index().type(), def.attributes()); + r.add(new JilStmt.Assign(targetVar, aindex1.target(), def + .attributes())); + r.add(new JilStmt.Assign(indexVar, aindex1.index(), def + .attributes())); + r.addAll(rhs.second()); + JilExpr.ArrayIndex aindex2 = new JilExpr.ArrayIndex(targetVar, + indexVar, aindex1.type(), aindex1.attributes()); + r.add(new JilStmt.Assign(aindex2, rhs.first(), def.attributes())); + return new Pair(aindex2, r); + } else { + syntax_error( + "unknown l-value encountered on assignment with side-effects", + def); + return null; // unreachable. + } + } + + protected List doReturn(Stmt.Return ret) { + ArrayList r = new ArrayList(); + if(ret.expr() != null) { + Pair> expr = doExpression(ret.expr()); + r.addAll(expr.second()); + r.add(new JilStmt.Return(expr.first(),ret.attributes())); + } else { + r.add(new JilStmt.Return(null,ret.attributes())); + } + return r; + } + + protected List doThrow(Stmt.Throw ret) { + ArrayList r = new ArrayList(); + Pair> expr = doExpression(ret.expr()); + r.addAll(expr.second()); + r.add(new JilStmt.Throw(expr.first(),ret.attributes())); + return r; + } + + protected List doAssert(Stmt.Assert ret) { + ArrayList r = new ArrayList(); + Pair> expr = doExpression(ret.expr()); + + // need to do some real code generation here. + + return r; + } + + protected List doBreak(Stmt.Break brk) { + ArrayList r = new ArrayList(); + + if(brk.label() == null) { + SwitchScope ls = (SwitchScope) findEnclosingScope(SwitchScope.class); + if(ls == null) { + syntax_error("break used outside of loop or switch",brk); + } + r.add(new JilStmt.Goto(ls.exitLab,brk.attributes())); + } else { + String label = brk.label(); + LoopScope lastLoop = null; + for(int i=scopes.size()-1;i>=0;--i) { + Scope s = scopes.get(i); + if(s instanceof LoopScope) { + lastLoop = (LoopScope) s; + } else if(s instanceof LabelScope) { + LabelScope lab = (LabelScope) s; + if(lab.label.equals(label)) { + break; + } + } + } + if(lastLoop == null) { + syntax_error("no enclosing loop instance",brk); + } + r.add(new JilStmt.Goto(lastLoop.exitLab,brk.attributes())); + } + + return r; + } + + protected List doContinue(Stmt.Continue brk) { + ArrayList r = new ArrayList(); + + if(brk.label() == null) { + LoopScope ls = (LoopScope) findEnclosingScope(LoopScope.class); + + if(ls == null) { + syntax_error("continue used outside loop",brk); + } + + r.add(new JilStmt.Goto(ls.continueLab,brk.attributes())); + } else { + String label = brk.label(); + LoopScope lastLoop = null; + for(int i=scopes.size()-1;i>=0;--i) { + Scope s = scopes.get(i); + if(s instanceof LoopScope) { + lastLoop = (LoopScope) s; + } else if(s instanceof LabelScope) { + LabelScope lab = (LabelScope) s; + if(lab.label.equals(label)) { + break; + } + } + } + if(lastLoop == null) { + syntax_error("no enclosing loop instance",brk); + } + r.add(new JilStmt.Goto(lastLoop.continueLab,brk.attributes())); + } + + return r; + } + + protected List doLabel(Stmt.Label lab) { + scopes.push(new LabelScope(lab.label())); + List r = doStatement(lab.statement()); + scopes.pop(); + r.add(0, new JilStmt.Label(lab.label(), lab.attributes())); + return r; + } + + static protected int ifexit_label = 0; + static protected int iftrue_label = 0; + + protected List doIf(Stmt.If stmt) { + ArrayList r = new ArrayList(); + + Pair> cond = doExpression(stmt.condition()); + List tbranch = doStatement(stmt.trueStatement()); + List fbranch = doStatement(stmt.falseStatement()); + + r.addAll(cond.second()); + + if(stmt.falseStatement() == null) { + r.add(new JilStmt.IfGoto( + new JilExpr.UnOp(cond.first(), JilExpr.UnOp.NOT, T_BOOL, + stmt.condition().attributes()), "ifexit" + ifexit_label, stmt.attributes())); + r.addAll(tbranch); + } else if(stmt.trueStatement() == null) { + r.add(new JilStmt.IfGoto(cond.first(),"ifexit" + ifexit_label,stmt.attributes())); + r.addAll(fbranch); + } else { + r.add(new JilStmt.IfGoto(cond.first(),"iftrue" + iftrue_label,stmt.attributes())); + r.addAll(fbranch); + r.add(new JilStmt.Goto("ifexit" + ifexit_label,stmt.attributes())); + r.add(new JilStmt.Label("iftrue" + iftrue_label++,stmt.attributes())); + r.addAll(tbranch); + } + + r.add(new JilStmt.Label("ifexit" + ifexit_label++,stmt.attributes())); + return r; + + } + + static protected int whileheader_label = 0; + static protected int whileexit_label = 0; + + protected List doWhile(Stmt.While stmt) { + String headerLab = "whileheader" + whileheader_label++; + String exitLab = "whileexit" + whileexit_label++; + ArrayList r = new ArrayList(); + + r.add(new JilStmt.Label(headerLab, stmt + .attributes())); + Pair> cond = doExpression(stmt.condition()); + r.addAll(cond.second()); + r.add(new JilStmt.IfGoto(new JilExpr.UnOp(cond.first(), JilExpr.UnOp.NOT, + T_BOOL, stmt.condition().attributes()), exitLab, stmt + .attributes())); + scopes.push(new LoopScope(headerLab,exitLab)); + r.addAll(doStatement(stmt.body())); + scopes.pop(); + r.add(new JilStmt.Goto(headerLab, stmt + .attributes())); + r.add(new JilStmt.Label(exitLab, stmt + .attributes())); + + return r; + } + + static protected int dowhileheader_label = 0; + static protected int dowhileexit_label = 0; + + protected List doDoWhile(Stmt.DoWhile stmt) { + String headerLab = "dowhileheader" + dowhileheader_label++; + String exitLab = "dowhileexit" + dowhileexit_label++; + + ArrayList r = new ArrayList(); + + r.add(new JilStmt.Label(headerLab, stmt + .attributes())); + scopes.push(new LoopScope(headerLab,exitLab)); + r.addAll(doStatement(stmt.body())); + scopes.pop(); + Pair> cond = doExpression(stmt.condition()); + r.addAll(cond.second()); + r.add(new JilStmt.IfGoto(cond.first(), headerLab, stmt.attributes())); + r.add(new JilStmt.Label(exitLab, stmt + .attributes())); + return r; + } + + static protected int forheader_label = 0; + static protected int forinc_label = 0; + static protected int forexit_label = 0; + + protected List doFor(Stmt.For stmt) { + String headerLab = "forheader" + forheader_label++; + String exitLab = "forexit" + forexit_label++; + String incLab = "forinc" + forinc_label++; + + ArrayList r = new ArrayList(); + + if(stmt.initialiser() != null) { + r.addAll(doStatement(stmt.initialiser())); + } + + r.add(new JilStmt.Label(headerLab, stmt + .attributes())); + + if(stmt.condition() != null) { + Pair> cond = doExpression(stmt.condition()); + r.addAll(cond.second()); + r.add(new JilStmt.IfGoto(new JilExpr.UnOp(cond.first(), JilExpr.UnOp.NOT, + T_BOOL, stmt.condition().attributes()), exitLab, + stmt.attributes())); + } + + if(stmt.increment() != null) { + scopes.push(new LoopScope(incLab,exitLab)); + } else { + // this is a minor optimisation in the case that no increment is + // provided. + scopes.push(new LoopScope(headerLab,exitLab)); + } + r.addAll(doStatement(stmt.body())); + scopes.pop(); + + if(stmt.increment() != null) { + r.add(new JilStmt.Label(incLab)); + r.addAll(doStatement(stmt.increment())); + } + + r.add(new JilStmt.Goto(headerLab, stmt + .attributes())); + r.add(new JilStmt.Label(exitLab, stmt + .attributes())); + + return r; + } + + static protected int forallheader_label = 0; + static protected int forallexit_label = 0; + static protected int foralliter_label = 0; + static protected int forallinc_label = 0; + + protected List doForEach(Stmt.ForEach stmt) { + String headerLab = "forallheader" + forallheader_label++; + String exitLab = "forallexit" + forallexit_label++; + String iterLab = "foralliter" + foralliter_label++; + String incLab = "forallinc" + forallinc_label++; + + ArrayList stmts = new ArrayList(); + + Pair> src = doExpression(stmt.source()); + JilExpr.Variable loopVar = new JilExpr.Variable(stmt.var(), stmt.type() + .attribute(Type.class), stmt.attributes()); + + Type srcType = src.first().type(); + + stmts.addAll(src.second()); + JilExpr.Variable iter; + + if (srcType instanceof Type.Array) { + iter = new JilExpr.Variable(iterLab, T_INT); + stmts + .add(new JilStmt.Assign(iter, new JilExpr.Int(0), stmt + .attributes())); + } else { + // the following needs to be expanded upon, so as to include generic + // information on the iterator. The easiest way to do this is to + // look up the iterator() method in the src class, and use it's + // return type. + iter = new JilExpr.Variable(iterLab, JAVA_UTIL_ITERATOR); + + stmts.add(new JilStmt.Assign(iter, new JilExpr.Invoke(src.first(), + "iterator", new ArrayList(), new Type.Function( + JAVA_UTIL_ITERATOR), JAVA_UTIL_ITERATOR), stmt + .attributes())); + } + + stmts.add(new JilStmt.Label(headerLab, stmt + .attributes())); + + // Second, do condition + + if (srcType instanceof Type.Array) { + Type.Array arrType = (Type.Array) srcType; + JilExpr arrlength = new JilExpr.Deref(src.first(),"length",false,T_INT, stmt + .attributes()); + JilExpr gecmp = new JilExpr.BinOp(iter,arrlength,JilExpr.BinOp.GTEQ,T_BOOL, stmt + .attributes()); + stmts.add(new JilStmt.IfGoto(gecmp,exitLab, stmt + .attributes())); + + stmts.add(new JilStmt.Assign(loopVar, implicitCast(new JilExpr.ArrayIndex(src.first(), + iter, arrType.element()),loopVar.type()))); + } else { + JilExpr hasnext = new JilExpr.Invoke(iter, "hasNext", + new ArrayList(), new Type.Function(T_BOOL), + T_BOOL, stmt.attributes()); + stmts.add(new JilStmt.IfGoto(new JilExpr.UnOp(hasnext, JilExpr.UnOp.NOT, + T_BOOL), exitLab)); + + JilExpr cast; + if(loopVar.type() instanceof Type.Primitive) { + // In this case, we have to deal with casting and implicit + // conversion. + JilExpr next = new JilExpr.Invoke(iter, "next", new ArrayList(), + new Type.Function(JAVA_LANG_OBJECT), + boxedType((Type.Primitive) loopVar.type()), stmt.attributes()); + + cast = implicitCast(next, loopVar.type()); + } else { + JilExpr next = new JilExpr.Invoke(iter, "next", new ArrayList(), + new Type.Function(JAVA_LANG_OBJECT), + loopVar.type(), stmt.attributes()); + cast = new JilExpr.Cast(next, loopVar.type()); + } + stmts.add(new JilStmt.Assign(loopVar, cast, stmt.attributes())); + } + + // Third, do body + + if(srcType instanceof Type.Array) { + scopes.push(new LoopScope(incLab,exitLab)); + } else { + scopes.push(new LoopScope(headerLab,exitLab)); + } + stmts.addAll(doStatement(stmt.body())); + scopes.pop(); + + // Fourth, do increment + if (srcType instanceof Type.Array) { + stmts.add(new JilStmt.Label(incLab)); + forallinc_label++; + JilExpr.BinOp rhs = new JilExpr.BinOp(iter, new JilExpr.Int(1), + JilExpr.BinOp.ADD, T_INT, stmt.attributes()); + stmts.add(new JilStmt.Assign(iter,rhs,stmt.attributes())); + } + + stmts.add(new JilStmt.Goto(headerLab,stmt.attributes())); + + stmts.add(new JilStmt.Label(exitLab, stmt + .attributes())); + + return stmts; + } + + protected int switchcase_label = 0; + protected int switchexit_label = 0; + protected List doSwitch(Stmt.Switch sw) { + String switchExitLab = "switchexit" + switchexit_label++; + ArrayList r = new ArrayList(); + + Pair> cond = doExpression(sw.condition()); + ArrayList> cases = new ArrayList(); + ArrayList caseStmts = new ArrayList(); + String defaultLab = null; + for(Stmt.Case c : sw.cases()) { + Pair> ce = doExpression(c.condition()); + String caseLab = "switchcase" + switchcase_label++; + caseStmts.add(new JilStmt.Label(caseLab)); + scopes.push(new SwitchScope(switchExitLab)); + for(Stmt s : c.statements()) { + caseStmts.addAll(doStatement(s)); + } + scopes.pop(); + if(c.condition() != null) { + JilExpr ce_first = ce.first(); + if(ce_first instanceof JilExpr.Number) { + cases.add(new Pair(ce_first,caseLab)); + } else { + syntax_error("constant expression required",c.condition()); + } + } else { + defaultLab = caseLab; + } + } + if(defaultLab == null) { defaultLab = switchExitLab; } + r.addAll(cond.second()); + r.add(new JilStmt.Switch(cond.first(),cases,defaultLab)); + r.addAll(caseStmts); + r.add(new JilStmt.Label(switchExitLab)); + return r; + } + + protected Pair> doExpression(Expr e) { + try { + if(e instanceof Value.Bool) { + return doBoolVal((Value.Bool)e); + } else if(e instanceof Value.Byte) { + return doByteVal((Value.Byte)e); + } else if(e instanceof Value.Char) { + return doCharVal((Value.Char)e); + } else if(e instanceof Value.Short) { + return doShortVal((Value.Short)e); + } else if(e instanceof Value.Int) { + return doIntVal((Value.Int)e); + } else if(e instanceof Value.Long) { + return doLongVal((Value.Long)e); + } else if(e instanceof Value.Float) { + return doFloatVal((Value.Float)e); + } else if(e instanceof Value.Double) { + return doDoubleVal((Value.Double)e); + } else if(e instanceof Value.String) { + return doStringVal((Value.String)e); + } else if(e instanceof Value.Null) { + return doNullVal((Value.Null)e); + } else if(e instanceof Value.TypedArray) { + return doTypedArrayVal((Value.TypedArray)e); + } else if(e instanceof Value.Array) { + return doArrayVal((Value.Array)e); + } else if(e instanceof Value.Class) { + return doClassVal((Value.Class) e); + } else if(e instanceof Expr.LocalVariable) { + return doLocalVariable((Expr.LocalVariable)e); + } else if(e instanceof Expr.NonLocalVariable) { + return doNonLocalVariable((Expr.NonLocalVariable)e); + } else if(e instanceof Expr.ClassVariable) { + return doClassVariable((Expr.ClassVariable)e); + } else if(e instanceof Expr.UnOp) { + return doUnOp((Expr.UnOp)e); + } else if(e instanceof Expr.BinOp) { + return doBinOp((Expr.BinOp)e); + } else if(e instanceof Expr.TernOp) { + return doTernOp((Expr.TernOp)e); + } else if(e instanceof Expr.Cast) { + return doCast((Expr.Cast)e); + } else if(e instanceof Expr.Convert) { + return doConvert((Expr.Convert)e); + } else if(e instanceof Expr.InstanceOf) { + return doInstanceOf((Expr.InstanceOf)e); + } else if(e instanceof Expr.Invoke) { + return doInvoke((Expr.Invoke) e); + } else if(e instanceof Expr.New) { + return doNew((Expr.New) e); + } else if(e instanceof Expr.ArrayIndex) { + return doArrayIndex((Expr.ArrayIndex) e); + } else if(e instanceof Expr.Deref) { + return doDeref((Expr.Deref) e); + } else if(e instanceof Stmt.AssignmentOp) { + // force brackets + return doAssignmentOp((Stmt.AssignmentOp) e); + } else if(e instanceof Stmt.Assignment) { + // force brackets + return doAssignment((Stmt.Assignment) e); + } + } catch(Exception ex) { + internal_error(e,ex); + } + + if(e != null) { + syntax_error("Invalid expression encountered: " + + e.getClass(),e); + } + + return null; + } + + protected Pair> doDeref(Expr.Deref e) + throws ClassNotFoundException, FieldNotFoundException { + Pair> target = doExpression(e.target()); + Type type = e.attribute(Type.class); + Type.Reference _targetT = e.target().attribute(Type.Reference.class); + + if(_targetT instanceof Type.Clazz) { + Type.Clazz targetT = (Type.Clazz) _targetT; + if(e.name().equals("this")) { + // This is a special case, where we're trying to look up a field + // called "this". No such field can exist! What this means is that + // we're inside an inner class, and we're trying to access the this + // pointer of an enclosing class. + ClassScope cs = (ClassScope) findEnclosingScope(ClassScope.class); + int level = cs.type.components().size() - targetT.components().size(); + JilExpr r = new JilExpr.Variable("this",cs.type); + Type.Clazz t = cs.type; + while(level > 0) { + t = parentType(t); + r = new JilExpr.Deref(r,"this$0",false,t); + level = level - 1; + } + return new Pair(r, new ArrayList()); + } else { + Triple r = types + .resolveField(targetT, e.name(), loader); + + return new Pair>(new JilExpr.Deref(target.first(), e + .name(),r.second().isStatic(), type, e.attributes()), + target.second()); + } + } else if(_targetT instanceof Type.Array && e.name().equals("length")) { + return new Pair>(new JilExpr.Deref(target.first(), e + .name(), false, type, e.attributes()), + target.second()); + } else { + syntax_error("cannot dereference type " + _targetT,e); + } + return null; // dead code + } + + protected Pair> doArrayIndex(Expr.ArrayIndex e) { + Pair> target = doExpression(e.target()); + Pair> index = doExpression(e.index()); + Type type = e.attribute(Type.class); + + List r = target.second(); + + if(index.second().isEmpty()) { + // easy case when no side-effects in index expression. + + return new Pair>(new JilExpr.ArrayIndex(target.first(), + index.first(), type, e.attributes()), r); + } else { + // harder case, there are side-effects in the index expression. + JilExpr.Variable tmpVar = new JilExpr.Variable(getTempVar(), + target.first().type(), e.attributes()); + r.add(new JilStmt.Assign(tmpVar,target.first(),e.attributes())); + r.addAll(index.second()); + return new Pair>(new JilExpr.ArrayIndex(tmpVar, + index.first(), type, e.attributes()), r); + } + } + + protected Pair> doNew(Expr.New e) { + // Second, recurse through any parameters supplied ... + ArrayList r = new ArrayList(); + Type.Reference type = e.type().attribute(Type.Reference.class); + + MethodInfo mi = e.attribute(MethodInfo.class); + + Pair> context = doExpression(e.context()); + Pair,List> params = doExpressionList(e.parameters()); + + if(context != null) { + r.addAll(context.second()); + } + + r.addAll(params.second()); + + if(mi != null) { + return new Pair>(new JilExpr.New(type, params + .first(), mi.type, e.attributes()), r); + } else if(type instanceof Type.Array){ + return new Pair>(new JilExpr.New(type, + params.first(), new Type.Function(Types.T_VOID), e + .attributes()), r); + } else { + syntax_error("internal failure --- unable to find method information",e); + return null; + } + } + + protected Pair> doInvoke(Expr.Invoke e) { + ArrayList r = new ArrayList(); + Type type = e.attribute(Type.class); + MethodInfo mi = e.attribute(MethodInfo.class); + + Pair> target = doExpression(e.target()); + r.addAll(target.second()); + + Pair,List> params = doExpressionList(e.parameters()); + r.addAll(params.second()); + + JilExpr rec = target.first(); + + if (rec instanceof JilExpr.ClassVariable) { + return new Pair>(new JilExpr.Invoke(target.first(), e + .name(), params.first(), mi.type, type, e + .attributes()), r); + } else if (rec instanceof JilExpr.Variable + && ((JilExpr.Variable) rec).value().equals("super")) { + return new Pair>(new JilExpr.SpecialInvoke(target + .first(), e.name(), params.first(), mi.type, type, e + .attributes()), r); + } else if (rec.type() instanceof Type.Array && e.name().equals("clone")) { + // this is a special case for array cloning. It should really be in + // TypePropagation, but the problem is that implicit cast needs to + // be on the returned expression, which type propagation does not + // support. + JilExpr ie = new JilExpr.Invoke(target.first(), e.name(), params + .first(), mi.type, type, e.attributes()); + ie = new JilExpr.Cast(ie, rec.type()); + return new Pair>(ie, r); + } else { + return new Pair>(new JilExpr.Invoke(target.first(), e + .name(), params.first(), mi.type, type, e + .attributes()), r); + } + } + + protected Pair> doInstanceOf(Expr.InstanceOf e) { + Pair> lhs = doExpression(e.lhs()); + Type type = e.attribute(Type.class); + Type.Reference rhs = e.rhs().attribute(Type.Reference.class); + return new Pair>(new JilExpr.InstanceOf(lhs.first(), rhs, + type, e.attributes()), lhs.second()); + } + + protected Pair> doCast(Expr.Cast e) { + Pair> expr = doExpression(e.expr()); + Type type = e.attribute(Type.class); + return new Pair>(new JilExpr.Cast(expr.first(), + type, e.attributes()), expr.second()); + } + + protected Pair> doConvert(Expr.Convert e) { + Pair> expr = doExpression(e.expr()); + Type.Primitive type = e.attribute(Type.Primitive.class); + return new Pair>(new JilExpr.Convert(type, expr.first(), + e.attributes()), expr.second()); + } + + protected Pair> doBoolVal(Value.Bool e) { + return new Pair>(new JilExpr.Bool(e.value()), + new ArrayList()); + } + + protected Pair> doCharVal(Value.Char e) { + return new Pair>(new JilExpr.Char(e.value()), new ArrayList()); + } + + protected Pair> doByteVal(Value.Byte e) { + return new Pair>(new JilExpr.Byte(e.value()), new ArrayList()); + } + + protected Pair> doShortVal(Value.Short e) { + return new Pair>(new JilExpr.Short(e.value()), new ArrayList()); + } + + protected Pair> doIntVal(Value.Int e) { + return new Pair>(new JilExpr.Int(e.value()), new ArrayList()); + } + + protected Pair> doLongVal(Value.Long e) { + return new Pair>(new JilExpr.Long(e.value()), new ArrayList()); + } + + protected Pair> doFloatVal(Value.Float e) { + return new Pair>(new JilExpr.Float(e.value()), new ArrayList()); + } + + protected Pair> doDoubleVal(Value.Double e) { + return new Pair>(new JilExpr.Double(e.value()), new ArrayList()); + } + + protected Pair> doStringVal(Value.String e) { + return new Pair>(new JilExpr.StringVal(e.value()), new ArrayList()); + } + + protected Pair> doNullVal(Value.Null e) { + return new Pair>(new JilExpr.Null(), new ArrayList()); + } + + protected Pair> doTypedArrayVal(Value.TypedArray e) { + ArrayList r = new ArrayList(); + Type.Array type = e.attribute(Type.Array.class); + Pair,List> exprs = doExpressionList(e.values()); + r.addAll(exprs.second()); + return new Pair>(new JilExpr.Array( + exprs.first(), type, e.attributes()), r); + } + + protected Pair> doArrayVal(Value.Array e) { + ArrayList r = new ArrayList(); + Type.Array type = e.attribute(Type.Array.class); + Pair,List> exprs = doExpressionList(e.values()); + r.addAll(exprs.second()); + return new Pair>(new JilExpr.Array( + exprs.first(), type, e.attributes()), r); + } + + protected Pair> doClassVal(Value.Class e) { + Type classType = e.value().attribute(Type.class); + Type.Clazz type = e.attribute(Type.Clazz.class); + + if(type instanceof Type.Clazz) { + return new Pair>(new JilExpr.Class( + classType, type, e.attributes()), + new ArrayList()); + } else { + return new Pair>(new JilExpr.Class( + classType, JAVA_LANG_OBJECT, e.attributes()), + new ArrayList()); + } + } + + protected Pair> doLocalVariable( + Expr.LocalVariable e) { + Type type = e.attribute(Type.class); + return new Pair>(new JilExpr.Variable(e.value(), type, e + .attributes()), new ArrayList()); + } + + protected Pair> doNonLocalVariable( + Expr.NonLocalVariable e) { + syntax_error( + "internal failure (support for non-local variables not implemented!)", + e); + return null; + } + + protected Pair> doClassVariable(Expr.ClassVariable e) { + Type.Clazz type = e.attribute(Type.Clazz.class); + return new Pair>(new JilExpr.ClassVariable(type, e.attributes()), + new ArrayList()); + } + + protected Pair> doUnOp(Expr.UnOp e) { + Pair> r = doExpression(e.expr()); + Type.Primitive type = e.attribute(Type.Primitive.class); + List stmts = r.second(); + + switch (e.op()) { + case Expr.UnOp.PREDEC: + { + JilExpr lhs = r.first(); + JilExpr rhs = new JilExpr.BinOp(lhs, constant(1,lhs.type()), JilExpr.BinOp.SUB, + type, e.attributes()); + stmts.add(new JilStmt.Assign(lhs,rhs,e.attributes())); + return new Pair>(r.first(),stmts); + } + case Expr.UnOp.PREINC: + { + JilExpr lhs = r.first(); + JilExpr rhs = new JilExpr.BinOp(lhs, constant(1,lhs.type()), JilExpr.BinOp.ADD, + type, e.attributes()); + stmts.add(new JilStmt.Assign(lhs,rhs,e.attributes())); + return new Pair>(lhs,stmts); + } + case Expr.UnOp.POSTINC: + { + JilExpr lhs = r.first(); + JilExpr.Variable tmp = new JilExpr.Variable(getTempVar(),type,new ArrayList(lhs.attributes())); + stmts.add(new JilStmt.Assign(tmp,lhs,e.attributes())); + JilExpr rhs = new JilExpr.BinOp(lhs, constant(1,lhs.type()), JilExpr.BinOp.ADD, + type, e.attributes()); + stmts.add(new JilStmt.Assign(lhs,rhs,e.attributes())); + return new Pair>(tmp,stmts); + } + case Expr.UnOp.POSTDEC: + { + JilExpr lhs = r.first(); + JilExpr.Variable tmp = new JilExpr.Variable(getTempVar(),type,new ArrayList(lhs.attributes())); + stmts.add(new JilStmt.Assign(tmp,lhs,e.attributes())); + JilExpr rhs = new JilExpr.BinOp(lhs, constant(1,lhs.type()), JilExpr.BinOp.SUB, + type, e.attributes()); + stmts.add(new JilStmt.Assign(lhs,rhs,e.attributes())); + return new Pair>(tmp,stmts); + } + default: + return new Pair>(new JilExpr.UnOp(r.first(), e.op(), + type, e.attributes()), r.second()); + } + } + + protected Pair> doBinOp(Expr.BinOp e) { + + // First, check for special string concatenation operator. + if(e.op() == Expr.BinOp.CONCAT) { + return doStringConcat(e); + } + + Pair> lhs = doExpression(e.lhs()); + Pair> rhs = doExpression(e.rhs()); + + Type type = e.attribute(Type.class); + + if(type instanceof Type.Primitive) { + Type.Primitive ptype = (Type.Primitive) type; + List r = lhs.second(); + + if(rhs.second().isEmpty()) { + // This is the easy case: there are no side-effects in the rhs. + r.addAll(rhs.second()); + + return new Pair>(new JilExpr.BinOp(lhs.first(), rhs + .first(), e.op(), ptype, e.attributes()), r); + } else { + // This is the harder case: we have side-effects in the rhs. + // Now, to deal with this i'm going to be a little conservative + // and use a temporary variable. In some cases, it may be + // possible to avoid the temporary variable, but for simplicity + // I don't do this yet. [NOTE, IT's HARD TO AVOID THE TMP] + JilExpr.Variable tmpVar = new JilExpr.Variable(getTempVar(), + lhs.first().type(), e.attributes()); + r.add(new JilStmt.Assign(tmpVar,lhs.first(),e.attributes())); + r.addAll(rhs.second()); + return new Pair>(new JilExpr.BinOp(tmpVar, rhs + .first(), e.op(), ptype, e.attributes()), r); + } + } else { + syntax_error( + "internal failure --- problem processing binary operator (" + + lhs.first().type() + ")", e); + return null; + } + } + + protected static int stringbuilder_label = 0; + + protected Pair> doStringConcat(Expr.BinOp bop){ + + // This method is evidence as to why Java sucks as a programming + // language. It should be easy to construct datatypes, as I'm doing + // here, but lack of good notation makes it awkward in Java. Sure, there + // are some hacks to can do to improve the situation but basically it's + // screwed. + String builderLab = "$builder" + stringbuilder_label++; + Pair> lhs = doExpression(bop.lhs()); + Pair> rhs = doExpression(bop.rhs()); + + List stmts = lhs.second(); + stmts.addAll(rhs.second()); + + Type.Clazz builder = new Type.Clazz("java.lang", + "StringBuilder"); + + stmts.add(new JilStmt.Assign(new JilExpr.Variable(builderLab, builder), + new JilExpr.New(builder, new ArrayList(), + new Type.Function(T_VOID), bop.attributes()), bop + .attributes())); + + Type lhs_t = lhs.first().type(); + if (lhs_t instanceof Type.Primitive || isString(lhs_t)) { + ArrayList params = new ArrayList(); + params.add(lhs.first()); + stmts.add(new JilExpr.Invoke(new JilExpr.Variable(builderLab, + builder), "append", params, new Type.Function( + new Type.Clazz("java.lang", "StringBuilder"), lhs.first() + .type()), new Type.Clazz("java.lang", + "StringBuilder"), bop.attributes())); + } else { + ArrayList params = new ArrayList(); + params.add(lhs.first()); + stmts.add(new JilExpr.Invoke(new JilExpr.Variable(builderLab, + builder), "append", params, new Type.Function( + new Type.Clazz("java.lang", "StringBuilder"), + JAVA_LANG_OBJECT), new Type.Clazz("java.lang", + "StringBuilder"), bop.attributes())); + } + + // Now, do the right hand side + JilExpr r; + Type rhs_t = rhs.first().type(); + if(rhs_t instanceof Type.Primitive || isString(rhs_t)) { + ArrayList params = new ArrayList(); + params.add(rhs.first()); + r = new JilExpr.Invoke(new JilExpr.Variable(builderLab, builder), + "append", params, new Type.Function(new Type.Clazz( + "java.lang", "StringBuilder"), rhs_t), + new Type.Clazz("java.lang", "StringBuilder"), bop + .attributes()); + } else { + ArrayList params = new ArrayList(); + params.add(rhs.first()); + r = new JilExpr.Invoke(new JilExpr.Variable(builderLab, builder), + "append", params, new Type.Function(new Type.Clazz( + "java.lang", "StringBuilder"), JAVA_LANG_OBJECT), + new Type.Clazz("java.lang", "StringBuilder"), bop + .attributes()); + } + + r = new JilExpr.Invoke(r, "toString", new ArrayList(), + new Type.Function(JAVA_LANG_STRING), JAVA_LANG_STRING, bop + .attributes()); + + return new Pair>(r,stmts); + } + + protected int ternop_label = 0; + protected Pair> doTernOp(Expr.TernOp e) { + String trueLab = "$ternoptrue" + ternop_label; + String exitLab = "$ternopexit" + ternop_label++; + Type r_t = e.attribute(Type.class); + Pair> cond = doExpression(e.condition()); + Pair> tbranch = doExpression(e.trueBranch()); + Pair> fbranch = doExpression(e.falseBranch()); + ArrayList r = new ArrayList(); + JilExpr.Variable tmp = new JilExpr.Variable(getTempVar(),r_t,e.attributes()); + r.addAll(cond.second()); + r.add(new JilStmt.IfGoto(cond.first(),trueLab,e.attributes())); + r.addAll(fbranch.second()); + r.add(new JilStmt.Assign(tmp,fbranch.first(),e.attributes())); + r.add(new JilStmt.Goto(exitLab,e.attributes())); + r.add(new JilStmt.Label(trueLab,e.attributes())); + r.addAll(tbranch.second()); + r.add(new JilStmt.Assign(tmp,tbranch.first(),e.attributes())); + r.add(new JilStmt.Label(exitLab,e.attributes())); + return new Pair(tmp,r); + } + + /** + * The purpose of this method is to simplify the processing of an expression + * list. This is particular complex in the case of side-effecting + * statements. + */ + protected Pair,List> doExpressionList(List exprs) { + ArrayList nexprs = new ArrayList(); + ArrayList nstmts = new ArrayList(); + boolean hasSideEffects = false; + for(int i=exprs.size()-1;i>=0;--i) { + Expr p = exprs.get(i); + Pair> tmp = doExpression(p); + if(hasSideEffects) { + JilExpr.Variable var = new JilExpr.Variable(getTempVar(), tmp + .first().type(), p.attributes()); + nstmts.add(0,new JilStmt.Assign(var,tmp.first(),p.attributes())); + nexprs.add(0,var); + } else { + nexprs.add(0,tmp.first()); + } + nstmts.addAll(0,tmp.second()); + if(!tmp.second().isEmpty()) { + hasSideEffects=true; + } + } + return new Pair,List>(nexprs,nstmts); + } + + /** + * This method determines whether or not it is possible for a statement to + * throw a particular exception. Observe that this determination is from a + * static point of view; thus, it may be possible with more complex + * reasoning to determine that a statement cannot throw an exception. + * However, we need to be somewhat conservative here to ensure that the + * bytecode produced will in fact pass the JVM bytecode verifier. + * + * @param stmt + * @param exception + * @return + */ + protected boolean canThrowException(JilStmt stmt, Type.Clazz exception) + throws ClassNotFoundException { + + if (types.subtype(JAVA_LANG_VIRTUALMACHINEERROR, + exception, loader)) { + // must treat these exceptions very conservatively, spec JVM spec + // dictates they can occur at any point during exceptions. + return true; + } + // now, try to eliminate all statements which definitely cannot + // throw an exception. + if (stmt instanceof JilStmt.Goto || stmt instanceof JilStmt.Label + || stmt instanceof JilStmt.Nop) { + return false; + } + // Now, tackle the easier ones. + if (stmt instanceof JilStmt.Lock || stmt instanceof JilStmt.Unlock) { + // these statements can only throw null pointer exceptions. + // Actually, can also throw IllegaMonitorStateException + return types.subtype(exception, JAVA_LANG_NULLPOINTEREXCEPTION, + loader); + } + + if (stmt instanceof JilStmt.Throw) { + // this can throw null pointer exception if the argument is null. + // can also throw IllegalMonitorStateException (see JVM spec athrow) + JilStmt.Throw tr = (JilStmt.Throw) stmt; + return types.subtype(exception, tr.expr().type(), loader) + || types.subtype(exception, JAVA_LANG_NULLPOINTEREXCEPTION, + loader); + } + + ArrayList exprs = new ArrayList(); + if (stmt instanceof JilExpr.Invoke) { + // Some possible issue with respect to throwing Errors if + // this causes class loading. See JVM Section 2.17.5. + // + // Also, can throw IncompatibleClassChangeError, IllegalAccessError, + // AbstractMethodError, UnsatisfiedLinkError. + if (types.subtype(exception, JAVA_LANG_RUNTIMEEXCEPTION, loader) + || types.subtype(JAVA_LANG_RUNTIMEEXCEPTION, exception, + loader)) { + return true; + } + + // check declared exceptions + MethodInfo mi = stmt.attribute(MethodInfo.class); + for(Type.Clazz ex : mi.exceptions) { + if (types.subtype(exception, ex, loader)) { + return true; + } + } + + JilExpr.Invoke ivk = (JilExpr.Invoke) stmt; + exprs.addAll(ivk.parameters()); + } else if (stmt instanceof JilExpr.New) { + if (types.subtype(exception, JAVA_LANG_RUNTIMEEXCEPTION, loader) + || types.subtype(JAVA_LANG_RUNTIMEEXCEPTION, exception, loader)) { + return true; + } + JilExpr.New ivk = (JilExpr.New) stmt; + if (ivk.type() instanceof Type.Array + && types.subtype(exception, new Type.Clazz("java.lang", + "NegativeArraySizeException"), loader)) { + // In some cases, we can certain figure out that this cannot + // happen. + return true; + } else if(!(ivk.type() instanceof Type.Array)) { + // check declared exceptions + MethodInfo mi = ivk.attribute(MethodInfo.class); + for(Type.Clazz ex : mi.exceptions) { + if (types.subtype(exception, ex, loader)) { + return true; + } + } + } + + // Need to do something about checked exceptions. Also, if static + // method then cannot throw NullPointException + exprs.addAll(ivk.parameters()); + } else if(stmt instanceof JilStmt.Return) { + JilStmt.Return r = (JilStmt.Return) stmt; + // can also throw IllegalMonitorStateException (see JVM spec areturn) + if(r.expr() == null) { + return false; + } else { + exprs.add(r.expr()); + } + } else if(stmt instanceof JilStmt.Assign) { + JilStmt.Assign r = (JilStmt.Assign) stmt; + if (r.lhs() instanceof JilExpr.ArrayIndex + && types.subtype(exception, new Type.Clazz("java.lang", + "ArrayStoreException"), loader)) { + return true; + } + exprs.add(r.lhs()); + exprs.add(r.rhs()); + } else if(stmt instanceof JilStmt.IfGoto) { + JilStmt.IfGoto r = (JilStmt.IfGoto) stmt; + exprs.add(r.condition()); + } else if(stmt instanceof JilStmt.Switch) { + JilStmt.Switch r = (JilStmt.Switch) stmt; + exprs.add(r.condition()); + } + // Right, at this point, we have a bunch of expressions and we need to + // check whether or not any of these could throw the exception in + // question. + for(JilExpr e : exprs) { + if(canThrowException(e,exception)) { + return true; + } + } + return false; + } + + protected boolean canThrowException(JilExpr expr, Type.Clazz exception) + throws ClassNotFoundException { + // reuse code above if possible + if(expr instanceof JilExpr.Invoke || expr instanceof JilExpr.New) { + return canThrowException((JilStmt)expr,exception); + } + + // Again, build up an expression list to check + ArrayList exprs = new ArrayList(); + + if(expr instanceof JilExpr.Cast) { + JilExpr.Cast ec = (JilExpr.Cast) expr; + if ((ec.type() instanceof Type.Reference || ec.type() instanceof Type.Null) + && types.subtype(exception, new Type.Clazz("java.lang", + "ClassCastException"), loader)) { + return true; + } + exprs.add(ec.expr()); + } else if(expr instanceof JilExpr.BinOp) { + JilExpr.BinOp bop = (JilExpr.BinOp) expr; + if (!(bop.type() instanceof Type.Float || bop.type() instanceof Type.Double) + && (bop.op() == JilExpr.BinOp.DIV || bop.op() == JilExpr.BinOp.MOD) + && types.subtype(exception, JAVA_LANG_ARITHMETICEXCEPTION, + loader)) { + // Curiously, divide-by-zero is only a problem for integer types. + return true; + } + exprs.add(bop.lhs()); + exprs.add(bop.rhs()); + } else if(expr instanceof JilExpr.UnOp) { + JilExpr.UnOp bop = (JilExpr.UnOp) expr; + exprs.add(bop.expr()); + } else if(expr instanceof JilExpr.Deref) { + // Some possible issue with respect to throwing Errors if this + // instruction causes class loading. See JVM Section 2.17.5. + JilExpr.Deref def = (JilExpr.Deref) expr; + if (types.subtype(exception, JAVA_LANG_NULLPOINTEREXCEPTION, loader)) { + return true; + } + exprs.add(def.target()); + } else if(expr instanceof JilExpr.Array) { + JilExpr.Array arr = (JilExpr.Array) expr; + exprs.addAll(arr.values()); + } else if(expr instanceof JilExpr.ArrayIndex) { + JilExpr.ArrayIndex ai = (JilExpr.ArrayIndex) expr; + if (types.subtype(exception, JAVA_LANG_NULLPOINTEREXCEPTION, loader) + || types.subtype(exception, new Type.Clazz("java.lang", + "ArrayIndexOutOfBoundsException"), loader)) { + return true; + } + exprs.add(ai.target()); + exprs.add(ai.index()); + } else if(expr instanceof JilExpr.InstanceOf) { + JilExpr.InstanceOf iof = (JilExpr.InstanceOf) expr; + exprs.add(iof.lhs()); + } + + // Right, at this point, we have a bunch of expressions and we need to + // check whether or not any of these could throw the exception in + // question. + for (JilExpr e : exprs) { + if (canThrowException(e, exception)) { + return true; + } + } + + return false; + } + + protected Scope findEnclosingScope(Class c) { + for(int i=scopes.size()-1;i>=0;--i) { + Scope s = scopes.get(i); + if(c.isInstance(s)) { + return s; + } + } + return null; + } + + protected int findSuperCall(List stmts) { + int r = 0; + for(JilStmt stmt : stmts) { + if(stmt instanceof JilExpr.Invoke) { + JilExpr.Invoke sc = (JilExpr.Invoke) stmt; + if (sc.name().equals("super") || sc.name().equals("this")) { + return r; + } + } + r=r+1; + } + return -1; + } + + public JilMethod createStaticInitialiser(JilClass parent) { + List si = parent.methods(""); + if(si.size() == 0) { + ArrayList mods = new ArrayList(); + mods.add(Modifier.ACC_STATIC); + JilMethod r = new JilMethod("", new Type.Function( + Types.T_VOID), new ArrayList(), mods, + new ArrayList()); + r.body().add(new JilStmt.Return(null)); + parent.methods().add(r); + return r; + } else { + // It should be impossible to have more than one. + return si.get(0); + } + } + + protected JilExpr constant(int constant, Type t) { + if(t instanceof Type.Byte) { + return new JilExpr.Byte((byte)constant); + } else if(t instanceof Type.Char) { + return new JilExpr.Char((char)constant); + } else if(t instanceof Type.Short) { + return new JilExpr.Short((short)constant); + } else if(t instanceof Type.Int) { + return new JilExpr.Int(constant); + } else if(t instanceof Type.Long) { + return new JilExpr.Long((long)constant); + } else if(t instanceof Type.Float) { + return new JilExpr.Float((float)constant); + } else { + return new JilExpr.Double((double)constant); + } + } + + protected int tmp_label = 0; + protected String getTempVar() { + return "$tmp" + tmp_label++; + } + + /** + * Check wither a given type is a reference to java.lang.String or not. + * + * @param t + * @return + */ + protected static boolean isString(Type t) { + if(t instanceof Type.Clazz) { + Type.Clazz c = (Type.Clazz) t; + return c.pkg().equals("java.lang") && c.components().size() == 1 + && c.components().get(0).first().equals("String"); + } + return false; + } +} diff --git a/tests/java/ShipSquare.java b/tests/java/ShipSquare.java new file mode 100755 index 0000000..59a0267 --- /dev/null +++ b/tests/java/ShipSquare.java @@ -0,0 +1,40 @@ +import javax.swing.ImageIcon; + +/** + * A Ship square represents a square that contains a battle ship, but has not + * yet been bombed. Ship Squares can be either visible or invisible (depending + * on which side they are). + * + * @author djp + */ +public class ShipSquare extends GridSquare { + private BattleShip ship; + private Type type; + + public enum Type { + VERTICAL_TOP_END, + HORIZONTAL_MIDDLE + }; + + /** + * Construct a ShipSquare representing part of a battle ship (either a + * middle or end piece). + * + */ + public ShipSquare(Type type, BattleShip ship) { + this.type = type; + this.ship = ship; + } + + /** + * Get the ship that this square is part of. + * @return + */ + public BattleShip getShip() { return ship; } + + /** + * Determine what part of the ship this piece represents. + * @return + */ + public Type getType() { return type; } +} diff --git a/tests/javascript/backboje.js b/tests/javascript/backboje.js new file mode 100644 index 0000000..70e7d5d --- /dev/null +++ b/tests/javascript/backboje.js @@ -0,0 +1,16 @@ +$(function(){ + window.Todo = Backbone.Model.extend({ + defaults: function() { + return { + done: false, + order: Todos.nextOrder() + }; + }, + + // Toggle the `done` state of this todo item. + toggle: function() { + this.save({done: !this.get("done")}); + } + + }); +}); diff --git a/tests/javascript/class.js b/tests/javascript/class.js new file mode 100644 index 0000000..f0dae41 --- /dev/null +++ b/tests/javascript/class.js @@ -0,0 +1,83 @@ +// base class +var Animal = Class.create({ + initialize: function(name) { + this.name = name; + }, + name: "", + eat: function() { + return this.say("Yum!"); + }, + say: function(message) { + return this.name + ": " + message; + } +}); + +// subclass that augments a method +var Cat = Class.create(Animal, { + eat: function($super, food) { + if (food instanceof Mouse) return $super(); + else return this.say("Yuk! I only eat mice."); + } +}); + +// empty subclass +var Mouse = Class.create(Animal, {}); + +//mixins +var Sellable = { + getValue: function(pricePerKilo) { + return this.weight * pricePerKilo; + }, + + inspect: function() { + return '#'.interpolate(this); + } +}; + +var Reproduceable = { + reproduce: function(partner) { + if (partner.constructor != this.constructor || partner.sex == this.sex) + return null; + var weight = this.weight / 10, sex = Math.random(1).round() ? 'male' : 'female'; + return new this.constructor('baby', weight, sex); + } +}; + +// base class with mixin +var Plant = Class.create(Sellable, { + initialize: function(name, weight) { + this.name = name; + this.weight = weight; + }, + + inspect: function() { + return '#'.interpolate(this); + } +}); + +// subclass with mixin +var Dog = Class.create(Animal, Reproduceable, { + initialize: function($super, name, weight, sex) { + this.weight = weight; + this.sex = sex; + $super(name); + } +}); + +// subclass with mixins +var Ox = Class.create(Animal, Sellable, Reproduceable, { + initialize: function($super, name, weight, sex) { + this.weight = weight; + this.sex = sex; + $super(name); + }, + + eat: function(food) { + if (food instanceof Plant) + this.weight += food.weight; + }, + + inspect: function() { + return '#'.interpolate(this); + } +}); diff --git a/tests/javascript/jquery.ui.progressbar.js b/tests/javascript/jquery.ui.progressbar.js new file mode 100644 index 0000000..e3b25cf --- /dev/null +++ b/tests/javascript/jquery.ui.progressbar.js @@ -0,0 +1,107 @@ +/* + * jQuery UI Progressbar @VERSION + * + * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Progressbar + * + * Depends: + * jquery.ui.core.js + * jquery.ui.widget.js + */ +(function( $, undefined ) { + +$.widget( "ui.progressbar", { + options: { + value: 0, + max: 100 + }, + + min: 0, + + _create: function() { + this.element + .addClass( "ui-progressbar ui-widget ui-widget-content ui-corner-all" ) + .attr({ + role: "progressbar", + "aria-valuemin": this.min, + "aria-valuemax": this.options.max, + "aria-valuenow": this._value() + }); + + this.valueDiv = $( "
" ) + .appendTo( this.element ); + + this.oldValue = this._value(); + this._refreshValue(); + }, + + _destroy: function() { + this.element + .removeClass( "ui-progressbar ui-widget ui-widget-content ui-corner-all" ) + .removeAttr( "role" ) + .removeAttr( "aria-valuemin" ) + .removeAttr( "aria-valuemax" ) + .removeAttr( "aria-valuenow" ); + + this.valueDiv.remove(); + }, + + value: function( newValue ) { + if ( newValue === undefined ) { + return this._value(); + } + + this._setOption( "value", newValue ); + return this; + }, + + _setOption: function( key, value ) { + if ( key === "value" ) { + this.options.value = value; + this._refreshValue(); + if ( this._value() === this.options.max ) { + this._trigger( "complete" ); + } + } + + this._super( "_setOption", key, value ); + }, + + _value: function() { + var val = this.options.value; + // normalize invalid value + if ( typeof val !== "number" ) { + val = 0; + } + return Math.min( this.options.max, Math.max( this.min, val ) ); + }, + + _percentage: function() { + return 100 * this._value() / this.options.max; + }, + + _refreshValue: function() { + var value = this.value(); + var percentage = this._percentage(); + + if ( this.oldValue !== value ) { + this.oldValue = value; + this._trigger( "change" ); + } + + this.valueDiv + .toggle( value > this.min ) + .toggleClass( "ui-corner-right", value === this.options.max ) + .width( percentage.toFixed(0) + "%" ); + this.element.attr( "aria-valuenow", value ); + } +}); + +$.extend( $.ui.progressbar, { + version: "@VERSION" +}); + +})( jQuery ); diff --git a/tests/javascript/namespaces.js b/tests/javascript/namespaces.js new file mode 100644 index 0000000..c2eb486 --- /dev/null +++ b/tests/javascript/namespaces.js @@ -0,0 +1,27 @@ +/* Object Literal Notation */ +var objectLiteral = { + str: '1', + func: function() { return 1; } +}; + +/* Module Pattern 1 */ +var module = (function(){ + var private = 1; + return { + method: function() { private++; } + }; +})(); + +/* Module Pattern 2 */ +var module2 = {}; +(function(context){ + var private = 1; + context.method = function() { private++; } +})(module2); + +/* Module Pattern 3 */ +var module3 = {}; +(function(){ + var private = 1; + this.method = function() { private++; } +}).apply(module3); diff --git a/tests/javascript/oreilly1.js b/tests/javascript/oreilly1.js new file mode 100644 index 0000000..441a829 --- /dev/null +++ b/tests/javascript/oreilly1.js @@ -0,0 +1,42 @@ +// This example is from the book _JavaScript: The Definitive Guide_. +// Written by David Flanagan. Copyright (c) 1996 O'Reilly & Associates. +// This example is provided WITHOUT WARRANTY either expressed or implied. +// You may study, use, modify, and distribute it for any purpose. + +function Circle(radius) { // the constructor defines the class itself + // r is an instance variable; defined and initialized in the constructor + this.r = radius; +} + +// Circle.PI is a class variable--it is a property of the constructor function +Circle.PI = 3.14159; + +// Here is a function that computes a circle area. +function Circle_area() { return Circle.PI * this.r * this.r; } + +// Here we make the function into an instance method by assigning it +// to the prototype object of the constructor. Remember that we have to +// create and discard one object before the prototype object exists +new Circle(0); +Circle.prototype.area = Circle_area; + +// Here's another function. It takes two circle objects are arguments and +// returns the one that is larger (has the larger radius). +function Circle_max(a,b) { + if (a.r > b.r) return a; + else return b; +} + +// Since this function compares two circle objects, it doesn't make sense as +// an instance method operating on a single circle object. But we don't want +// it to be a stand-alone function either, so we make it into a class method +// by assigning it to the constructor function: +Circle.max = Circle_max; + +// Here is some code that uses each of these fields: +c = new Circle(1.0); // create an instance of the Circle class +c.r = 2.2; // set the r instance variable +a = c.area(); // invoke the area() instance method +x = Math.exp(Circle.PI); // use the PI class variable in our own computation. +d = new Circle(1.2); // create another Circle instance +bigger = Circle.max(c,d); // use the max() class method. diff --git a/tests/javascript/prototype.js b/tests/javascript/prototype.js new file mode 100644 index 0000000..04a4779 --- /dev/null +++ b/tests/javascript/prototype.js @@ -0,0 +1,6081 @@ +/* Prototype JavaScript framework, version 1.7 + * (c) 2005-2010 Sam Stephenson + * + * Prototype is freely distributable under the terms of an MIT-style license. + * For details, see the Prototype web site: http://www.prototypejs.org/ + * + *--------------------------------------------------------------------------*/ + +var Prototype = { + + Version: '1.7', + + Browser: (function(){ + var ua = navigator.userAgent; + var isOpera = Object.prototype.toString.call(window.opera) == '[object Opera]'; + return { + IE: !!window.attachEvent && !isOpera, + Opera: isOpera, + WebKit: ua.indexOf('AppleWebKit/') > -1, + Gecko: ua.indexOf('Gecko') > -1 && ua.indexOf('KHTML') === -1, + MobileSafari: /Apple.*Mobile/.test(ua) + } + })(), + + BrowserFeatures: { + XPath: !!document.evaluate, + + SelectorsAPI: !!document.querySelector, + + ElementExtensions: (function() { + var constructor = window.Element || window.HTMLElement; + return !!(constructor && constructor.prototype); + })(), + SpecificElementExtensions: (function() { + if (typeof window.HTMLDivElement !== 'undefined') + return true; + + var div = document.createElement('div'), + form = document.createElement('form'), + isSupported = false; + + if (div['__proto__'] && (div['__proto__'] !== form['__proto__'])) { + isSupported = true; + } + + div = form = null; + + return isSupported; + })() + }, + + ScriptFragment: ']*>([\\S\\s]*?)<\/script>', + JSONFilter: /^\/\*-secure-([\s\S]*)\*\/\s*$/, + + emptyFunction: function() { }, + + K: function(x) { return x } +}; + +if (Prototype.Browser.MobileSafari) + Prototype.BrowserFeatures.SpecificElementExtensions = false; +/* Based on Alex Arnell's inheritance implementation. */ + +var Class = (function() { + + var IS_DONTENUM_BUGGY = (function(){ + for (var p in { toString: 1 }) { + if (p === 'toString') return false; + } + return true; + })(); + + function subclass() {}; + function create() { + var parent = null, properties = $A(arguments); + if (Object.isFunction(properties[0])) + parent = properties.shift(); + + function klass() { + this.initialize.apply(this, arguments); + } + + Object.extend(klass, Class.Methods); + klass.superclass = parent; + klass.subclasses = []; + + if (parent) { + subclass.prototype = parent.prototype; + klass.prototype = new subclass; + parent.subclasses.push(klass); + } + + for (var i = 0, length = properties.length; i < length; i++) + klass.addMethods(properties[i]); + + if (!klass.prototype.initialize) + klass.prototype.initialize = Prototype.emptyFunction; + + klass.prototype.constructor = klass; + return klass; + } + + function addMethods(source) { + var ancestor = this.superclass && this.superclass.prototype, + properties = Object.keys(source); + + if (IS_DONTENUM_BUGGY) { + if (source.toString != Object.prototype.toString) + properties.push("toString"); + if (source.valueOf != Object.prototype.valueOf) + properties.push("valueOf"); + } + + for (var i = 0, length = properties.length; i < length; i++) { + var property = properties[i], value = source[property]; + if (ancestor && Object.isFunction(value) && + value.argumentNames()[0] == "$super") { + var method = value; + value = (function(m) { + return function() { return ancestor[m].apply(this, arguments); }; + })(property).wrap(method); + + value.valueOf = method.valueOf.bind(method); + value.toString = method.toString.bind(method); + } + this.prototype[property] = value; + } + + return this; + } + + return { + create: create, + Methods: { + addMethods: addMethods + } + }; +})(); +(function() { + + var _toString = Object.prototype.toString, + NULL_TYPE = 'Null', + UNDEFINED_TYPE = 'Undefined', + BOOLEAN_TYPE = 'Boolean', + NUMBER_TYPE = 'Number', + STRING_TYPE = 'String', + OBJECT_TYPE = 'Object', + FUNCTION_CLASS = '[object Function]', + BOOLEAN_CLASS = '[object Boolean]', + NUMBER_CLASS = '[object Number]', + STRING_CLASS = '[object String]', + ARRAY_CLASS = '[object Array]', + DATE_CLASS = '[object Date]', + NATIVE_JSON_STRINGIFY_SUPPORT = window.JSON && + typeof JSON.stringify === 'function' && + JSON.stringify(0) === '0' && + typeof JSON.stringify(Prototype.K) === 'undefined'; + + function Type(o) { + switch(o) { + case null: return NULL_TYPE; + case (void 0): return UNDEFINED_TYPE; + } + var type = typeof o; + switch(type) { + case 'boolean': return BOOLEAN_TYPE; + case 'number': return NUMBER_TYPE; + case 'string': return STRING_TYPE; + } + return OBJECT_TYPE; + } + + function extend(destination, source) { + for (var property in source) + destination[property] = source[property]; + return destination; + } + + function inspect(object) { + try { + if (isUndefined(object)) return 'undefined'; + if (object === null) return 'null'; + return object.inspect ? object.inspect() : String(object); + } catch (e) { + if (e instanceof RangeError) return '...'; + throw e; + } + } + + function toJSON(value) { + return Str('', { '': value }, []); + } + + function Str(key, holder, stack) { + var value = holder[key], + type = typeof value; + + if (Type(value) === OBJECT_TYPE && typeof value.toJSON === 'function') { + value = value.toJSON(key); + } + + var _class = _toString.call(value); + + switch (_class) { + case NUMBER_CLASS: + case BOOLEAN_CLASS: + case STRING_CLASS: + value = value.valueOf(); + } + + switch (value) { + case null: return 'null'; + case true: return 'true'; + case false: return 'false'; + } + + type = typeof value; + switch (type) { + case 'string': + return value.inspect(true); + case 'number': + return isFinite(value) ? String(value) : 'null'; + case 'object': + + for (var i = 0, length = stack.length; i < length; i++) { + if (stack[i] === value) { throw new TypeError(); } + } + stack.push(value); + + var partial = []; + if (_class === ARRAY_CLASS) { + for (var i = 0, length = value.length; i < length; i++) { + var str = Str(i, value, stack); + partial.push(typeof str === 'undefined' ? 'null' : str); + } + partial = '[' + partial.join(',') + ']'; + } else { + var keys = Object.keys(value); + for (var i = 0, length = keys.length; i < length; i++) { + var key = keys[i], str = Str(key, value, stack); + if (typeof str !== "undefined") { + partial.push(key.inspect(true)+ ':' + str); + } + } + partial = '{' + partial.join(',') + '}'; + } + stack.pop(); + return partial; + } + } + + function stringify(object) { + return JSON.stringify(object); + } + + function toQueryString(object) { + return $H(object).toQueryString(); + } + + function toHTML(object) { + return object && object.toHTML ? object.toHTML() : String.interpret(object); + } + + function keys(object) { + if (Type(object) !== OBJECT_TYPE) { throw new TypeError(); } + var results = []; + for (var property in object) { + if (object.hasOwnProperty(property)) { + results.push(property); + } + } + return results; + } + + function values(object) { + var results = []; + for (var property in object) + results.push(object[property]); + return results; + } + + function clone(object) { + return extend({ }, object); + } + + function isElement(object) { + return !!(object && object.nodeType == 1); + } + + function isArray(object) { + return _toString.call(object) === ARRAY_CLASS; + } + + var hasNativeIsArray = (typeof Array.isArray == 'function') + && Array.isArray([]) && !Array.isArray({}); + + if (hasNativeIsArray) { + isArray = Array.isArray; + } + + function isHash(object) { + return object instanceof Hash; + } + + function isFunction(object) { + return _toString.call(object) === FUNCTION_CLASS; + } + + function isString(object) { + return _toString.call(object) === STRING_CLASS; + } + + function isNumber(object) { + return _toString.call(object) === NUMBER_CLASS; + } + + function isDate(object) { + return _toString.call(object) === DATE_CLASS; + } + + function isUndefined(object) { + return typeof object === "undefined"; + } + + extend(Object, { + extend: extend, + inspect: inspect, + toJSON: NATIVE_JSON_STRINGIFY_SUPPORT ? stringify : toJSON, + toQueryString: toQueryString, + toHTML: toHTML, + keys: Object.keys || keys, + values: values, + clone: clone, + isElement: isElement, + isArray: isArray, + isHash: isHash, + isFunction: isFunction, + isString: isString, + isNumber: isNumber, + isDate: isDate, + isUndefined: isUndefined + }); +})(); +Object.extend(Function.prototype, (function() { + var slice = Array.prototype.slice; + + function update(array, args) { + var arrayLength = array.length, length = args.length; + while (length--) array[arrayLength + length] = args[length]; + return array; + } + + function merge(array, args) { + array = slice.call(array, 0); + return update(array, args); + } + + function argumentNames() { + var names = this.toString().match(/^[\s\(]*function[^(]*\(([^)]*)\)/)[1] + .replace(/\/\/.*?[\r\n]|\/\*(?:.|[\r\n])*?\*\//g, '') + .replace(/\s+/g, '').split(','); + return names.length == 1 && !names[0] ? [] : names; + } + + function bind(context) { + if (arguments.length < 2 && Object.isUndefined(arguments[0])) return this; + var __method = this, args = slice.call(arguments, 1); + return function() { + var a = merge(args, arguments); + return __method.apply(context, a); + } + } + + function bindAsEventListener(context) { + var __method = this, args = slice.call(arguments, 1); + return function(event) { + var a = update([event || window.event], args); + return __method.apply(context, a); + } + } + + function curry() { + if (!arguments.length) return this; + var __method = this, args = slice.call(arguments, 0); + return function() { + var a = merge(args, arguments); + return __method.apply(this, a); + } + } + + function delay(timeout) { + var __method = this, args = slice.call(arguments, 1); + timeout = timeout * 1000; + return window.setTimeout(function() { + return __method.apply(__method, args); + }, timeout); + } + + function defer() { + var args = update([0.01], arguments); + return this.delay.apply(this, args); + } + + function wrap(wrapper) { + var __method = this; + return function() { + var a = update([__method.bind(this)], arguments); + return wrapper.apply(this, a); + } + } + + function methodize() { + if (this._methodized) return this._methodized; + var __method = this; + return this._methodized = function() { + var a = update([this], arguments); + return __method.apply(null, a); + }; + } + + return { + argumentNames: argumentNames, + bind: bind, + bindAsEventListener: bindAsEventListener, + curry: curry, + delay: delay, + defer: defer, + wrap: wrap, + methodize: methodize + } +})()); + + + +(function(proto) { + + + function toISOString() { + return this.getUTCFullYear() + '-' + + (this.getUTCMonth() + 1).toPaddedString(2) + '-' + + this.getUTCDate().toPaddedString(2) + 'T' + + this.getUTCHours().toPaddedString(2) + ':' + + this.getUTCMinutes().toPaddedString(2) + ':' + + this.getUTCSeconds().toPaddedString(2) + 'Z'; + } + + + function toJSON() { + return this.toISOString(); + } + + if (!proto.toISOString) proto.toISOString = toISOString; + if (!proto.toJSON) proto.toJSON = toJSON; + +})(Date.prototype); + + +RegExp.prototype.match = RegExp.prototype.test; + +RegExp.escape = function(str) { + return String(str).replace(/([.*+?^=!:${}()|[\]\/\\])/g, '\\$1'); +}; +var PeriodicalExecuter = Class.create({ + initialize: function(callback, frequency) { + this.callback = callback; + this.frequency = frequency; + this.currentlyExecuting = false; + + this.registerCallback(); + }, + + registerCallback: function() { + this.timer = setInterval(this.onTimerEvent.bind(this), this.frequency * 1000); + }, + + execute: function() { + this.callback(this); + }, + + stop: function() { + if (!this.timer) return; + clearInterval(this.timer); + this.timer = null; + }, + + onTimerEvent: function() { + if (!this.currentlyExecuting) { + try { + this.currentlyExecuting = true; + this.execute(); + this.currentlyExecuting = false; + } catch(e) { + this.currentlyExecuting = false; + throw e; + } + } + } +}); +Object.extend(String, { + interpret: function(value) { + return value == null ? '' : String(value); + }, + specialChar: { + '\b': '\\b', + '\t': '\\t', + '\n': '\\n', + '\f': '\\f', + '\r': '\\r', + '\\': '\\\\' + } +}); + +Object.extend(String.prototype, (function() { + var NATIVE_JSON_PARSE_SUPPORT = window.JSON && + typeof JSON.parse === 'function' && + JSON.parse('{"test": true}').test; + + function prepareReplacement(replacement) { + if (Object.isFunction(replacement)) return replacement; + var template = new Template(replacement); + return function(match) { return template.evaluate(match) }; + } + + function gsub(pattern, replacement) { + var result = '', source = this, match; + replacement = prepareReplacement(replacement); + + if (Object.isString(pattern)) + pattern = RegExp.escape(pattern); + + if (!(pattern.length || pattern.source)) { + replacement = replacement(''); + return replacement + source.split('').join(replacement) + replacement; + } + + while (source.length > 0) { + if (match = source.match(pattern)) { + result += source.slice(0, match.index); + result += String.interpret(replacement(match)); + source = source.slice(match.index + match[0].length); + } else { + result += source, source = ''; + } + } + return result; + } + + function sub(pattern, replacement, count) { + replacement = prepareReplacement(replacement); + count = Object.isUndefined(count) ? 1 : count; + + return this.gsub(pattern, function(match) { + if (--count < 0) return match[0]; + return replacement(match); + }); + } + + function scan(pattern, iterator) { + this.gsub(pattern, iterator); + return String(this); + } + + function truncate(length, truncation) { + length = length || 30; + truncation = Object.isUndefined(truncation) ? '...' : truncation; + return this.length > length ? + this.slice(0, length - truncation.length) + truncation : String(this); + } + + function strip() { + return this.replace(/^\s+/, '').replace(/\s+$/, ''); + } + + function stripTags() { + return this.replace(/<\w+(\s+("[^"]*"|'[^']*'|[^>])+)?>|<\/\w+>/gi, ''); + } + + function stripScripts() { + return this.replace(new RegExp(Prototype.ScriptFragment, 'img'), ''); + } + + function extractScripts() { + var matchAll = new RegExp(Prototype.ScriptFragment, 'img'), + matchOne = new RegExp(Prototype.ScriptFragment, 'im'); + return (this.match(matchAll) || []).map(function(scriptTag) { + return (scriptTag.match(matchOne) || ['', ''])[1]; + }); + } + + function evalScripts() { + return this.extractScripts().map(function(script) { return eval(script) }); + } + + function escapeHTML() { + return this.replace(/&/g,'&').replace(//g,'>'); + } + + function unescapeHTML() { + return this.stripTags().replace(/</g,'<').replace(/>/g,'>').replace(/&/g,'&'); + } + + + function toQueryParams(separator) { + var match = this.strip().match(/([^?#]*)(#.*)?$/); + if (!match) return { }; + + return match[1].split(separator || '&').inject({ }, function(hash, pair) { + if ((pair = pair.split('='))[0]) { + var key = decodeURIComponent(pair.shift()), + value = pair.length > 1 ? pair.join('=') : pair[0]; + + if (value != undefined) value = decodeURIComponent(value); + + if (key in hash) { + if (!Object.isArray(hash[key])) hash[key] = [hash[key]]; + hash[key].push(value); + } + else hash[key] = value; + } + return hash; + }); + } + + function toArray() { + return this.split(''); + } + + function succ() { + return this.slice(0, this.length - 1) + + String.fromCharCode(this.charCodeAt(this.length - 1) + 1); + } + + function times(count) { + return count < 1 ? '' : new Array(count + 1).join(this); + } + + function camelize() { + return this.replace(/-+(.)?/g, function(match, chr) { + return chr ? chr.toUpperCase() : ''; + }); + } + + function capitalize() { + return this.charAt(0).toUpperCase() + this.substring(1).toLowerCase(); + } + + function underscore() { + return this.replace(/::/g, '/') + .replace(/([A-Z]+)([A-Z][a-z])/g, '$1_$2') + .replace(/([a-z\d])([A-Z])/g, '$1_$2') + .replace(/-/g, '_') + .toLowerCase(); + } + + function dasherize() { + return this.replace(/_/g, '-'); + } + + function inspect(useDoubleQuotes) { + var escapedString = this.replace(/[\x00-\x1f\\]/g, function(character) { + if (character in String.specialChar) { + return String.specialChar[character]; + } + return '\\u00' + character.charCodeAt().toPaddedString(2, 16); + }); + if (useDoubleQuotes) return '"' + escapedString.replace(/"/g, '\\"') + '"'; + return "'" + escapedString.replace(/'/g, '\\\'') + "'"; + } + + function unfilterJSON(filter) { + return this.replace(filter || Prototype.JSONFilter, '$1'); + } + + function isJSON() { + var str = this; + if (str.blank()) return false; + str = str.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@'); + str = str.replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']'); + str = str.replace(/(?:^|:|,)(?:\s*\[)+/g, ''); + return (/^[\],:{}\s]*$/).test(str); + } + + function evalJSON(sanitize) { + var json = this.unfilterJSON(), + cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g; + if (cx.test(json)) { + json = json.replace(cx, function (a) { + return '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); + }); + } + try { + if (!sanitize || json.isJSON()) return eval('(' + json + ')'); + } catch (e) { } + throw new SyntaxError('Badly formed JSON string: ' + this.inspect()); + } + + function parseJSON() { + var json = this.unfilterJSON(); + return JSON.parse(json); + } + + function include(pattern) { + return this.indexOf(pattern) > -1; + } + + function startsWith(pattern) { + return this.lastIndexOf(pattern, 0) === 0; + } + + function endsWith(pattern) { + var d = this.length - pattern.length; + return d >= 0 && this.indexOf(pattern, d) === d; + } + + function empty() { + return this == ''; + } + + function blank() { + return /^\s*$/.test(this); + } + + function interpolate(object, pattern) { + return new Template(this, pattern).evaluate(object); + } + + return { + gsub: gsub, + sub: sub, + scan: scan, + truncate: truncate, + strip: String.prototype.trim || strip, + stripTags: stripTags, + stripScripts: stripScripts, + extractScripts: extractScripts, + evalScripts: evalScripts, + escapeHTML: escapeHTML, + unescapeHTML: unescapeHTML, + toQueryParams: toQueryParams, + parseQuery: toQueryParams, + toArray: toArray, + succ: succ, + times: times, + camelize: camelize, + capitalize: capitalize, + underscore: underscore, + dasherize: dasherize, + inspect: inspect, + unfilterJSON: unfilterJSON, + isJSON: isJSON, + evalJSON: NATIVE_JSON_PARSE_SUPPORT ? parseJSON : evalJSON, + include: include, + startsWith: startsWith, + endsWith: endsWith, + empty: empty, + blank: blank, + interpolate: interpolate + }; +})()); + +var Template = Class.create({ + initialize: function(template, pattern) { + this.template = template.toString(); + this.pattern = pattern || Template.Pattern; + }, + + evaluate: function(object) { + if (object && Object.isFunction(object.toTemplateReplacements)) + object = object.toTemplateReplacements(); + + return this.template.gsub(this.pattern, function(match) { + if (object == null) return (match[1] + ''); + + var before = match[1] || ''; + if (before == '\\') return match[2]; + + var ctx = object, expr = match[3], + pattern = /^([^.[]+|\[((?:.*?[^\\])?)\])(\.|\[|$)/; + + match = pattern.exec(expr); + if (match == null) return before; + + while (match != null) { + var comp = match[1].startsWith('[') ? match[2].replace(/\\\\]/g, ']') : match[1]; + ctx = ctx[comp]; + if (null == ctx || '' == match[3]) break; + expr = expr.substring('[' == match[3] ? match[1].length : match[0].length); + match = pattern.exec(expr); + } + + return before + String.interpret(ctx); + }); + } +}); +Template.Pattern = /(^|.|\r|\n)(#\{(.*?)\})/; + +var $break = { }; + +var Enumerable = (function() { + function each(iterator, context) { + var index = 0; + try { + this._each(function(value) { + iterator.call(context, value, index++); + }); + } catch (e) { + if (e != $break) throw e; + } + return this; + } + + function eachSlice(number, iterator, context) { + var index = -number, slices = [], array = this.toArray(); + if (number < 1) return array; + while ((index += number) < array.length) + slices.push(array.slice(index, index+number)); + return slices.collect(iterator, context); + } + + function all(iterator, context) { + iterator = iterator || Prototype.K; + var result = true; + this.each(function(value, index) { + result = result && !!iterator.call(context, value, index); + if (!result) throw $break; + }); + return result; + } + + function any(iterator, context) { + iterator = iterator || Prototype.K; + var result = false; + this.each(function(value, index) { + if (result = !!iterator.call(context, value, index)) + throw $break; + }); + return result; + } + + function collect(iterator, context) { + iterator = iterator || Prototype.K; + var results = []; + this.each(function(value, index) { + results.push(iterator.call(context, value, index)); + }); + return results; + } + + function detect(iterator, context) { + var result; + this.each(function(value, index) { + if (iterator.call(context, value, index)) { + result = value; + throw $break; + } + }); + return result; + } + + function findAll(iterator, context) { + var results = []; + this.each(function(value, index) { + if (iterator.call(context, value, index)) + results.push(value); + }); + return results; + } + + function grep(filter, iterator, context) { + iterator = iterator || Prototype.K; + var results = []; + + if (Object.isString(filter)) + filter = new RegExp(RegExp.escape(filter)); + + this.each(function(value, index) { + if (filter.match(value)) + results.push(iterator.call(context, value, index)); + }); + return results; + } + + function include(object) { + if (Object.isFunction(this.indexOf)) + if (this.indexOf(object) != -1) return true; + + var found = false; + this.each(function(value) { + if (value == object) { + found = true; + throw $break; + } + }); + return found; + } + + function inGroupsOf(number, fillWith) { + fillWith = Object.isUndefined(fillWith) ? null : fillWith; + return this.eachSlice(number, function(slice) { + while(slice.length < number) slice.push(fillWith); + return slice; + }); + } + + function inject(memo, iterator, context) { + this.each(function(value, index) { + memo = iterator.call(context, memo, value, index); + }); + return memo; + } + + function invoke(method) { + var args = $A(arguments).slice(1); + return this.map(function(value) { + return value[method].apply(value, args); + }); + } + + function max(iterator, context) { + iterator = iterator || Prototype.K; + var result; + this.each(function(value, index) { + value = iterator.call(context, value, index); + if (result == null || value >= result) + result = value; + }); + return result; + } + + function min(iterator, context) { + iterator = iterator || Prototype.K; + var result; + this.each(function(value, index) { + value = iterator.call(context, value, index); + if (result == null || value < result) + result = value; + }); + return result; + } + + function partition(iterator, context) { + iterator = iterator || Prototype.K; + var trues = [], falses = []; + this.each(function(value, index) { + (iterator.call(context, value, index) ? + trues : falses).push(value); + }); + return [trues, falses]; + } + + function pluck(property) { + var results = []; + this.each(function(value) { + results.push(value[property]); + }); + return results; + } + + function reject(iterator, context) { + var results = []; + this.each(function(value, index) { + if (!iterator.call(context, value, index)) + results.push(value); + }); + return results; + } + + function sortBy(iterator, context) { + return this.map(function(value, index) { + return { + value: value, + criteria: iterator.call(context, value, index) + }; + }).sort(function(left, right) { + var a = left.criteria, b = right.criteria; + return a < b ? -1 : a > b ? 1 : 0; + }).pluck('value'); + } + + function toArray() { + return this.map(); + } + + function zip() { + var iterator = Prototype.K, args = $A(arguments); + if (Object.isFunction(args.last())) + iterator = args.pop(); + + var collections = [this].concat(args).map($A); + return this.map(function(value, index) { + return iterator(collections.pluck(index)); + }); + } + + function size() { + return this.toArray().length; + } + + function inspect() { + return '#'; + } + + + + + + + + + + return { + each: each, + eachSlice: eachSlice, + all: all, + every: all, + any: any, + some: any, + collect: collect, + map: collect, + detect: detect, + findAll: findAll, + select: findAll, + filter: findAll, + grep: grep, + include: include, + member: include, + inGroupsOf: inGroupsOf, + inject: inject, + invoke: invoke, + max: max, + min: min, + partition: partition, + pluck: pluck, + reject: reject, + sortBy: sortBy, + toArray: toArray, + entries: toArray, + zip: zip, + size: size, + inspect: inspect, + find: detect + }; +})(); + +function $A(iterable) { + if (!iterable) return []; + if ('toArray' in Object(iterable)) return iterable.toArray(); + var length = iterable.length || 0, results = new Array(length); + while (length--) results[length] = iterable[length]; + return results; +} + + +function $w(string) { + if (!Object.isString(string)) return []; + string = string.strip(); + return string ? string.split(/\s+/) : []; +} + +Array.from = $A; + + +(function() { + var arrayProto = Array.prototype, + slice = arrayProto.slice, + _each = arrayProto.forEach; // use native browser JS 1.6 implementation if available + + function each(iterator, context) { + for (var i = 0, length = this.length >>> 0; i < length; i++) { + if (i in this) iterator.call(context, this[i], i, this); + } + } + if (!_each) _each = each; + + function clear() { + this.length = 0; + return this; + } + + function first() { + return this[0]; + } + + function last() { + return this[this.length - 1]; + } + + function compact() { + return this.select(function(value) { + return value != null; + }); + } + + function flatten() { + return this.inject([], function(array, value) { + if (Object.isArray(value)) + return array.concat(value.flatten()); + array.push(value); + return array; + }); + } + + function without() { + var values = slice.call(arguments, 0); + return this.select(function(value) { + return !values.include(value); + }); + } + + function reverse(inline) { + return (inline === false ? this.toArray() : this)._reverse(); + } + + function uniq(sorted) { + return this.inject([], function(array, value, index) { + if (0 == index || (sorted ? array.last() != value : !array.include(value))) + array.push(value); + return array; + }); + } + + function intersect(array) { + return this.uniq().findAll(function(item) { + return array.detect(function(value) { return item === value }); + }); + } + + + function clone() { + return slice.call(this, 0); + } + + function size() { + return this.length; + } + + function inspect() { + return '[' + this.map(Object.inspect).join(', ') + ']'; + } + + function indexOf(item, i) { + i || (i = 0); + var length = this.length; + if (i < 0) i = length + i; + for (; i < length; i++) + if (this[i] === item) return i; + return -1; + } + + function lastIndexOf(item, i) { + i = isNaN(i) ? this.length : (i < 0 ? this.length + i : i) + 1; + var n = this.slice(0, i).reverse().indexOf(item); + return (n < 0) ? n : i - n - 1; + } + + function concat() { + var array = slice.call(this, 0), item; + for (var i = 0, length = arguments.length; i < length; i++) { + item = arguments[i]; + if (Object.isArray(item) && !('callee' in item)) { + for (var j = 0, arrayLength = item.length; j < arrayLength; j++) + array.push(item[j]); + } else { + array.push(item); + } + } + return array; + } + + Object.extend(arrayProto, Enumerable); + + if (!arrayProto._reverse) + arrayProto._reverse = arrayProto.reverse; + + Object.extend(arrayProto, { + _each: _each, + clear: clear, + first: first, + last: last, + compact: compact, + flatten: flatten, + without: without, + reverse: reverse, + uniq: uniq, + intersect: intersect, + clone: clone, + toArray: clone, + size: size, + inspect: inspect + }); + + var CONCAT_ARGUMENTS_BUGGY = (function() { + return [].concat(arguments)[0][0] !== 1; + })(1,2) + + if (CONCAT_ARGUMENTS_BUGGY) arrayProto.concat = concat; + + if (!arrayProto.indexOf) arrayProto.indexOf = indexOf; + if (!arrayProto.lastIndexOf) arrayProto.lastIndexOf = lastIndexOf; +})(); +function $H(object) { + return new Hash(object); +}; + +var Hash = Class.create(Enumerable, (function() { + function initialize(object) { + this._object = Object.isHash(object) ? object.toObject() : Object.clone(object); + } + + + function _each(iterator) { + for (var key in this._object) { + var value = this._object[key], pair = [key, value]; + pair.key = key; + pair.value = value; + iterator(pair); + } + } + + function set(key, value) { + return this._object[key] = value; + } + + function get(key) { + if (this._object[key] !== Object.prototype[key]) + return this._object[key]; + } + + function unset(key) { + var value = this._object[key]; + delete this._object[key]; + return value; + } + + function toObject() { + return Object.clone(this._object); + } + + + + function keys() { + return this.pluck('key'); + } + + function values() { + return this.pluck('value'); + } + + function index(value) { + var match = this.detect(function(pair) { + return pair.value === value; + }); + return match && match.key; + } + + function merge(object) { + return this.clone().update(object); + } + + function update(object) { + return new Hash(object).inject(this, function(result, pair) { + result.set(pair.key, pair.value); + return result; + }); + } + + function toQueryPair(key, value) { + if (Object.isUndefined(value)) return key; + return key + '=' + encodeURIComponent(String.interpret(value)); + } + + function toQueryString() { + return this.inject([], function(results, pair) { + var key = encodeURIComponent(pair.key), values = pair.value; + + if (values && typeof values == 'object') { + if (Object.isArray(values)) { + var queryValues = []; + for (var i = 0, len = values.length, value; i < len; i++) { + value = values[i]; + queryValues.push(toQueryPair(key, value)); + } + return results.concat(queryValues); + } + } else results.push(toQueryPair(key, values)); + return results; + }).join('&'); + } + + function inspect() { + return '#'; + } + + function clone() { + return new Hash(this); + } + + return { + initialize: initialize, + _each: _each, + set: set, + get: get, + unset: unset, + toObject: toObject, + toTemplateReplacements: toObject, + keys: keys, + values: values, + index: index, + merge: merge, + update: update, + toQueryString: toQueryString, + inspect: inspect, + toJSON: toObject, + clone: clone + }; +})()); + +Hash.from = $H; +Object.extend(Number.prototype, (function() { + function toColorPart() { + return this.toPaddedString(2, 16); + } + + function succ() { + return this + 1; + } + + function times(iterator, context) { + $R(0, this, true).each(iterator, context); + return this; + } + + function toPaddedString(length, radix) { + var string = this.toString(radix || 10); + return '0'.times(length - string.length) + string; + } + + function abs() { + return Math.abs(this); + } + + function round() { + return Math.round(this); + } + + function ceil() { + return Math.ceil(this); + } + + function floor() { + return Math.floor(this); + } + + return { + toColorPart: toColorPart, + succ: succ, + times: times, + toPaddedString: toPaddedString, + abs: abs, + round: round, + ceil: ceil, + floor: floor + }; +})()); + +function $R(start, end, exclusive) { + return new ObjectRange(start, end, exclusive); +} + +var ObjectRange = Class.create(Enumerable, (function() { + function initialize(start, end, exclusive) { + this.start = start; + this.end = end; + this.exclusive = exclusive; + } + + function _each(iterator) { + var value = this.start; + while (this.include(value)) { + iterator(value); + value = value.succ(); + } + } + + function include(value) { + if (value < this.start) + return false; + if (this.exclusive) + return value < this.end; + return value <= this.end; + } + + return { + initialize: initialize, + _each: _each, + include: include + }; +})()); + + + +var Abstract = { }; + + +var Try = { + these: function() { + var returnValue; + + for (var i = 0, length = arguments.length; i < length; i++) { + var lambda = arguments[i]; + try { + returnValue = lambda(); + break; + } catch (e) { } + } + + return returnValue; + } +}; + +var Ajax = { + getTransport: function() { + return Try.these( + function() {return new XMLHttpRequest()}, + function() {return new ActiveXObject('Msxml2.XMLHTTP')}, + function() {return new ActiveXObject('Microsoft.XMLHTTP')} + ) || false; + }, + + activeRequestCount: 0 +}; + +Ajax.Responders = { + responders: [], + + _each: function(iterator) { + this.responders._each(iterator); + }, + + register: function(responder) { + if (!this.include(responder)) + this.responders.push(responder); + }, + + unregister: function(responder) { + this.responders = this.responders.without(responder); + }, + + dispatch: function(callback, request, transport, json) { + this.each(function(responder) { + if (Object.isFunction(responder[callback])) { + try { + responder[callback].apply(responder, [request, transport, json]); + } catch (e) { } + } + }); + } +}; + +Object.extend(Ajax.Responders, Enumerable); + +Ajax.Responders.register({ + onCreate: function() { Ajax.activeRequestCount++ }, + onComplete: function() { Ajax.activeRequestCount-- } +}); +Ajax.Base = Class.create({ + initialize: function(options) { + this.options = { + method: 'post', + asynchronous: true, + contentType: 'application/x-www-form-urlencoded', + encoding: 'UTF-8', + parameters: '', + evalJSON: true, + evalJS: true + }; + Object.extend(this.options, options || { }); + + this.options.method = this.options.method.toLowerCase(); + + if (Object.isHash(this.options.parameters)) + this.options.parameters = this.options.parameters.toObject(); + } +}); +Ajax.Request = Class.create(Ajax.Base, { + _complete: false, + + initialize: function($super, url, options) { + $super(options); + this.transport = Ajax.getTransport(); + this.request(url); + }, + + request: function(url) { + this.url = url; + this.method = this.options.method; + var params = Object.isString(this.options.parameters) ? + this.options.parameters : + Object.toQueryString(this.options.parameters); + + if (!['get', 'post'].include(this.method)) { + params += (params ? '&' : '') + "_method=" + this.method; + this.method = 'post'; + } + + if (params && this.method === 'get') { + this.url += (this.url.include('?') ? '&' : '?') + params; + } + + this.parameters = params.toQueryParams(); + + try { + var response = new Ajax.Response(this); + if (this.options.onCreate) this.options.onCreate(response); + Ajax.Responders.dispatch('onCreate', this, response); + + this.transport.open(this.method.toUpperCase(), this.url, + this.options.asynchronous); + + if (this.options.asynchronous) this.respondToReadyState.bind(this).defer(1); + + this.transport.onreadystatechange = this.onStateChange.bind(this); + this.setRequestHeaders(); + + this.body = this.method == 'post' ? (this.options.postBody || params) : null; + this.transport.send(this.body); + + /* Force Firefox to handle ready state 4 for synchronous requests */ + if (!this.options.asynchronous && this.transport.overrideMimeType) + this.onStateChange(); + + } + catch (e) { + this.dispatchException(e); + } + }, + + onStateChange: function() { + var readyState = this.transport.readyState; + if (readyState > 1 && !((readyState == 4) && this._complete)) + this.respondToReadyState(this.transport.readyState); + }, + + setRequestHeaders: function() { + var headers = { + 'X-Requested-With': 'XMLHttpRequest', + 'X-Prototype-Version': Prototype.Version, + 'Accept': 'text/javascript, text/html, application/xml, text/xml, */*' + }; + + if (this.method == 'post') { + headers['Content-type'] = this.options.contentType + + (this.options.encoding ? '; charset=' + this.options.encoding : ''); + + /* Force "Connection: close" for older Mozilla browsers to work + * around a bug where XMLHttpRequest sends an incorrect + * Content-length header. See Mozilla Bugzilla #246651. + */ + if (this.transport.overrideMimeType && + (navigator.userAgent.match(/Gecko\/(\d{4})/) || [0,2005])[1] < 2005) + headers['Connection'] = 'close'; + } + + if (typeof this.options.requestHeaders == 'object') { + var extras = this.options.requestHeaders; + + if (Object.isFunction(extras.push)) + for (var i = 0, length = extras.length; i < length; i += 2) + headers[extras[i]] = extras[i+1]; + else + $H(extras).each(function(pair) { headers[pair.key] = pair.value }); + } + + for (var name in headers) + this.transport.setRequestHeader(name, headers[name]); + }, + + success: function() { + var status = this.getStatus(); + return !status || (status >= 200 && status < 300) || status == 304; + }, + + getStatus: function() { + try { + if (this.transport.status === 1223) return 204; + return this.transport.status || 0; + } catch (e) { return 0 } + }, + + respondToReadyState: function(readyState) { + var state = Ajax.Request.Events[readyState], response = new Ajax.Response(this); + + if (state == 'Complete') { + try { + this._complete = true; + (this.options['on' + response.status] + || this.options['on' + (this.success() ? 'Success' : 'Failure')] + || Prototype.emptyFunction)(response, response.headerJSON); + } catch (e) { + this.dispatchException(e); + } + + var contentType = response.getHeader('Content-type'); + if (this.options.evalJS == 'force' + || (this.options.evalJS && this.isSameOrigin() && contentType + && contentType.match(/^\s*(text|application)\/(x-)?(java|ecma)script(;.*)?\s*$/i))) + this.evalResponse(); + } + + try { + (this.options['on' + state] || Prototype.emptyFunction)(response, response.headerJSON); + Ajax.Responders.dispatch('on' + state, this, response, response.headerJSON); + } catch (e) { + this.dispatchException(e); + } + + if (state == 'Complete') { + this.transport.onreadystatechange = Prototype.emptyFunction; + } + }, + + isSameOrigin: function() { + var m = this.url.match(/^\s*https?:\/\/[^\/]*/); + return !m || (m[0] == '#{protocol}//#{domain}#{port}'.interpolate({ + protocol: location.protocol, + domain: document.domain, + port: location.port ? ':' + location.port : '' + })); + }, + + getHeader: function(name) { + try { + return this.transport.getResponseHeader(name) || null; + } catch (e) { return null; } + }, + + evalResponse: function() { + try { + return eval((this.transport.responseText || '').unfilterJSON()); + } catch (e) { + this.dispatchException(e); + } + }, + + dispatchException: function(exception) { + (this.options.onException || Prototype.emptyFunction)(this, exception); + Ajax.Responders.dispatch('onException', this, exception); + } +}); + +Ajax.Request.Events = + ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete']; + + + + + + + + +Ajax.Response = Class.create({ + initialize: function(request){ + this.request = request; + var transport = this.transport = request.transport, + readyState = this.readyState = transport.readyState; + + if ((readyState > 2 && !Prototype.Browser.IE) || readyState == 4) { + this.status = this.getStatus(); + this.statusText = this.getStatusText(); + this.responseText = String.interpret(transport.responseText); + this.headerJSON = this._getHeaderJSON(); + } + + if (readyState == 4) { + var xml = transport.responseXML; + this.responseXML = Object.isUndefined(xml) ? null : xml; + this.responseJSON = this._getResponseJSON(); + } + }, + + status: 0, + + statusText: '', + + getStatus: Ajax.Request.prototype.getStatus, + + getStatusText: function() { + try { + return this.transport.statusText || ''; + } catch (e) { return '' } + }, + + getHeader: Ajax.Request.prototype.getHeader, + + getAllHeaders: function() { + try { + return this.getAllResponseHeaders(); + } catch (e) { return null } + }, + + getResponseHeader: function(name) { + return this.transport.getResponseHeader(name); + }, + + getAllResponseHeaders: function() { + return this.transport.getAllResponseHeaders(); + }, + + _getHeaderJSON: function() { + var json = this.getHeader('X-JSON'); + if (!json) return null; + json = decodeURIComponent(escape(json)); + try { + return json.evalJSON(this.request.options.sanitizeJSON || + !this.request.isSameOrigin()); + } catch (e) { + this.request.dispatchException(e); + } + }, + + _getResponseJSON: function() { + var options = this.request.options; + if (!options.evalJSON || (options.evalJSON != 'force' && + !(this.getHeader('Content-type') || '').include('application/json')) || + this.responseText.blank()) + return null; + try { + return this.responseText.evalJSON(options.sanitizeJSON || + !this.request.isSameOrigin()); + } catch (e) { + this.request.dispatchException(e); + } + } +}); + +Ajax.Updater = Class.create(Ajax.Request, { + initialize: function($super, container, url, options) { + this.container = { + success: (container.success || container), + failure: (container.failure || (container.success ? null : container)) + }; + + options = Object.clone(options); + var onComplete = options.onComplete; + options.onComplete = (function(response, json) { + this.updateContent(response.responseText); + if (Object.isFunction(onComplete)) onComplete(response, json); + }).bind(this); + + $super(url, options); + }, + + updateContent: function(responseText) { + var receiver = this.container[this.success() ? 'success' : 'failure'], + options = this.options; + + if (!options.evalScripts) responseText = responseText.stripScripts(); + + if (receiver = $(receiver)) { + if (options.insertion) { + if (Object.isString(options.insertion)) { + var insertion = { }; insertion[options.insertion] = responseText; + receiver.insert(insertion); + } + else options.insertion(receiver, responseText); + } + else receiver.update(responseText); + } + } +}); + +Ajax.PeriodicalUpdater = Class.create(Ajax.Base, { + initialize: function($super, container, url, options) { + $super(options); + this.onComplete = this.options.onComplete; + + this.frequency = (this.options.frequency || 2); + this.decay = (this.options.decay || 1); + + this.updater = { }; + this.container = container; + this.url = url; + + this.start(); + }, + + start: function() { + this.options.onComplete = this.updateComplete.bind(this); + this.onTimerEvent(); + }, + + stop: function() { + this.updater.options.onComplete = undefined; + clearTimeout(this.timer); + (this.onComplete || Prototype.emptyFunction).apply(this, arguments); + }, + + updateComplete: function(response) { + if (this.options.decay) { + this.decay = (response.responseText == this.lastText ? + this.decay * this.options.decay : 1); + + this.lastText = response.responseText; + } + this.timer = this.onTimerEvent.bind(this).delay(this.decay * this.frequency); + }, + + onTimerEvent: function() { + this.updater = new Ajax.Updater(this.container, this.url, this.options); + } +}); + + +function $(element) { + if (arguments.length > 1) { + for (var i = 0, elements = [], length = arguments.length; i < length; i++) + elements.push($(arguments[i])); + return elements; + } + if (Object.isString(element)) + element = document.getElementById(element); + return Element.extend(element); +} + +if (Prototype.BrowserFeatures.XPath) { + document._getElementsByXPath = function(expression, parentElement) { + var results = []; + var query = document.evaluate(expression, $(parentElement) || document, + null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null); + for (var i = 0, length = query.snapshotLength; i < length; i++) + results.push(Element.extend(query.snapshotItem(i))); + return results; + }; +} + +/*--------------------------------------------------------------------------*/ + +if (!Node) var Node = { }; + +if (!Node.ELEMENT_NODE) { + Object.extend(Node, { + ELEMENT_NODE: 1, + ATTRIBUTE_NODE: 2, + TEXT_NODE: 3, + CDATA_SECTION_NODE: 4, + ENTITY_REFERENCE_NODE: 5, + ENTITY_NODE: 6, + PROCESSING_INSTRUCTION_NODE: 7, + COMMENT_NODE: 8, + DOCUMENT_NODE: 9, + DOCUMENT_TYPE_NODE: 10, + DOCUMENT_FRAGMENT_NODE: 11, + NOTATION_NODE: 12 + }); +} + + + +(function(global) { + function shouldUseCache(tagName, attributes) { + if (tagName === 'select') return false; + if ('type' in attributes) return false; + return true; + } + + var HAS_EXTENDED_CREATE_ELEMENT_SYNTAX = (function(){ + try { + var el = document.createElement(''); + return el.tagName.toLowerCase() === 'input' && el.name === 'x'; + } + catch(err) { + return false; + } + })(); + + var element = global.Element; + + global.Element = function(tagName, attributes) { + attributes = attributes || { }; + tagName = tagName.toLowerCase(); + var cache = Element.cache; + + if (HAS_EXTENDED_CREATE_ELEMENT_SYNTAX && attributes.name) { + tagName = '<' + tagName + ' name="' + attributes.name + '">'; + delete attributes.name; + return Element.writeAttribute(document.createElement(tagName), attributes); + } + + if (!cache[tagName]) cache[tagName] = Element.extend(document.createElement(tagName)); + + var node = shouldUseCache(tagName, attributes) ? + cache[tagName].cloneNode(false) : document.createElement(tagName); + + return Element.writeAttribute(node, attributes); + }; + + Object.extend(global.Element, element || { }); + if (element) global.Element.prototype = element.prototype; + +})(this); + +Element.idCounter = 1; +Element.cache = { }; + +Element._purgeElement = function(element) { + var uid = element._prototypeUID; + if (uid) { + Element.stopObserving(element); + element._prototypeUID = void 0; + delete Element.Storage[uid]; + } +} + +Element.Methods = { + visible: function(element) { + return $(element).style.display != 'none'; + }, + + toggle: function(element) { + element = $(element); + Element[Element.visible(element) ? 'hide' : 'show'](element); + return element; + }, + + hide: function(element) { + element = $(element); + element.style.display = 'none'; + return element; + }, + + show: function(element) { + element = $(element); + element.style.display = ''; + return element; + }, + + remove: function(element) { + element = $(element); + element.parentNode.removeChild(element); + return element; + }, + + update: (function(){ + + var SELECT_ELEMENT_INNERHTML_BUGGY = (function(){ + var el = document.createElement("select"), + isBuggy = true; + el.innerHTML = ""; + if (el.options && el.options[0]) { + isBuggy = el.options[0].nodeName.toUpperCase() !== "OPTION"; + } + el = null; + return isBuggy; + })(); + + var TABLE_ELEMENT_INNERHTML_BUGGY = (function(){ + try { + var el = document.createElement("table"); + if (el && el.tBodies) { + el.innerHTML = "test"; + var isBuggy = typeof el.tBodies[0] == "undefined"; + el = null; + return isBuggy; + } + } catch (e) { + return true; + } + })(); + + var LINK_ELEMENT_INNERHTML_BUGGY = (function() { + try { + var el = document.createElement('div'); + el.innerHTML = ""; + var isBuggy = (el.childNodes.length === 0); + el = null; + return isBuggy; + } catch(e) { + return true; + } + })(); + + var ANY_INNERHTML_BUGGY = SELECT_ELEMENT_INNERHTML_BUGGY || + TABLE_ELEMENT_INNERHTML_BUGGY || LINK_ELEMENT_INNERHTML_BUGGY; + + var SCRIPT_ELEMENT_REJECTS_TEXTNODE_APPENDING = (function () { + var s = document.createElement("script"), + isBuggy = false; + try { + s.appendChild(document.createTextNode("")); + isBuggy = !s.firstChild || + s.firstChild && s.firstChild.nodeType !== 3; + } catch (e) { + isBuggy = true; + } + s = null; + return isBuggy; + })(); + + + function update(element, content) { + element = $(element); + var purgeElement = Element._purgeElement; + + var descendants = element.getElementsByTagName('*'), + i = descendants.length; + while (i--) purgeElement(descendants[i]); + + if (content && content.toElement) + content = content.toElement(); + + if (Object.isElement(content)) + return element.update().insert(content); + + content = Object.toHTML(content); + + var tagName = element.tagName.toUpperCase(); + + if (tagName === 'SCRIPT' && SCRIPT_ELEMENT_REJECTS_TEXTNODE_APPENDING) { + element.text = content; + return element; + } + + if (ANY_INNERHTML_BUGGY) { + if (tagName in Element._insertionTranslations.tags) { + while (element.firstChild) { + element.removeChild(element.firstChild); + } + Element._getContentFromAnonymousElement(tagName, content.stripScripts()) + .each(function(node) { + element.appendChild(node) + }); + } else if (LINK_ELEMENT_INNERHTML_BUGGY && Object.isString(content) && content.indexOf(' -1) { + while (element.firstChild) { + element.removeChild(element.firstChild); + } + var nodes = Element._getContentFromAnonymousElement(tagName, content.stripScripts(), true); + nodes.each(function(node) { element.appendChild(node) }); + } + else { + element.innerHTML = content.stripScripts(); + } + } + else { + element.innerHTML = content.stripScripts(); + } + + content.evalScripts.bind(content).defer(); + return element; + } + + return update; + })(), + + replace: function(element, content) { + element = $(element); + if (content && content.toElement) content = content.toElement(); + else if (!Object.isElement(content)) { + content = Object.toHTML(content); + var range = element.ownerDocument.createRange(); + range.selectNode(element); + content.evalScripts.bind(content).defer(); + content = range.createContextualFragment(content.stripScripts()); + } + element.parentNode.replaceChild(content, element); + return element; + }, + + insert: function(element, insertions) { + element = $(element); + + if (Object.isString(insertions) || Object.isNumber(insertions) || + Object.isElement(insertions) || (insertions && (insertions.toElement || insertions.toHTML))) + insertions = {bottom:insertions}; + + var content, insert, tagName, childNodes; + + for (var position in insertions) { + content = insertions[position]; + position = position.toLowerCase(); + insert = Element._insertionTranslations[position]; + + if (content && content.toElement) content = content.toElement(); + if (Object.isElement(content)) { + insert(element, content); + continue; + } + + content = Object.toHTML(content); + + tagName = ((position == 'before' || position == 'after') + ? element.parentNode : element).tagName.toUpperCase(); + + childNodes = Element._getContentFromAnonymousElement(tagName, content.stripScripts()); + + if (position == 'top' || position == 'after') childNodes.reverse(); + childNodes.each(insert.curry(element)); + + content.evalScripts.bind(content).defer(); + } + + return element; + }, + + wrap: function(element, wrapper, attributes) { + element = $(element); + if (Object.isElement(wrapper)) + $(wrapper).writeAttribute(attributes || { }); + else if (Object.isString(wrapper)) wrapper = new Element(wrapper, attributes); + else wrapper = new Element('div', wrapper); + if (element.parentNode) + element.parentNode.replaceChild(wrapper, element); + wrapper.appendChild(element); + return wrapper; + }, + + inspect: function(element) { + element = $(element); + var result = '<' + element.tagName.toLowerCase(); + $H({'id': 'id', 'className': 'class'}).each(function(pair) { + var property = pair.first(), + attribute = pair.last(), + value = (element[property] || '').toString(); + if (value) result += ' ' + attribute + '=' + value.inspect(true); + }); + return result + '>'; + }, + + recursivelyCollect: function(element, property, maximumLength) { + element = $(element); + maximumLength = maximumLength || -1; + var elements = []; + + while (element = element[property]) { + if (element.nodeType == 1) + elements.push(Element.extend(element)); + if (elements.length == maximumLength) + break; + } + + return elements; + }, + + ancestors: function(element) { + return Element.recursivelyCollect(element, 'parentNode'); + }, + + descendants: function(element) { + return Element.select(element, "*"); + }, + + firstDescendant: function(element) { + element = $(element).firstChild; + while (element && element.nodeType != 1) element = element.nextSibling; + return $(element); + }, + + immediateDescendants: function(element) { + var results = [], child = $(element).firstChild; + while (child) { + if (child.nodeType === 1) { + results.push(Element.extend(child)); + } + child = child.nextSibling; + } + return results; + }, + + previousSiblings: function(element, maximumLength) { + return Element.recursivelyCollect(element, 'previousSibling'); + }, + + nextSiblings: function(element) { + return Element.recursivelyCollect(element, 'nextSibling'); + }, + + siblings: function(element) { + element = $(element); + return Element.previousSiblings(element).reverse() + .concat(Element.nextSiblings(element)); + }, + + match: function(element, selector) { + element = $(element); + if (Object.isString(selector)) + return Prototype.Selector.match(element, selector); + return selector.match(element); + }, + + up: function(element, expression, index) { + element = $(element); + if (arguments.length == 1) return $(element.parentNode); + var ancestors = Element.ancestors(element); + return Object.isNumber(expression) ? ancestors[expression] : + Prototype.Selector.find(ancestors, expression, index); + }, + + down: function(element, expression, index) { + element = $(element); + if (arguments.length == 1) return Element.firstDescendant(element); + return Object.isNumber(expression) ? Element.descendants(element)[expression] : + Element.select(element, expression)[index || 0]; + }, + + previous: function(element, expression, index) { + element = $(element); + if (Object.isNumber(expression)) index = expression, expression = false; + if (!Object.isNumber(index)) index = 0; + + if (expression) { + return Prototype.Selector.find(element.previousSiblings(), expression, index); + } else { + return element.recursivelyCollect("previousSibling", index + 1)[index]; + } + }, + + next: function(element, expression, index) { + element = $(element); + if (Object.isNumber(expression)) index = expression, expression = false; + if (!Object.isNumber(index)) index = 0; + + if (expression) { + return Prototype.Selector.find(element.nextSiblings(), expression, index); + } else { + var maximumLength = Object.isNumber(index) ? index + 1 : 1; + return element.recursivelyCollect("nextSibling", index + 1)[index]; + } + }, + + + select: function(element) { + element = $(element); + var expressions = Array.prototype.slice.call(arguments, 1).join(', '); + return Prototype.Selector.select(expressions, element); + }, + + adjacent: function(element) { + element = $(element); + var expressions = Array.prototype.slice.call(arguments, 1).join(', '); + return Prototype.Selector.select(expressions, element.parentNode).without(element); + }, + + identify: function(element) { + element = $(element); + var id = Element.readAttribute(element, 'id'); + if (id) return id; + do { id = 'anonymous_element_' + Element.idCounter++ } while ($(id)); + Element.writeAttribute(element, 'id', id); + return id; + }, + + readAttribute: function(element, name) { + element = $(element); + if (Prototype.Browser.IE) { + var t = Element._attributeTranslations.read; + if (t.values[name]) return t.values[name](element, name); + if (t.names[name]) name = t.names[name]; + if (name.include(':')) { + return (!element.attributes || !element.attributes[name]) ? null : + element.attributes[name].value; + } + } + return element.getAttribute(name); + }, + + writeAttribute: function(element, name, value) { + element = $(element); + var attributes = { }, t = Element._attributeTranslations.write; + + if (typeof name == 'object') attributes = name; + else attributes[name] = Object.isUndefined(value) ? true : value; + + for (var attr in attributes) { + name = t.names[attr] || attr; + value = attributes[attr]; + if (t.values[attr]) name = t.values[attr](element, value); + if (value === false || value === null) + element.removeAttribute(name); + else if (value === true) + element.setAttribute(name, name); + else element.setAttribute(name, value); + } + return element; + }, + + getHeight: function(element) { + return Element.getDimensions(element).height; + }, + + getWidth: function(element) { + return Element.getDimensions(element).width; + }, + + classNames: function(element) { + return new Element.ClassNames(element); + }, + + hasClassName: function(element, className) { + if (!(element = $(element))) return; + var elementClassName = element.className; + return (elementClassName.length > 0 && (elementClassName == className || + new RegExp("(^|\\s)" + className + "(\\s|$)").test(elementClassName))); + }, + + addClassName: function(element, className) { + if (!(element = $(element))) return; + if (!Element.hasClassName(element, className)) + element.className += (element.className ? ' ' : '') + className; + return element; + }, + + removeClassName: function(element, className) { + if (!(element = $(element))) return; + element.className = element.className.replace( + new RegExp("(^|\\s+)" + className + "(\\s+|$)"), ' ').strip(); + return element; + }, + + toggleClassName: function(element, className) { + if (!(element = $(element))) return; + return Element[Element.hasClassName(element, className) ? + 'removeClassName' : 'addClassName'](element, className); + }, + + cleanWhitespace: function(element) { + element = $(element); + var node = element.firstChild; + while (node) { + var nextNode = node.nextSibling; + if (node.nodeType == 3 && !/\S/.test(node.nodeValue)) + element.removeChild(node); + node = nextNode; + } + return element; + }, + + empty: function(element) { + return $(element).innerHTML.blank(); + }, + + descendantOf: function(element, ancestor) { + element = $(element), ancestor = $(ancestor); + + if (element.compareDocumentPosition) + return (element.compareDocumentPosition(ancestor) & 8) === 8; + + if (ancestor.contains) + return ancestor.contains(element) && ancestor !== element; + + while (element = element.parentNode) + if (element == ancestor) return true; + + return false; + }, + + scrollTo: function(element) { + element = $(element); + var pos = Element.cumulativeOffset(element); + window.scrollTo(pos[0], pos[1]); + return element; + }, + + getStyle: function(element, style) { + element = $(element); + style = style == 'float' ? 'cssFloat' : style.camelize(); + var value = element.style[style]; + if (!value || value == 'auto') { + var css = document.defaultView.getComputedStyle(element, null); + value = css ? css[style] : null; + } + if (style == 'opacity') return value ? parseFloat(value) : 1.0; + return value == 'auto' ? null : value; + }, + + getOpacity: function(element) { + return $(element).getStyle('opacity'); + }, + + setStyle: function(element, styles) { + element = $(element); + var elementStyle = element.style, match; + if (Object.isString(styles)) { + element.style.cssText += ';' + styles; + return styles.include('opacity') ? + element.setOpacity(styles.match(/opacity:\s*(\d?\.?\d*)/)[1]) : element; + } + for (var property in styles) + if (property == 'opacity') element.setOpacity(styles[property]); + else + elementStyle[(property == 'float' || property == 'cssFloat') ? + (Object.isUndefined(elementStyle.styleFloat) ? 'cssFloat' : 'styleFloat') : + property] = styles[property]; + + return element; + }, + + setOpacity: function(element, value) { + element = $(element); + element.style.opacity = (value == 1 || value === '') ? '' : + (value < 0.00001) ? 0 : value; + return element; + }, + + makePositioned: function(element) { + element = $(element); + var pos = Element.getStyle(element, 'position'); + if (pos == 'static' || !pos) { + element._madePositioned = true; + element.style.position = 'relative'; + if (Prototype.Browser.Opera) { + element.style.top = 0; + element.style.left = 0; + } + } + return element; + }, + + undoPositioned: function(element) { + element = $(element); + if (element._madePositioned) { + element._madePositioned = undefined; + element.style.position = + element.style.top = + element.style.left = + element.style.bottom = + element.style.right = ''; + } + return element; + }, + + makeClipping: function(element) { + element = $(element); + if (element._overflow) return element; + element._overflow = Element.getStyle(element, 'overflow') || 'auto'; + if (element._overflow !== 'hidden') + element.style.overflow = 'hidden'; + return element; + }, + + undoClipping: function(element) { + element = $(element); + if (!element._overflow) return element; + element.style.overflow = element._overflow == 'auto' ? '' : element._overflow; + element._overflow = null; + return element; + }, + + clonePosition: function(element, source) { + var options = Object.extend({ + setLeft: true, + setTop: true, + setWidth: true, + setHeight: true, + offsetTop: 0, + offsetLeft: 0 + }, arguments[2] || { }); + + source = $(source); + var p = Element.viewportOffset(source), delta = [0, 0], parent = null; + + element = $(element); + + if (Element.getStyle(element, 'position') == 'absolute') { + parent = Element.getOffsetParent(element); + delta = Element.viewportOffset(parent); + } + + if (parent == document.body) { + delta[0] -= document.body.offsetLeft; + delta[1] -= document.body.offsetTop; + } + + if (options.setLeft) element.style.left = (p[0] - delta[0] + options.offsetLeft) + 'px'; + if (options.setTop) element.style.top = (p[1] - delta[1] + options.offsetTop) + 'px'; + if (options.setWidth) element.style.width = source.offsetWidth + 'px'; + if (options.setHeight) element.style.height = source.offsetHeight + 'px'; + return element; + } +}; + +Object.extend(Element.Methods, { + getElementsBySelector: Element.Methods.select, + + childElements: Element.Methods.immediateDescendants +}); + +Element._attributeTranslations = { + write: { + names: { + className: 'class', + htmlFor: 'for' + }, + values: { } + } +}; + +if (Prototype.Browser.Opera) { + Element.Methods.getStyle = Element.Methods.getStyle.wrap( + function(proceed, element, style) { + switch (style) { + case 'height': case 'width': + if (!Element.visible(element)) return null; + + var dim = parseInt(proceed(element, style), 10); + + if (dim !== element['offset' + style.capitalize()]) + return dim + 'px'; + + var properties; + if (style === 'height') { + properties = ['border-top-width', 'padding-top', + 'padding-bottom', 'border-bottom-width']; + } + else { + properties = ['border-left-width', 'padding-left', + 'padding-right', 'border-right-width']; + } + return properties.inject(dim, function(memo, property) { + var val = proceed(element, property); + return val === null ? memo : memo - parseInt(val, 10); + }) + 'px'; + default: return proceed(element, style); + } + } + ); + + Element.Methods.readAttribute = Element.Methods.readAttribute.wrap( + function(proceed, element, attribute) { + if (attribute === 'title') return element.title; + return proceed(element, attribute); + } + ); +} + +else if (Prototype.Browser.IE) { + Element.Methods.getStyle = function(element, style) { + element = $(element); + style = (style == 'float' || style == 'cssFloat') ? 'styleFloat' : style.camelize(); + var value = element.style[style]; + if (!value && element.currentStyle) value = element.currentStyle[style]; + + if (style == 'opacity') { + if (value = (element.getStyle('filter') || '').match(/alpha\(opacity=(.*)\)/)) + if (value[1]) return parseFloat(value[1]) / 100; + return 1.0; + } + + if (value == 'auto') { + if ((style == 'width' || style == 'height') && (element.getStyle('display') != 'none')) + return element['offset' + style.capitalize()] + 'px'; + return null; + } + return value; + }; + + Element.Methods.setOpacity = function(element, value) { + function stripAlpha(filter){ + return filter.replace(/alpha\([^\)]*\)/gi,''); + } + element = $(element); + var currentStyle = element.currentStyle; + if ((currentStyle && !currentStyle.hasLayout) || + (!currentStyle && element.style.zoom == 'normal')) + element.style.zoom = 1; + + var filter = element.getStyle('filter'), style = element.style; + if (value == 1 || value === '') { + (filter = stripAlpha(filter)) ? + style.filter = filter : style.removeAttribute('filter'); + return element; + } else if (value < 0.00001) value = 0; + style.filter = stripAlpha(filter) + + 'alpha(opacity=' + (value * 100) + ')'; + return element; + }; + + Element._attributeTranslations = (function(){ + + var classProp = 'className', + forProp = 'for', + el = document.createElement('div'); + + el.setAttribute(classProp, 'x'); + + if (el.className !== 'x') { + el.setAttribute('class', 'x'); + if (el.className === 'x') { + classProp = 'class'; + } + } + el = null; + + el = document.createElement('label'); + el.setAttribute(forProp, 'x'); + if (el.htmlFor !== 'x') { + el.setAttribute('htmlFor', 'x'); + if (el.htmlFor === 'x') { + forProp = 'htmlFor'; + } + } + el = null; + + return { + read: { + names: { + 'class': classProp, + 'className': classProp, + 'for': forProp, + 'htmlFor': forProp + }, + values: { + _getAttr: function(element, attribute) { + return element.getAttribute(attribute); + }, + _getAttr2: function(element, attribute) { + return element.getAttribute(attribute, 2); + }, + _getAttrNode: function(element, attribute) { + var node = element.getAttributeNode(attribute); + return node ? node.value : ""; + }, + _getEv: (function(){ + + var el = document.createElement('div'), f; + el.onclick = Prototype.emptyFunction; + var value = el.getAttribute('onclick'); + + if (String(value).indexOf('{') > -1) { + f = function(element, attribute) { + attribute = element.getAttribute(attribute); + if (!attribute) return null; + attribute = attribute.toString(); + attribute = attribute.split('{')[1]; + attribute = attribute.split('}')[0]; + return attribute.strip(); + }; + } + else if (value === '') { + f = function(element, attribute) { + attribute = element.getAttribute(attribute); + if (!attribute) return null; + return attribute.strip(); + }; + } + el = null; + return f; + })(), + _flag: function(element, attribute) { + return $(element).hasAttribute(attribute) ? attribute : null; + }, + style: function(element) { + return element.style.cssText.toLowerCase(); + }, + title: function(element) { + return element.title; + } + } + } + } + })(); + + Element._attributeTranslations.write = { + names: Object.extend({ + cellpadding: 'cellPadding', + cellspacing: 'cellSpacing' + }, Element._attributeTranslations.read.names), + values: { + checked: function(element, value) { + element.checked = !!value; + }, + + style: function(element, value) { + element.style.cssText = value ? value : ''; + } + } + }; + + Element._attributeTranslations.has = {}; + + $w('colSpan rowSpan vAlign dateTime accessKey tabIndex ' + + 'encType maxLength readOnly longDesc frameBorder').each(function(attr) { + Element._attributeTranslations.write.names[attr.toLowerCase()] = attr; + Element._attributeTranslations.has[attr.toLowerCase()] = attr; + }); + + (function(v) { + Object.extend(v, { + href: v._getAttr2, + src: v._getAttr2, + type: v._getAttr, + action: v._getAttrNode, + disabled: v._flag, + checked: v._flag, + readonly: v._flag, + multiple: v._flag, + onload: v._getEv, + onunload: v._getEv, + onclick: v._getEv, + ondblclick: v._getEv, + onmousedown: v._getEv, + onmouseup: v._getEv, + onmouseover: v._getEv, + onmousemove: v._getEv, + onmouseout: v._getEv, + onfocus: v._getEv, + onblur: v._getEv, + onkeypress: v._getEv, + onkeydown: v._getEv, + onkeyup: v._getEv, + onsubmit: v._getEv, + onreset: v._getEv, + onselect: v._getEv, + onchange: v._getEv + }); + })(Element._attributeTranslations.read.values); + + if (Prototype.BrowserFeatures.ElementExtensions) { + (function() { + function _descendants(element) { + var nodes = element.getElementsByTagName('*'), results = []; + for (var i = 0, node; node = nodes[i]; i++) + if (node.tagName !== "!") // Filter out comment nodes. + results.push(node); + return results; + } + + Element.Methods.down = function(element, expression, index) { + element = $(element); + if (arguments.length == 1) return element.firstDescendant(); + return Object.isNumber(expression) ? _descendants(element)[expression] : + Element.select(element, expression)[index || 0]; + } + })(); + } + +} + +else if (Prototype.Browser.Gecko && /rv:1\.8\.0/.test(navigator.userAgent)) { + Element.Methods.setOpacity = function(element, value) { + element = $(element); + element.style.opacity = (value == 1) ? 0.999999 : + (value === '') ? '' : (value < 0.00001) ? 0 : value; + return element; + }; +} + +else if (Prototype.Browser.WebKit) { + Element.Methods.setOpacity = function(element, value) { + element = $(element); + element.style.opacity = (value == 1 || value === '') ? '' : + (value < 0.00001) ? 0 : value; + + if (value == 1) + if (element.tagName.toUpperCase() == 'IMG' && element.width) { + element.width++; element.width--; + } else try { + var n = document.createTextNode(' '); + element.appendChild(n); + element.removeChild(n); + } catch (e) { } + + return element; + }; +} + +if ('outerHTML' in document.documentElement) { + Element.Methods.replace = function(element, content) { + element = $(element); + + if (content && content.toElement) content = content.toElement(); + if (Object.isElement(content)) { + element.parentNode.replaceChild(content, element); + return element; + } + + content = Object.toHTML(content); + var parent = element.parentNode, tagName = parent.tagName.toUpperCase(); + + if (Element._insertionTranslations.tags[tagName]) { + var nextSibling = element.next(), + fragments = Element._getContentFromAnonymousElement(tagName, content.stripScripts()); + parent.removeChild(element); + if (nextSibling) + fragments.each(function(node) { parent.insertBefore(node, nextSibling) }); + else + fragments.each(function(node) { parent.appendChild(node) }); + } + else element.outerHTML = content.stripScripts(); + + content.evalScripts.bind(content).defer(); + return element; + }; +} + +Element._returnOffset = function(l, t) { + var result = [l, t]; + result.left = l; + result.top = t; + return result; +}; + +Element._getContentFromAnonymousElement = function(tagName, html, force) { + var div = new Element('div'), + t = Element._insertionTranslations.tags[tagName]; + + var workaround = false; + if (t) workaround = true; + else if (force) { + workaround = true; + t = ['', '', 0]; + } + + if (workaround) { + div.innerHTML = ' ' + t[0] + html + t[1]; + div.removeChild(div.firstChild); + for (var i = t[2]; i--; ) { + div = div.firstChild; + } + } + else { + div.innerHTML = html; + } + return $A(div.childNodes); +}; + +Element._insertionTranslations = { + before: function(element, node) { + element.parentNode.insertBefore(node, element); + }, + top: function(element, node) { + element.insertBefore(node, element.firstChild); + }, + bottom: function(element, node) { + element.appendChild(node); + }, + after: function(element, node) { + element.parentNode.insertBefore(node, element.nextSibling); + }, + tags: { + TABLE: ['', '
', 1], + TBODY: ['', '
', 2], + TR: ['', '
', 3], + TD: ['
', '
', 4], + SELECT: ['', 1] + } +}; + +(function() { + var tags = Element._insertionTranslations.tags; + Object.extend(tags, { + THEAD: tags.TBODY, + TFOOT: tags.TBODY, + TH: tags.TD + }); +})(); + +Element.Methods.Simulated = { + hasAttribute: function(element, attribute) { + attribute = Element._attributeTranslations.has[attribute] || attribute; + var node = $(element).getAttributeNode(attribute); + return !!(node && node.specified); + } +}; + +Element.Methods.ByTag = { }; + +Object.extend(Element, Element.Methods); + +(function(div) { + + if (!Prototype.BrowserFeatures.ElementExtensions && div['__proto__']) { + window.HTMLElement = { }; + window.HTMLElement.prototype = div['__proto__']; + Prototype.BrowserFeatures.ElementExtensions = true; + } + + div = null; + +})(document.createElement('div')); + +Element.extend = (function() { + + function checkDeficiency(tagName) { + if (typeof window.Element != 'undefined') { + var proto = window.Element.prototype; + if (proto) { + var id = '_' + (Math.random()+'').slice(2), + el = document.createElement(tagName); + proto[id] = 'x'; + var isBuggy = (el[id] !== 'x'); + delete proto[id]; + el = null; + return isBuggy; + } + } + return false; + } + + function extendElementWith(element, methods) { + for (var property in methods) { + var value = methods[property]; + if (Object.isFunction(value) && !(property in element)) + element[property] = value.methodize(); + } + } + + var HTMLOBJECTELEMENT_PROTOTYPE_BUGGY = checkDeficiency('object'); + + if (Prototype.BrowserFeatures.SpecificElementExtensions) { + if (HTMLOBJECTELEMENT_PROTOTYPE_BUGGY) { + return function(element) { + if (element && typeof element._extendedByPrototype == 'undefined') { + var t = element.tagName; + if (t && (/^(?:object|applet|embed)$/i.test(t))) { + extendElementWith(element, Element.Methods); + extendElementWith(element, Element.Methods.Simulated); + extendElementWith(element, Element.Methods.ByTag[t.toUpperCase()]); + } + } + return element; + } + } + return Prototype.K; + } + + var Methods = { }, ByTag = Element.Methods.ByTag; + + var extend = Object.extend(function(element) { + if (!element || typeof element._extendedByPrototype != 'undefined' || + element.nodeType != 1 || element == window) return element; + + var methods = Object.clone(Methods), + tagName = element.tagName.toUpperCase(); + + if (ByTag[tagName]) Object.extend(methods, ByTag[tagName]); + + extendElementWith(element, methods); + + element._extendedByPrototype = Prototype.emptyFunction; + return element; + + }, { + refresh: function() { + if (!Prototype.BrowserFeatures.ElementExtensions) { + Object.extend(Methods, Element.Methods); + Object.extend(Methods, Element.Methods.Simulated); + } + } + }); + + extend.refresh(); + return extend; +})(); + +if (document.documentElement.hasAttribute) { + Element.hasAttribute = function(element, attribute) { + return element.hasAttribute(attribute); + }; +} +else { + Element.hasAttribute = Element.Methods.Simulated.hasAttribute; +} + +Element.addMethods = function(methods) { + var F = Prototype.BrowserFeatures, T = Element.Methods.ByTag; + + if (!methods) { + Object.extend(Form, Form.Methods); + Object.extend(Form.Element, Form.Element.Methods); + Object.extend(Element.Methods.ByTag, { + "FORM": Object.clone(Form.Methods), + "INPUT": Object.clone(Form.Element.Methods), + "SELECT": Object.clone(Form.Element.Methods), + "TEXTAREA": Object.clone(Form.Element.Methods), + "BUTTON": Object.clone(Form.Element.Methods) + }); + } + + if (arguments.length == 2) { + var tagName = methods; + methods = arguments[1]; + } + + if (!tagName) Object.extend(Element.Methods, methods || { }); + else { + if (Object.isArray(tagName)) tagName.each(extend); + else extend(tagName); + } + + function extend(tagName) { + tagName = tagName.toUpperCase(); + if (!Element.Methods.ByTag[tagName]) + Element.Methods.ByTag[tagName] = { }; + Object.extend(Element.Methods.ByTag[tagName], methods); + } + + function copy(methods, destination, onlyIfAbsent) { + onlyIfAbsent = onlyIfAbsent || false; + for (var property in methods) { + var value = methods[property]; + if (!Object.isFunction(value)) continue; + if (!onlyIfAbsent || !(property in destination)) + destination[property] = value.methodize(); + } + } + + function findDOMClass(tagName) { + var klass; + var trans = { + "OPTGROUP": "OptGroup", "TEXTAREA": "TextArea", "P": "Paragraph", + "FIELDSET": "FieldSet", "UL": "UList", "OL": "OList", "DL": "DList", + "DIR": "Directory", "H1": "Heading", "H2": "Heading", "H3": "Heading", + "H4": "Heading", "H5": "Heading", "H6": "Heading", "Q": "Quote", + "INS": "Mod", "DEL": "Mod", "A": "Anchor", "IMG": "Image", "CAPTION": + "TableCaption", "COL": "TableCol", "COLGROUP": "TableCol", "THEAD": + "TableSection", "TFOOT": "TableSection", "TBODY": "TableSection", "TR": + "TableRow", "TH": "TableCell", "TD": "TableCell", "FRAMESET": + "FrameSet", "IFRAME": "IFrame" + }; + if (trans[tagName]) klass = 'HTML' + trans[tagName] + 'Element'; + if (window[klass]) return window[klass]; + klass = 'HTML' + tagName + 'Element'; + if (window[klass]) return window[klass]; + klass = 'HTML' + tagName.capitalize() + 'Element'; + if (window[klass]) return window[klass]; + + var element = document.createElement(tagName), + proto = element['__proto__'] || element.constructor.prototype; + + element = null; + return proto; + } + + var elementPrototype = window.HTMLElement ? HTMLElement.prototype : + Element.prototype; + + if (F.ElementExtensions) { + copy(Element.Methods, elementPrototype); + copy(Element.Methods.Simulated, elementPrototype, true); + } + + if (F.SpecificElementExtensions) { + for (var tag in Element.Methods.ByTag) { + var klass = findDOMClass(tag); + if (Object.isUndefined(klass)) continue; + copy(T[tag], klass.prototype); + } + } + + Object.extend(Element, Element.Methods); + delete Element.ByTag; + + if (Element.extend.refresh) Element.extend.refresh(); + Element.cache = { }; +}; + + +document.viewport = { + + getDimensions: function() { + return { width: this.getWidth(), height: this.getHeight() }; + }, + + getScrollOffsets: function() { + return Element._returnOffset( + window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft, + window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop); + } +}; + +(function(viewport) { + var B = Prototype.Browser, doc = document, element, property = {}; + + function getRootElement() { + if (B.WebKit && !doc.evaluate) + return document; + + if (B.Opera && window.parseFloat(window.opera.version()) < 9.5) + return document.body; + + return document.documentElement; + } + + function define(D) { + if (!element) element = getRootElement(); + + property[D] = 'client' + D; + + viewport['get' + D] = function() { return element[property[D]] }; + return viewport['get' + D](); + } + + viewport.getWidth = define.curry('Width'); + + viewport.getHeight = define.curry('Height'); +})(document.viewport); + + +Element.Storage = { + UID: 1 +}; + +Element.addMethods({ + getStorage: function(element) { + if (!(element = $(element))) return; + + var uid; + if (element === window) { + uid = 0; + } else { + if (typeof element._prototypeUID === "undefined") + element._prototypeUID = Element.Storage.UID++; + uid = element._prototypeUID; + } + + if (!Element.Storage[uid]) + Element.Storage[uid] = $H(); + + return Element.Storage[uid]; + }, + + store: function(element, key, value) { + if (!(element = $(element))) return; + + if (arguments.length === 2) { + Element.getStorage(element).update(key); + } else { + Element.getStorage(element).set(key, value); + } + + return element; + }, + + retrieve: function(element, key, defaultValue) { + if (!(element = $(element))) return; + var hash = Element.getStorage(element), value = hash.get(key); + + if (Object.isUndefined(value)) { + hash.set(key, defaultValue); + value = defaultValue; + } + + return value; + }, + + clone: function(element, deep) { + if (!(element = $(element))) return; + var clone = element.cloneNode(deep); + clone._prototypeUID = void 0; + if (deep) { + var descendants = Element.select(clone, '*'), + i = descendants.length; + while (i--) { + descendants[i]._prototypeUID = void 0; + } + } + return Element.extend(clone); + }, + + purge: function(element) { + if (!(element = $(element))) return; + var purgeElement = Element._purgeElement; + + purgeElement(element); + + var descendants = element.getElementsByTagName('*'), + i = descendants.length; + + while (i--) purgeElement(descendants[i]); + + return null; + } +}); + +(function() { + + function toDecimal(pctString) { + var match = pctString.match(/^(\d+)%?$/i); + if (!match) return null; + return (Number(match[1]) / 100); + } + + function getPixelValue(value, property, context) { + var element = null; + if (Object.isElement(value)) { + element = value; + value = element.getStyle(property); + } + + if (value === null) { + return null; + } + + if ((/^(?:-)?\d+(\.\d+)?(px)?$/i).test(value)) { + return window.parseFloat(value); + } + + var isPercentage = value.include('%'), isViewport = (context === document.viewport); + + if (/\d/.test(value) && element && element.runtimeStyle && !(isPercentage && isViewport)) { + var style = element.style.left, rStyle = element.runtimeStyle.left; + element.runtimeStyle.left = element.currentStyle.left; + element.style.left = value || 0; + value = element.style.pixelLeft; + element.style.left = style; + element.runtimeStyle.left = rStyle; + + return value; + } + + if (element && isPercentage) { + context = context || element.parentNode; + var decimal = toDecimal(value); + var whole = null; + var position = element.getStyle('position'); + + var isHorizontal = property.include('left') || property.include('right') || + property.include('width'); + + var isVertical = property.include('top') || property.include('bottom') || + property.include('height'); + + if (context === document.viewport) { + if (isHorizontal) { + whole = document.viewport.getWidth(); + } else if (isVertical) { + whole = document.viewport.getHeight(); + } + } else { + if (isHorizontal) { + whole = $(context).measure('width'); + } else if (isVertical) { + whole = $(context).measure('height'); + } + } + + return (whole === null) ? 0 : whole * decimal; + } + + return 0; + } + + function toCSSPixels(number) { + if (Object.isString(number) && number.endsWith('px')) { + return number; + } + return number + 'px'; + } + + function isDisplayed(element) { + var originalElement = element; + while (element && element.parentNode) { + var display = element.getStyle('display'); + if (display === 'none') { + return false; + } + element = $(element.parentNode); + } + return true; + } + + var hasLayout = Prototype.K; + if ('currentStyle' in document.documentElement) { + hasLayout = function(element) { + if (!element.currentStyle.hasLayout) { + element.style.zoom = 1; + } + return element; + }; + } + + function cssNameFor(key) { + if (key.include('border')) key = key + '-width'; + return key.camelize(); + } + + Element.Layout = Class.create(Hash, { + initialize: function($super, element, preCompute) { + $super(); + this.element = $(element); + + Element.Layout.PROPERTIES.each( function(property) { + this._set(property, null); + }, this); + + if (preCompute) { + this._preComputing = true; + this._begin(); + Element.Layout.PROPERTIES.each( this._compute, this ); + this._end(); + this._preComputing = false; + } + }, + + _set: function(property, value) { + return Hash.prototype.set.call(this, property, value); + }, + + set: function(property, value) { + throw "Properties of Element.Layout are read-only."; + }, + + get: function($super, property) { + var value = $super(property); + return value === null ? this._compute(property) : value; + }, + + _begin: function() { + if (this._prepared) return; + + var element = this.element; + if (isDisplayed(element)) { + this._prepared = true; + return; + } + + var originalStyles = { + position: element.style.position || '', + width: element.style.width || '', + visibility: element.style.visibility || '', + display: element.style.display || '' + }; + + element.store('prototype_original_styles', originalStyles); + + var position = element.getStyle('position'), + width = element.getStyle('width'); + + if (width === "0px" || width === null) { + element.style.display = 'block'; + width = element.getStyle('width'); + } + + var context = (position === 'fixed') ? document.viewport : + element.parentNode; + + element.setStyle({ + position: 'absolute', + visibility: 'hidden', + display: 'block' + }); + + var positionedWidth = element.getStyle('width'); + + var newWidth; + if (width && (positionedWidth === width)) { + newWidth = getPixelValue(element, 'width', context); + } else if (position === 'absolute' || position === 'fixed') { + newWidth = getPixelValue(element, 'width', context); + } else { + var parent = element.parentNode, pLayout = $(parent).getLayout(); + + newWidth = pLayout.get('width') - + this.get('margin-left') - + this.get('border-left') - + this.get('padding-left') - + this.get('padding-right') - + this.get('border-right') - + this.get('margin-right'); + } + + element.setStyle({ width: newWidth + 'px' }); + + this._prepared = true; + }, + + _end: function() { + var element = this.element; + var originalStyles = element.retrieve('prototype_original_styles'); + element.store('prototype_original_styles', null); + element.setStyle(originalStyles); + this._prepared = false; + }, + + _compute: function(property) { + var COMPUTATIONS = Element.Layout.COMPUTATIONS; + if (!(property in COMPUTATIONS)) { + throw "Property not found."; + } + + return this._set(property, COMPUTATIONS[property].call(this, this.element)); + }, + + toObject: function() { + var args = $A(arguments); + var keys = (args.length === 0) ? Element.Layout.PROPERTIES : + args.join(' ').split(' '); + var obj = {}; + keys.each( function(key) { + if (!Element.Layout.PROPERTIES.include(key)) return; + var value = this.get(key); + if (value != null) obj[key] = value; + }, this); + return obj; + }, + + toHash: function() { + var obj = this.toObject.apply(this, arguments); + return new Hash(obj); + }, + + toCSS: function() { + var args = $A(arguments); + var keys = (args.length === 0) ? Element.Layout.PROPERTIES : + args.join(' ').split(' '); + var css = {}; + + keys.each( function(key) { + if (!Element.Layout.PROPERTIES.include(key)) return; + if (Element.Layout.COMPOSITE_PROPERTIES.include(key)) return; + + var value = this.get(key); + if (value != null) css[cssNameFor(key)] = value + 'px'; + }, this); + return css; + }, + + inspect: function() { + return "#"; + } + }); + + Object.extend(Element.Layout, { + PROPERTIES: $w('height width top left right bottom border-left border-right border-top border-bottom padding-left padding-right padding-top padding-bottom margin-top margin-bottom margin-left margin-right padding-box-width padding-box-height border-box-width border-box-height margin-box-width margin-box-height'), + + COMPOSITE_PROPERTIES: $w('padding-box-width padding-box-height margin-box-width margin-box-height border-box-width border-box-height'), + + COMPUTATIONS: { + 'height': function(element) { + if (!this._preComputing) this._begin(); + + var bHeight = this.get('border-box-height'); + if (bHeight <= 0) { + if (!this._preComputing) this._end(); + return 0; + } + + var bTop = this.get('border-top'), + bBottom = this.get('border-bottom'); + + var pTop = this.get('padding-top'), + pBottom = this.get('padding-bottom'); + + if (!this._preComputing) this._end(); + + return bHeight - bTop - bBottom - pTop - pBottom; + }, + + 'width': function(element) { + if (!this._preComputing) this._begin(); + + var bWidth = this.get('border-box-width'); + if (bWidth <= 0) { + if (!this._preComputing) this._end(); + return 0; + } + + var bLeft = this.get('border-left'), + bRight = this.get('border-right'); + + var pLeft = this.get('padding-left'), + pRight = this.get('padding-right'); + + if (!this._preComputing) this._end(); + + return bWidth - bLeft - bRight - pLeft - pRight; + }, + + 'padding-box-height': function(element) { + var height = this.get('height'), + pTop = this.get('padding-top'), + pBottom = this.get('padding-bottom'); + + return height + pTop + pBottom; + }, + + 'padding-box-width': function(element) { + var width = this.get('width'), + pLeft = this.get('padding-left'), + pRight = this.get('padding-right'); + + return width + pLeft + pRight; + }, + + 'border-box-height': function(element) { + if (!this._preComputing) this._begin(); + var height = element.offsetHeight; + if (!this._preComputing) this._end(); + return height; + }, + + 'border-box-width': function(element) { + if (!this._preComputing) this._begin(); + var width = element.offsetWidth; + if (!this._preComputing) this._end(); + return width; + }, + + 'margin-box-height': function(element) { + var bHeight = this.get('border-box-height'), + mTop = this.get('margin-top'), + mBottom = this.get('margin-bottom'); + + if (bHeight <= 0) return 0; + + return bHeight + mTop + mBottom; + }, + + 'margin-box-width': function(element) { + var bWidth = this.get('border-box-width'), + mLeft = this.get('margin-left'), + mRight = this.get('margin-right'); + + if (bWidth <= 0) return 0; + + return bWidth + mLeft + mRight; + }, + + 'top': function(element) { + var offset = element.positionedOffset(); + return offset.top; + }, + + 'bottom': function(element) { + var offset = element.positionedOffset(), + parent = element.getOffsetParent(), + pHeight = parent.measure('height'); + + var mHeight = this.get('border-box-height'); + + return pHeight - mHeight - offset.top; + }, + + 'left': function(element) { + var offset = element.positionedOffset(); + return offset.left; + }, + + 'right': function(element) { + var offset = element.positionedOffset(), + parent = element.getOffsetParent(), + pWidth = parent.measure('width'); + + var mWidth = this.get('border-box-width'); + + return pWidth - mWidth - offset.left; + }, + + 'padding-top': function(element) { + return getPixelValue(element, 'paddingTop'); + }, + + 'padding-bottom': function(element) { + return getPixelValue(element, 'paddingBottom'); + }, + + 'padding-left': function(element) { + return getPixelValue(element, 'paddingLeft'); + }, + + 'padding-right': function(element) { + return getPixelValue(element, 'paddingRight'); + }, + + 'border-top': function(element) { + return getPixelValue(element, 'borderTopWidth'); + }, + + 'border-bottom': function(element) { + return getPixelValue(element, 'borderBottomWidth'); + }, + + 'border-left': function(element) { + return getPixelValue(element, 'borderLeftWidth'); + }, + + 'border-right': function(element) { + return getPixelValue(element, 'borderRightWidth'); + }, + + 'margin-top': function(element) { + return getPixelValue(element, 'marginTop'); + }, + + 'margin-bottom': function(element) { + return getPixelValue(element, 'marginBottom'); + }, + + 'margin-left': function(element) { + return getPixelValue(element, 'marginLeft'); + }, + + 'margin-right': function(element) { + return getPixelValue(element, 'marginRight'); + } + } + }); + + if ('getBoundingClientRect' in document.documentElement) { + Object.extend(Element.Layout.COMPUTATIONS, { + 'right': function(element) { + var parent = hasLayout(element.getOffsetParent()); + var rect = element.getBoundingClientRect(), + pRect = parent.getBoundingClientRect(); + + return (pRect.right - rect.right).round(); + }, + + 'bottom': function(element) { + var parent = hasLayout(element.getOffsetParent()); + var rect = element.getBoundingClientRect(), + pRect = parent.getBoundingClientRect(); + + return (pRect.bottom - rect.bottom).round(); + } + }); + } + + Element.Offset = Class.create({ + initialize: function(left, top) { + this.left = left.round(); + this.top = top.round(); + + this[0] = this.left; + this[1] = this.top; + }, + + relativeTo: function(offset) { + return new Element.Offset( + this.left - offset.left, + this.top - offset.top + ); + }, + + inspect: function() { + return "#".interpolate(this); + }, + + toString: function() { + return "[#{left}, #{top}]".interpolate(this); + }, + + toArray: function() { + return [this.left, this.top]; + } + }); + + function getLayout(element, preCompute) { + return new Element.Layout(element, preCompute); + } + + function measure(element, property) { + return $(element).getLayout().get(property); + } + + function getDimensions(element) { + element = $(element); + var display = Element.getStyle(element, 'display'); + + if (display && display !== 'none') { + return { width: element.offsetWidth, height: element.offsetHeight }; + } + + var style = element.style; + var originalStyles = { + visibility: style.visibility, + position: style.position, + display: style.display + }; + + var newStyles = { + visibility: 'hidden', + display: 'block' + }; + + if (originalStyles.position !== 'fixed') + newStyles.position = 'absolute'; + + Element.setStyle(element, newStyles); + + var dimensions = { + width: element.offsetWidth, + height: element.offsetHeight + }; + + Element.setStyle(element, originalStyles); + + return dimensions; + } + + function getOffsetParent(element) { + element = $(element); + + if (isDocument(element) || isDetached(element) || isBody(element) || isHtml(element)) + return $(document.body); + + var isInline = (Element.getStyle(element, 'display') === 'inline'); + if (!isInline && element.offsetParent) return $(element.offsetParent); + + while ((element = element.parentNode) && element !== document.body) { + if (Element.getStyle(element, 'position') !== 'static') { + return isHtml(element) ? $(document.body) : $(element); + } + } + + return $(document.body); + } + + + function cumulativeOffset(element) { + element = $(element); + var valueT = 0, valueL = 0; + if (element.parentNode) { + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + element = element.offsetParent; + } while (element); + } + return new Element.Offset(valueL, valueT); + } + + function positionedOffset(element) { + element = $(element); + + var layout = element.getLayout(); + + var valueT = 0, valueL = 0; + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + element = element.offsetParent; + if (element) { + if (isBody(element)) break; + var p = Element.getStyle(element, 'position'); + if (p !== 'static') break; + } + } while (element); + + valueL -= layout.get('margin-top'); + valueT -= layout.get('margin-left'); + + return new Element.Offset(valueL, valueT); + } + + function cumulativeScrollOffset(element) { + var valueT = 0, valueL = 0; + do { + valueT += element.scrollTop || 0; + valueL += element.scrollLeft || 0; + element = element.parentNode; + } while (element); + return new Element.Offset(valueL, valueT); + } + + function viewportOffset(forElement) { + element = $(element); + var valueT = 0, valueL = 0, docBody = document.body; + + var element = forElement; + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + if (element.offsetParent == docBody && + Element.getStyle(element, 'position') == 'absolute') break; + } while (element = element.offsetParent); + + element = forElement; + do { + if (element != docBody) { + valueT -= element.scrollTop || 0; + valueL -= element.scrollLeft || 0; + } + } while (element = element.parentNode); + return new Element.Offset(valueL, valueT); + } + + function absolutize(element) { + element = $(element); + + if (Element.getStyle(element, 'position') === 'absolute') { + return element; + } + + var offsetParent = getOffsetParent(element); + var eOffset = element.viewportOffset(), + pOffset = offsetParent.viewportOffset(); + + var offset = eOffset.relativeTo(pOffset); + var layout = element.getLayout(); + + element.store('prototype_absolutize_original_styles', { + left: element.getStyle('left'), + top: element.getStyle('top'), + width: element.getStyle('width'), + height: element.getStyle('height') + }); + + element.setStyle({ + position: 'absolute', + top: offset.top + 'px', + left: offset.left + 'px', + width: layout.get('width') + 'px', + height: layout.get('height') + 'px' + }); + + return element; + } + + function relativize(element) { + element = $(element); + if (Element.getStyle(element, 'position') === 'relative') { + return element; + } + + var originalStyles = + element.retrieve('prototype_absolutize_original_styles'); + + if (originalStyles) element.setStyle(originalStyles); + return element; + } + + if (Prototype.Browser.IE) { + getOffsetParent = getOffsetParent.wrap( + function(proceed, element) { + element = $(element); + + if (isDocument(element) || isDetached(element) || isBody(element) || isHtml(element)) + return $(document.body); + + var position = element.getStyle('position'); + if (position !== 'static') return proceed(element); + + element.setStyle({ position: 'relative' }); + var value = proceed(element); + element.setStyle({ position: position }); + return value; + } + ); + + positionedOffset = positionedOffset.wrap(function(proceed, element) { + element = $(element); + if (!element.parentNode) return new Element.Offset(0, 0); + var position = element.getStyle('position'); + if (position !== 'static') return proceed(element); + + var offsetParent = element.getOffsetParent(); + if (offsetParent && offsetParent.getStyle('position') === 'fixed') + hasLayout(offsetParent); + + element.setStyle({ position: 'relative' }); + var value = proceed(element); + element.setStyle({ position: position }); + return value; + }); + } else if (Prototype.Browser.Webkit) { + cumulativeOffset = function(element) { + element = $(element); + var valueT = 0, valueL = 0; + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + if (element.offsetParent == document.body) + if (Element.getStyle(element, 'position') == 'absolute') break; + + element = element.offsetParent; + } while (element); + + return new Element.Offset(valueL, valueT); + }; + } + + + Element.addMethods({ + getLayout: getLayout, + measure: measure, + getDimensions: getDimensions, + getOffsetParent: getOffsetParent, + cumulativeOffset: cumulativeOffset, + positionedOffset: positionedOffset, + cumulativeScrollOffset: cumulativeScrollOffset, + viewportOffset: viewportOffset, + absolutize: absolutize, + relativize: relativize + }); + + function isBody(element) { + return element.nodeName.toUpperCase() === 'BODY'; + } + + function isHtml(element) { + return element.nodeName.toUpperCase() === 'HTML'; + } + + function isDocument(element) { + return element.nodeType === Node.DOCUMENT_NODE; + } + + function isDetached(element) { + return element !== document.body && + !Element.descendantOf(element, document.body); + } + + if ('getBoundingClientRect' in document.documentElement) { + Element.addMethods({ + viewportOffset: function(element) { + element = $(element); + if (isDetached(element)) return new Element.Offset(0, 0); + + var rect = element.getBoundingClientRect(), + docEl = document.documentElement; + return new Element.Offset(rect.left - docEl.clientLeft, + rect.top - docEl.clientTop); + } + }); + } +})(); +window.$$ = function() { + var expression = $A(arguments).join(', '); + return Prototype.Selector.select(expression, document); +}; + +Prototype.Selector = (function() { + + function select() { + throw new Error('Method "Prototype.Selector.select" must be defined.'); + } + + function match() { + throw new Error('Method "Prototype.Selector.match" must be defined.'); + } + + function find(elements, expression, index) { + index = index || 0; + var match = Prototype.Selector.match, length = elements.length, matchIndex = 0, i; + + for (i = 0; i < length; i++) { + if (match(elements[i], expression) && index == matchIndex++) { + return Element.extend(elements[i]); + } + } + } + + function extendElements(elements) { + for (var i = 0, length = elements.length; i < length; i++) { + Element.extend(elements[i]); + } + return elements; + } + + + var K = Prototype.K; + + return { + select: select, + match: match, + find: find, + extendElements: (Element.extend === K) ? K : extendElements, + extendElement: Element.extend + }; +})(); +/*! + * Sizzle CSS Selector Engine - v1.0 + * Copyright 2009, The Dojo Foundation + * Released under the MIT, BSD, and GPL Licenses. + * More information: http://sizzlejs.com/ + */ +(function(){ + +var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^[\]]*\]|['"][^'"]*['"]|[^[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g, + done = 0, + toString = Object.prototype.toString, + hasDuplicate = false, + baseHasDuplicate = true; + +[0, 0].sort(function(){ + baseHasDuplicate = false; + return 0; +}); + +var Sizzle = function(selector, context, results, seed) { + results = results || []; + var origContext = context = context || document; + + if ( context.nodeType !== 1 && context.nodeType !== 9 ) { + return []; + } + + if ( !selector || typeof selector !== "string" ) { + return results; + } + + var parts = [], m, set, checkSet, check, mode, extra, prune = true, contextXML = isXML(context), + soFar = selector; + + while ( (chunker.exec(""), m = chunker.exec(soFar)) !== null ) { + soFar = m[3]; + + parts.push( m[1] ); + + if ( m[2] ) { + extra = m[3]; + break; + } + } + + if ( parts.length > 1 && origPOS.exec( selector ) ) { + if ( parts.length === 2 && Expr.relative[ parts[0] ] ) { + set = posProcess( parts[0] + parts[1], context ); + } else { + set = Expr.relative[ parts[0] ] ? + [ context ] : + Sizzle( parts.shift(), context ); + + while ( parts.length ) { + selector = parts.shift(); + + if ( Expr.relative[ selector ] ) + selector += parts.shift(); + + set = posProcess( selector, set ); + } + } + } else { + if ( !seed && parts.length > 1 && context.nodeType === 9 && !contextXML && + Expr.match.ID.test(parts[0]) && !Expr.match.ID.test(parts[parts.length - 1]) ) { + var ret = Sizzle.find( parts.shift(), context, contextXML ); + context = ret.expr ? Sizzle.filter( ret.expr, ret.set )[0] : ret.set[0]; + } + + if ( context ) { + var ret = seed ? + { expr: parts.pop(), set: makeArray(seed) } : + Sizzle.find( parts.pop(), parts.length === 1 && (parts[0] === "~" || parts[0] === "+") && context.parentNode ? context.parentNode : context, contextXML ); + set = ret.expr ? Sizzle.filter( ret.expr, ret.set ) : ret.set; + + if ( parts.length > 0 ) { + checkSet = makeArray(set); + } else { + prune = false; + } + + while ( parts.length ) { + var cur = parts.pop(), pop = cur; + + if ( !Expr.relative[ cur ] ) { + cur = ""; + } else { + pop = parts.pop(); + } + + if ( pop == null ) { + pop = context; + } + + Expr.relative[ cur ]( checkSet, pop, contextXML ); + } + } else { + checkSet = parts = []; + } + } + + if ( !checkSet ) { + checkSet = set; + } + + if ( !checkSet ) { + throw "Syntax error, unrecognized expression: " + (cur || selector); + } + + if ( toString.call(checkSet) === "[object Array]" ) { + if ( !prune ) { + results.push.apply( results, checkSet ); + } else if ( context && context.nodeType === 1 ) { + for ( var i = 0; checkSet[i] != null; i++ ) { + if ( checkSet[i] && (checkSet[i] === true || checkSet[i].nodeType === 1 && contains(context, checkSet[i])) ) { + results.push( set[i] ); + } + } + } else { + for ( var i = 0; checkSet[i] != null; i++ ) { + if ( checkSet[i] && checkSet[i].nodeType === 1 ) { + results.push( set[i] ); + } + } + } + } else { + makeArray( checkSet, results ); + } + + if ( extra ) { + Sizzle( extra, origContext, results, seed ); + Sizzle.uniqueSort( results ); + } + + return results; +}; + +Sizzle.uniqueSort = function(results){ + if ( sortOrder ) { + hasDuplicate = baseHasDuplicate; + results.sort(sortOrder); + + if ( hasDuplicate ) { + for ( var i = 1; i < results.length; i++ ) { + if ( results[i] === results[i-1] ) { + results.splice(i--, 1); + } + } + } + } + + return results; +}; + +Sizzle.matches = function(expr, set){ + return Sizzle(expr, null, null, set); +}; + +Sizzle.find = function(expr, context, isXML){ + var set, match; + + if ( !expr ) { + return []; + } + + for ( var i = 0, l = Expr.order.length; i < l; i++ ) { + var type = Expr.order[i], match; + + if ( (match = Expr.leftMatch[ type ].exec( expr )) ) { + var left = match[1]; + match.splice(1,1); + + if ( left.substr( left.length - 1 ) !== "\\" ) { + match[1] = (match[1] || "").replace(/\\/g, ""); + set = Expr.find[ type ]( match, context, isXML ); + if ( set != null ) { + expr = expr.replace( Expr.match[ type ], "" ); + break; + } + } + } + } + + if ( !set ) { + set = context.getElementsByTagName("*"); + } + + return {set: set, expr: expr}; +}; + +Sizzle.filter = function(expr, set, inplace, not){ + var old = expr, result = [], curLoop = set, match, anyFound, + isXMLFilter = set && set[0] && isXML(set[0]); + + while ( expr && set.length ) { + for ( var type in Expr.filter ) { + if ( (match = Expr.match[ type ].exec( expr )) != null ) { + var filter = Expr.filter[ type ], found, item; + anyFound = false; + + if ( curLoop == result ) { + result = []; + } + + if ( Expr.preFilter[ type ] ) { + match = Expr.preFilter[ type ]( match, curLoop, inplace, result, not, isXMLFilter ); + + if ( !match ) { + anyFound = found = true; + } else if ( match === true ) { + continue; + } + } + + if ( match ) { + for ( var i = 0; (item = curLoop[i]) != null; i++ ) { + if ( item ) { + found = filter( item, match, i, curLoop ); + var pass = not ^ !!found; + + if ( inplace && found != null ) { + if ( pass ) { + anyFound = true; + } else { + curLoop[i] = false; + } + } else if ( pass ) { + result.push( item ); + anyFound = true; + } + } + } + } + + if ( found !== undefined ) { + if ( !inplace ) { + curLoop = result; + } + + expr = expr.replace( Expr.match[ type ], "" ); + + if ( !anyFound ) { + return []; + } + + break; + } + } + } + + if ( expr == old ) { + if ( anyFound == null ) { + throw "Syntax error, unrecognized expression: " + expr; + } else { + break; + } + } + + old = expr; + } + + return curLoop; +}; + +var Expr = Sizzle.selectors = { + order: [ "ID", "NAME", "TAG" ], + match: { + ID: /#((?:[\w\u00c0-\uFFFF-]|\\.)+)/, + CLASS: /\.((?:[\w\u00c0-\uFFFF-]|\\.)+)/, + NAME: /\[name=['"]*((?:[\w\u00c0-\uFFFF-]|\\.)+)['"]*\]/, + ATTR: /\[\s*((?:[\w\u00c0-\uFFFF-]|\\.)+)\s*(?:(\S?=)\s*(['"]*)(.*?)\3|)\s*\]/, + TAG: /^((?:[\w\u00c0-\uFFFF\*-]|\\.)+)/, + CHILD: /:(only|nth|last|first)-child(?:\((even|odd|[\dn+-]*)\))?/, + POS: /:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^-]|$)/, + PSEUDO: /:((?:[\w\u00c0-\uFFFF-]|\\.)+)(?:\((['"]*)((?:\([^\)]+\)|[^\2\(\)]*)+)\2\))?/ + }, + leftMatch: {}, + attrMap: { + "class": "className", + "for": "htmlFor" + }, + attrHandle: { + href: function(elem){ + return elem.getAttribute("href"); + } + }, + relative: { + "+": function(checkSet, part, isXML){ + var isPartStr = typeof part === "string", + isTag = isPartStr && !/\W/.test(part), + isPartStrNotTag = isPartStr && !isTag; + + if ( isTag && !isXML ) { + part = part.toUpperCase(); + } + + for ( var i = 0, l = checkSet.length, elem; i < l; i++ ) { + if ( (elem = checkSet[i]) ) { + while ( (elem = elem.previousSibling) && elem.nodeType !== 1 ) {} + + checkSet[i] = isPartStrNotTag || elem && elem.nodeName === part ? + elem || false : + elem === part; + } + } + + if ( isPartStrNotTag ) { + Sizzle.filter( part, checkSet, true ); + } + }, + ">": function(checkSet, part, isXML){ + var isPartStr = typeof part === "string"; + + if ( isPartStr && !/\W/.test(part) ) { + part = isXML ? part : part.toUpperCase(); + + for ( var i = 0, l = checkSet.length; i < l; i++ ) { + var elem = checkSet[i]; + if ( elem ) { + var parent = elem.parentNode; + checkSet[i] = parent.nodeName === part ? parent : false; + } + } + } else { + for ( var i = 0, l = checkSet.length; i < l; i++ ) { + var elem = checkSet[i]; + if ( elem ) { + checkSet[i] = isPartStr ? + elem.parentNode : + elem.parentNode === part; + } + } + + if ( isPartStr ) { + Sizzle.filter( part, checkSet, true ); + } + } + }, + "": function(checkSet, part, isXML){ + var doneName = done++, checkFn = dirCheck; + + if ( !/\W/.test(part) ) { + var nodeCheck = part = isXML ? part : part.toUpperCase(); + checkFn = dirNodeCheck; + } + + checkFn("parentNode", part, doneName, checkSet, nodeCheck, isXML); + }, + "~": function(checkSet, part, isXML){ + var doneName = done++, checkFn = dirCheck; + + if ( typeof part === "string" && !/\W/.test(part) ) { + var nodeCheck = part = isXML ? part : part.toUpperCase(); + checkFn = dirNodeCheck; + } + + checkFn("previousSibling", part, doneName, checkSet, nodeCheck, isXML); + } + }, + find: { + ID: function(match, context, isXML){ + if ( typeof context.getElementById !== "undefined" && !isXML ) { + var m = context.getElementById(match[1]); + return m ? [m] : []; + } + }, + NAME: function(match, context, isXML){ + if ( typeof context.getElementsByName !== "undefined" ) { + var ret = [], results = context.getElementsByName(match[1]); + + for ( var i = 0, l = results.length; i < l; i++ ) { + if ( results[i].getAttribute("name") === match[1] ) { + ret.push( results[i] ); + } + } + + return ret.length === 0 ? null : ret; + } + }, + TAG: function(match, context){ + return context.getElementsByTagName(match[1]); + } + }, + preFilter: { + CLASS: function(match, curLoop, inplace, result, not, isXML){ + match = " " + match[1].replace(/\\/g, "") + " "; + + if ( isXML ) { + return match; + } + + for ( var i = 0, elem; (elem = curLoop[i]) != null; i++ ) { + if ( elem ) { + if ( not ^ (elem.className && (" " + elem.className + " ").indexOf(match) >= 0) ) { + if ( !inplace ) + result.push( elem ); + } else if ( inplace ) { + curLoop[i] = false; + } + } + } + + return false; + }, + ID: function(match){ + return match[1].replace(/\\/g, ""); + }, + TAG: function(match, curLoop){ + for ( var i = 0; curLoop[i] === false; i++ ){} + return curLoop[i] && isXML(curLoop[i]) ? match[1] : match[1].toUpperCase(); + }, + CHILD: function(match){ + if ( match[1] == "nth" ) { + var test = /(-?)(\d*)n((?:\+|-)?\d*)/.exec( + match[2] == "even" && "2n" || match[2] == "odd" && "2n+1" || + !/\D/.test( match[2] ) && "0n+" + match[2] || match[2]); + + match[2] = (test[1] + (test[2] || 1)) - 0; + match[3] = test[3] - 0; + } + + match[0] = done++; + + return match; + }, + ATTR: function(match, curLoop, inplace, result, not, isXML){ + var name = match[1].replace(/\\/g, ""); + + if ( !isXML && Expr.attrMap[name] ) { + match[1] = Expr.attrMap[name]; + } + + if ( match[2] === "~=" ) { + match[4] = " " + match[4] + " "; + } + + return match; + }, + PSEUDO: function(match, curLoop, inplace, result, not){ + if ( match[1] === "not" ) { + if ( ( chunker.exec(match[3]) || "" ).length > 1 || /^\w/.test(match[3]) ) { + match[3] = Sizzle(match[3], null, null, curLoop); + } else { + var ret = Sizzle.filter(match[3], curLoop, inplace, true ^ not); + if ( !inplace ) { + result.push.apply( result, ret ); + } + return false; + } + } else if ( Expr.match.POS.test( match[0] ) || Expr.match.CHILD.test( match[0] ) ) { + return true; + } + + return match; + }, + POS: function(match){ + match.unshift( true ); + return match; + } + }, + filters: { + enabled: function(elem){ + return elem.disabled === false && elem.type !== "hidden"; + }, + disabled: function(elem){ + return elem.disabled === true; + }, + checked: function(elem){ + return elem.checked === true; + }, + selected: function(elem){ + elem.parentNode.selectedIndex; + return elem.selected === true; + }, + parent: function(elem){ + return !!elem.firstChild; + }, + empty: function(elem){ + return !elem.firstChild; + }, + has: function(elem, i, match){ + return !!Sizzle( match[3], elem ).length; + }, + header: function(elem){ + return /h\d/i.test( elem.nodeName ); + }, + text: function(elem){ + return "text" === elem.type; + }, + radio: function(elem){ + return "radio" === elem.type; + }, + checkbox: function(elem){ + return "checkbox" === elem.type; + }, + file: function(elem){ + return "file" === elem.type; + }, + password: function(elem){ + return "password" === elem.type; + }, + submit: function(elem){ + return "submit" === elem.type; + }, + image: function(elem){ + return "image" === elem.type; + }, + reset: function(elem){ + return "reset" === elem.type; + }, + button: function(elem){ + return "button" === elem.type || elem.nodeName.toUpperCase() === "BUTTON"; + }, + input: function(elem){ + return /input|select|textarea|button/i.test(elem.nodeName); + } + }, + setFilters: { + first: function(elem, i){ + return i === 0; + }, + last: function(elem, i, match, array){ + return i === array.length - 1; + }, + even: function(elem, i){ + return i % 2 === 0; + }, + odd: function(elem, i){ + return i % 2 === 1; + }, + lt: function(elem, i, match){ + return i < match[3] - 0; + }, + gt: function(elem, i, match){ + return i > match[3] - 0; + }, + nth: function(elem, i, match){ + return match[3] - 0 == i; + }, + eq: function(elem, i, match){ + return match[3] - 0 == i; + } + }, + filter: { + PSEUDO: function(elem, match, i, array){ + var name = match[1], filter = Expr.filters[ name ]; + + if ( filter ) { + return filter( elem, i, match, array ); + } else if ( name === "contains" ) { + return (elem.textContent || elem.innerText || "").indexOf(match[3]) >= 0; + } else if ( name === "not" ) { + var not = match[3]; + + for ( var i = 0, l = not.length; i < l; i++ ) { + if ( not[i] === elem ) { + return false; + } + } + + return true; + } + }, + CHILD: function(elem, match){ + var type = match[1], node = elem; + switch (type) { + case 'only': + case 'first': + while ( (node = node.previousSibling) ) { + if ( node.nodeType === 1 ) return false; + } + if ( type == 'first') return true; + node = elem; + case 'last': + while ( (node = node.nextSibling) ) { + if ( node.nodeType === 1 ) return false; + } + return true; + case 'nth': + var first = match[2], last = match[3]; + + if ( first == 1 && last == 0 ) { + return true; + } + + var doneName = match[0], + parent = elem.parentNode; + + if ( parent && (parent.sizcache !== doneName || !elem.nodeIndex) ) { + var count = 0; + for ( node = parent.firstChild; node; node = node.nextSibling ) { + if ( node.nodeType === 1 ) { + node.nodeIndex = ++count; + } + } + parent.sizcache = doneName; + } + + var diff = elem.nodeIndex - last; + if ( first == 0 ) { + return diff == 0; + } else { + return ( diff % first == 0 && diff / first >= 0 ); + } + } + }, + ID: function(elem, match){ + return elem.nodeType === 1 && elem.getAttribute("id") === match; + }, + TAG: function(elem, match){ + return (match === "*" && elem.nodeType === 1) || elem.nodeName === match; + }, + CLASS: function(elem, match){ + return (" " + (elem.className || elem.getAttribute("class")) + " ") + .indexOf( match ) > -1; + }, + ATTR: function(elem, match){ + var name = match[1], + result = Expr.attrHandle[ name ] ? + Expr.attrHandle[ name ]( elem ) : + elem[ name ] != null ? + elem[ name ] : + elem.getAttribute( name ), + value = result + "", + type = match[2], + check = match[4]; + + return result == null ? + type === "!=" : + type === "=" ? + value === check : + type === "*=" ? + value.indexOf(check) >= 0 : + type === "~=" ? + (" " + value + " ").indexOf(check) >= 0 : + !check ? + value && result !== false : + type === "!=" ? + value != check : + type === "^=" ? + value.indexOf(check) === 0 : + type === "$=" ? + value.substr(value.length - check.length) === check : + type === "|=" ? + value === check || value.substr(0, check.length + 1) === check + "-" : + false; + }, + POS: function(elem, match, i, array){ + var name = match[2], filter = Expr.setFilters[ name ]; + + if ( filter ) { + return filter( elem, i, match, array ); + } + } + } +}; + +var origPOS = Expr.match.POS; + +for ( var type in Expr.match ) { + Expr.match[ type ] = new RegExp( Expr.match[ type ].source + /(?![^\[]*\])(?![^\(]*\))/.source ); + Expr.leftMatch[ type ] = new RegExp( /(^(?:.|\r|\n)*?)/.source + Expr.match[ type ].source ); +} + +var makeArray = function(array, results) { + array = Array.prototype.slice.call( array, 0 ); + + if ( results ) { + results.push.apply( results, array ); + return results; + } + + return array; +}; + +try { + Array.prototype.slice.call( document.documentElement.childNodes, 0 ); + +} catch(e){ + makeArray = function(array, results) { + var ret = results || []; + + if ( toString.call(array) === "[object Array]" ) { + Array.prototype.push.apply( ret, array ); + } else { + if ( typeof array.length === "number" ) { + for ( var i = 0, l = array.length; i < l; i++ ) { + ret.push( array[i] ); + } + } else { + for ( var i = 0; array[i]; i++ ) { + ret.push( array[i] ); + } + } + } + + return ret; + }; +} + +var sortOrder; + +if ( document.documentElement.compareDocumentPosition ) { + sortOrder = function( a, b ) { + if ( !a.compareDocumentPosition || !b.compareDocumentPosition ) { + if ( a == b ) { + hasDuplicate = true; + } + return 0; + } + + var ret = a.compareDocumentPosition(b) & 4 ? -1 : a === b ? 0 : 1; + if ( ret === 0 ) { + hasDuplicate = true; + } + return ret; + }; +} else if ( "sourceIndex" in document.documentElement ) { + sortOrder = function( a, b ) { + if ( !a.sourceIndex || !b.sourceIndex ) { + if ( a == b ) { + hasDuplicate = true; + } + return 0; + } + + var ret = a.sourceIndex - b.sourceIndex; + if ( ret === 0 ) { + hasDuplicate = true; + } + return ret; + }; +} else if ( document.createRange ) { + sortOrder = function( a, b ) { + if ( !a.ownerDocument || !b.ownerDocument ) { + if ( a == b ) { + hasDuplicate = true; + } + return 0; + } + + var aRange = a.ownerDocument.createRange(), bRange = b.ownerDocument.createRange(); + aRange.setStart(a, 0); + aRange.setEnd(a, 0); + bRange.setStart(b, 0); + bRange.setEnd(b, 0); + var ret = aRange.compareBoundaryPoints(Range.START_TO_END, bRange); + if ( ret === 0 ) { + hasDuplicate = true; + } + return ret; + }; +} + +(function(){ + var form = document.createElement("div"), + id = "script" + (new Date).getTime(); + form.innerHTML = ""; + + var root = document.documentElement; + root.insertBefore( form, root.firstChild ); + + if ( !!document.getElementById( id ) ) { + Expr.find.ID = function(match, context, isXML){ + if ( typeof context.getElementById !== "undefined" && !isXML ) { + var m = context.getElementById(match[1]); + return m ? m.id === match[1] || typeof m.getAttributeNode !== "undefined" && m.getAttributeNode("id").nodeValue === match[1] ? [m] : undefined : []; + } + }; + + Expr.filter.ID = function(elem, match){ + var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode("id"); + return elem.nodeType === 1 && node && node.nodeValue === match; + }; + } + + root.removeChild( form ); + root = form = null; // release memory in IE +})(); + +(function(){ + + var div = document.createElement("div"); + div.appendChild( document.createComment("") ); + + if ( div.getElementsByTagName("*").length > 0 ) { + Expr.find.TAG = function(match, context){ + var results = context.getElementsByTagName(match[1]); + + if ( match[1] === "*" ) { + var tmp = []; + + for ( var i = 0; results[i]; i++ ) { + if ( results[i].nodeType === 1 ) { + tmp.push( results[i] ); + } + } + + results = tmp; + } + + return results; + }; + } + + div.innerHTML = ""; + if ( div.firstChild && typeof div.firstChild.getAttribute !== "undefined" && + div.firstChild.getAttribute("href") !== "#" ) { + Expr.attrHandle.href = function(elem){ + return elem.getAttribute("href", 2); + }; + } + + div = null; // release memory in IE +})(); + +if ( document.querySelectorAll ) (function(){ + var oldSizzle = Sizzle, div = document.createElement("div"); + div.innerHTML = "

"; + + if ( div.querySelectorAll && div.querySelectorAll(".TEST").length === 0 ) { + return; + } + + Sizzle = function(query, context, extra, seed){ + context = context || document; + + if ( !seed && context.nodeType === 9 && !isXML(context) ) { + try { + return makeArray( context.querySelectorAll(query), extra ); + } catch(e){} + } + + return oldSizzle(query, context, extra, seed); + }; + + for ( var prop in oldSizzle ) { + Sizzle[ prop ] = oldSizzle[ prop ]; + } + + div = null; // release memory in IE +})(); + +if ( document.getElementsByClassName && document.documentElement.getElementsByClassName ) (function(){ + var div = document.createElement("div"); + div.innerHTML = "
"; + + if ( div.getElementsByClassName("e").length === 0 ) + return; + + div.lastChild.className = "e"; + + if ( div.getElementsByClassName("e").length === 1 ) + return; + + Expr.order.splice(1, 0, "CLASS"); + Expr.find.CLASS = function(match, context, isXML) { + if ( typeof context.getElementsByClassName !== "undefined" && !isXML ) { + return context.getElementsByClassName(match[1]); + } + }; + + div = null; // release memory in IE +})(); + +function dirNodeCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) { + var sibDir = dir == "previousSibling" && !isXML; + for ( var i = 0, l = checkSet.length; i < l; i++ ) { + var elem = checkSet[i]; + if ( elem ) { + if ( sibDir && elem.nodeType === 1 ){ + elem.sizcache = doneName; + elem.sizset = i; + } + elem = elem[dir]; + var match = false; + + while ( elem ) { + if ( elem.sizcache === doneName ) { + match = checkSet[elem.sizset]; + break; + } + + if ( elem.nodeType === 1 && !isXML ){ + elem.sizcache = doneName; + elem.sizset = i; + } + + if ( elem.nodeName === cur ) { + match = elem; + break; + } + + elem = elem[dir]; + } + + checkSet[i] = match; + } + } +} + +function dirCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) { + var sibDir = dir == "previousSibling" && !isXML; + for ( var i = 0, l = checkSet.length; i < l; i++ ) { + var elem = checkSet[i]; + if ( elem ) { + if ( sibDir && elem.nodeType === 1 ) { + elem.sizcache = doneName; + elem.sizset = i; + } + elem = elem[dir]; + var match = false; + + while ( elem ) { + if ( elem.sizcache === doneName ) { + match = checkSet[elem.sizset]; + break; + } + + if ( elem.nodeType === 1 ) { + if ( !isXML ) { + elem.sizcache = doneName; + elem.sizset = i; + } + if ( typeof cur !== "string" ) { + if ( elem === cur ) { + match = true; + break; + } + + } else if ( Sizzle.filter( cur, [elem] ).length > 0 ) { + match = elem; + break; + } + } + + elem = elem[dir]; + } + + checkSet[i] = match; + } + } +} + +var contains = document.compareDocumentPosition ? function(a, b){ + return a.compareDocumentPosition(b) & 16; +} : function(a, b){ + return a !== b && (a.contains ? a.contains(b) : true); +}; + +var isXML = function(elem){ + return elem.nodeType === 9 && elem.documentElement.nodeName !== "HTML" || + !!elem.ownerDocument && elem.ownerDocument.documentElement.nodeName !== "HTML"; +}; + +var posProcess = function(selector, context){ + var tmpSet = [], later = "", match, + root = context.nodeType ? [context] : context; + + while ( (match = Expr.match.PSEUDO.exec( selector )) ) { + later += match[0]; + selector = selector.replace( Expr.match.PSEUDO, "" ); + } + + selector = Expr.relative[selector] ? selector + "*" : selector; + + for ( var i = 0, l = root.length; i < l; i++ ) { + Sizzle( selector, root[i], tmpSet ); + } + + return Sizzle.filter( later, tmpSet ); +}; + + +window.Sizzle = Sizzle; + +})(); + +Prototype._original_property = window.Sizzle; + +;(function(engine) { + var extendElements = Prototype.Selector.extendElements; + + function select(selector, scope) { + return extendElements(engine(selector, scope || document)); + } + + function match(element, selector) { + return engine.matches(selector, [element]).length == 1; + } + + Prototype.Selector.engine = engine; + Prototype.Selector.select = select; + Prototype.Selector.match = match; +})(Sizzle); + +window.Sizzle = Prototype._original_property; +delete Prototype._original_property; + +var Form = { + reset: function(form) { + form = $(form); + form.reset(); + return form; + }, + + serializeElements: function(elements, options) { + if (typeof options != 'object') options = { hash: !!options }; + else if (Object.isUndefined(options.hash)) options.hash = true; + var key, value, submitted = false, submit = options.submit, accumulator, initial; + + if (options.hash) { + initial = {}; + accumulator = function(result, key, value) { + if (key in result) { + if (!Object.isArray(result[key])) result[key] = [result[key]]; + result[key].push(value); + } else result[key] = value; + return result; + }; + } else { + initial = ''; + accumulator = function(result, key, value) { + return result + (result ? '&' : '') + encodeURIComponent(key) + '=' + encodeURIComponent(value); + } + } + + return elements.inject(initial, function(result, element) { + if (!element.disabled && element.name) { + key = element.name; value = $(element).getValue(); + if (value != null && element.type != 'file' && (element.type != 'submit' || (!submitted && + submit !== false && (!submit || key == submit) && (submitted = true)))) { + result = accumulator(result, key, value); + } + } + return result; + }); + } +}; + +Form.Methods = { + serialize: function(form, options) { + return Form.serializeElements(Form.getElements(form), options); + }, + + getElements: function(form) { + var elements = $(form).getElementsByTagName('*'), + element, + arr = [ ], + serializers = Form.Element.Serializers; + for (var i = 0; element = elements[i]; i++) { + arr.push(element); + } + return arr.inject([], function(elements, child) { + if (serializers[child.tagName.toLowerCase()]) + elements.push(Element.extend(child)); + return elements; + }) + }, + + getInputs: function(form, typeName, name) { + form = $(form); + var inputs = form.getElementsByTagName('input'); + + if (!typeName && !name) return $A(inputs).map(Element.extend); + + for (var i = 0, matchingInputs = [], length = inputs.length; i < length; i++) { + var input = inputs[i]; + if ((typeName && input.type != typeName) || (name && input.name != name)) + continue; + matchingInputs.push(Element.extend(input)); + } + + return matchingInputs; + }, + + disable: function(form) { + form = $(form); + Form.getElements(form).invoke('disable'); + return form; + }, + + enable: function(form) { + form = $(form); + Form.getElements(form).invoke('enable'); + return form; + }, + + findFirstElement: function(form) { + var elements = $(form).getElements().findAll(function(element) { + return 'hidden' != element.type && !element.disabled; + }); + var firstByIndex = elements.findAll(function(element) { + return element.hasAttribute('tabIndex') && element.tabIndex >= 0; + }).sortBy(function(element) { return element.tabIndex }).first(); + + return firstByIndex ? firstByIndex : elements.find(function(element) { + return /^(?:input|select|textarea)$/i.test(element.tagName); + }); + }, + + focusFirstElement: function(form) { + form = $(form); + var element = form.findFirstElement(); + if (element) element.activate(); + return form; + }, + + request: function(form, options) { + form = $(form), options = Object.clone(options || { }); + + var params = options.parameters, action = form.readAttribute('action') || ''; + if (action.blank()) action = window.location.href; + options.parameters = form.serialize(true); + + if (params) { + if (Object.isString(params)) params = params.toQueryParams(); + Object.extend(options.parameters, params); + } + + if (form.hasAttribute('method') && !options.method) + options.method = form.method; + + return new Ajax.Request(action, options); + } +}; + +/*--------------------------------------------------------------------------*/ + + +Form.Element = { + focus: function(element) { + $(element).focus(); + return element; + }, + + select: function(element) { + $(element).select(); + return element; + } +}; + +Form.Element.Methods = { + + serialize: function(element) { + element = $(element); + if (!element.disabled && element.name) { + var value = element.getValue(); + if (value != undefined) { + var pair = { }; + pair[element.name] = value; + return Object.toQueryString(pair); + } + } + return ''; + }, + + getValue: function(element) { + element = $(element); + var method = element.tagName.toLowerCase(); + return Form.Element.Serializers[method](element); + }, + + setValue: function(element, value) { + element = $(element); + var method = element.tagName.toLowerCase(); + Form.Element.Serializers[method](element, value); + return element; + }, + + clear: function(element) { + $(element).value = ''; + return element; + }, + + present: function(element) { + return $(element).value != ''; + }, + + activate: function(element) { + element = $(element); + try { + element.focus(); + if (element.select && (element.tagName.toLowerCase() != 'input' || + !(/^(?:button|reset|submit)$/i.test(element.type)))) + element.select(); + } catch (e) { } + return element; + }, + + disable: function(element) { + element = $(element); + element.disabled = true; + return element; + }, + + enable: function(element) { + element = $(element); + element.disabled = false; + return element; + } +}; + +/*--------------------------------------------------------------------------*/ + +var Field = Form.Element; + +var $F = Form.Element.Methods.getValue; + +/*--------------------------------------------------------------------------*/ + +Form.Element.Serializers = (function() { + function input(element, value) { + switch (element.type.toLowerCase()) { + case 'checkbox': + case 'radio': + return inputSelector(element, value); + default: + return valueSelector(element, value); + } + } + + function inputSelector(element, value) { + if (Object.isUndefined(value)) + return element.checked ? element.value : null; + else element.checked = !!value; + } + + function valueSelector(element, value) { + if (Object.isUndefined(value)) return element.value; + else element.value = value; + } + + function select(element, value) { + if (Object.isUndefined(value)) + return (element.type === 'select-one' ? selectOne : selectMany)(element); + + var opt, currentValue, single = !Object.isArray(value); + for (var i = 0, length = element.length; i < length; i++) { + opt = element.options[i]; + currentValue = this.optionValue(opt); + if (single) { + if (currentValue == value) { + opt.selected = true; + return; + } + } + else opt.selected = value.include(currentValue); + } + } + + function selectOne(element) { + var index = element.selectedIndex; + return index >= 0 ? optionValue(element.options[index]) : null; + } + + function selectMany(element) { + var values, length = element.length; + if (!length) return null; + + for (var i = 0, values = []; i < length; i++) { + var opt = element.options[i]; + if (opt.selected) values.push(optionValue(opt)); + } + return values; + } + + function optionValue(opt) { + return Element.hasAttribute(opt, 'value') ? opt.value : opt.text; + } + + return { + input: input, + inputSelector: inputSelector, + textarea: valueSelector, + select: select, + selectOne: selectOne, + selectMany: selectMany, + optionValue: optionValue, + button: valueSelector + }; +})(); + +/*--------------------------------------------------------------------------*/ + + +Abstract.TimedObserver = Class.create(PeriodicalExecuter, { + initialize: function($super, element, frequency, callback) { + $super(callback, frequency); + this.element = $(element); + this.lastValue = this.getValue(); + }, + + execute: function() { + var value = this.getValue(); + if (Object.isString(this.lastValue) && Object.isString(value) ? + this.lastValue != value : String(this.lastValue) != String(value)) { + this.callback(this.element, value); + this.lastValue = value; + } + } +}); + +Form.Element.Observer = Class.create(Abstract.TimedObserver, { + getValue: function() { + return Form.Element.getValue(this.element); + } +}); + +Form.Observer = Class.create(Abstract.TimedObserver, { + getValue: function() { + return Form.serialize(this.element); + } +}); + +/*--------------------------------------------------------------------------*/ + +Abstract.EventObserver = Class.create({ + initialize: function(element, callback) { + this.element = $(element); + this.callback = callback; + + this.lastValue = this.getValue(); + if (this.element.tagName.toLowerCase() == 'form') + this.registerFormCallbacks(); + else + this.registerCallback(this.element); + }, + + onElementEvent: function() { + var value = this.getValue(); + if (this.lastValue != value) { + this.callback(this.element, value); + this.lastValue = value; + } + }, + + registerFormCallbacks: function() { + Form.getElements(this.element).each(this.registerCallback, this); + }, + + registerCallback: function(element) { + if (element.type) { + switch (element.type.toLowerCase()) { + case 'checkbox': + case 'radio': + Event.observe(element, 'click', this.onElementEvent.bind(this)); + break; + default: + Event.observe(element, 'change', this.onElementEvent.bind(this)); + break; + } + } + } +}); + +Form.Element.EventObserver = Class.create(Abstract.EventObserver, { + getValue: function() { + return Form.Element.getValue(this.element); + } +}); + +Form.EventObserver = Class.create(Abstract.EventObserver, { + getValue: function() { + return Form.serialize(this.element); + } +}); +(function() { + + var Event = { + KEY_BACKSPACE: 8, + KEY_TAB: 9, + KEY_RETURN: 13, + KEY_ESC: 27, + KEY_LEFT: 37, + KEY_UP: 38, + KEY_RIGHT: 39, + KEY_DOWN: 40, + KEY_DELETE: 46, + KEY_HOME: 36, + KEY_END: 35, + KEY_PAGEUP: 33, + KEY_PAGEDOWN: 34, + KEY_INSERT: 45, + + cache: {} + }; + + var docEl = document.documentElement; + var MOUSEENTER_MOUSELEAVE_EVENTS_SUPPORTED = 'onmouseenter' in docEl + && 'onmouseleave' in docEl; + + + + var isIELegacyEvent = function(event) { return false; }; + + if (window.attachEvent) { + if (window.addEventListener) { + isIELegacyEvent = function(event) { + return !(event instanceof window.Event); + }; + } else { + isIELegacyEvent = function(event) { return true; }; + } + } + + var _isButton; + + function _isButtonForDOMEvents(event, code) { + return event.which ? (event.which === code + 1) : (event.button === code); + } + + var legacyButtonMap = { 0: 1, 1: 4, 2: 2 }; + function _isButtonForLegacyEvents(event, code) { + return event.button === legacyButtonMap[code]; + } + + function _isButtonForWebKit(event, code) { + switch (code) { + case 0: return event.which == 1 && !event.metaKey; + case 1: return event.which == 2 || (event.which == 1 && event.metaKey); + case 2: return event.which == 3; + default: return false; + } + } + + if (window.attachEvent) { + if (!window.addEventListener) { + _isButton = _isButtonForLegacyEvents; + } else { + _isButton = function(event, code) { + return isIELegacyEvent(event) ? _isButtonForLegacyEvents(event, code) : + _isButtonForDOMEvents(event, code); + } + } + } else if (Prototype.Browser.WebKit) { + _isButton = _isButtonForWebKit; + } else { + _isButton = _isButtonForDOMEvents; + } + + function isLeftClick(event) { return _isButton(event, 0) } + + function isMiddleClick(event) { return _isButton(event, 1) } + + function isRightClick(event) { return _isButton(event, 2) } + + function element(event) { + event = Event.extend(event); + + var node = event.target, type = event.type, + currentTarget = event.currentTarget; + + if (currentTarget && currentTarget.tagName) { + if (type === 'load' || type === 'error' || + (type === 'click' && currentTarget.tagName.toLowerCase() === 'input' + && currentTarget.type === 'radio')) + node = currentTarget; + } + + if (node.nodeType == Node.TEXT_NODE) + node = node.parentNode; + + return Element.extend(node); + } + + function findElement(event, expression) { + var element = Event.element(event); + + if (!expression) return element; + while (element) { + if (Object.isElement(element) && Prototype.Selector.match(element, expression)) { + return Element.extend(element); + } + element = element.parentNode; + } + } + + function pointer(event) { + return { x: pointerX(event), y: pointerY(event) }; + } + + function pointerX(event) { + var docElement = document.documentElement, + body = document.body || { scrollLeft: 0 }; + + return event.pageX || (event.clientX + + (docElement.scrollLeft || body.scrollLeft) - + (docElement.clientLeft || 0)); + } + + function pointerY(event) { + var docElement = document.documentElement, + body = document.body || { scrollTop: 0 }; + + return event.pageY || (event.clientY + + (docElement.scrollTop || body.scrollTop) - + (docElement.clientTop || 0)); + } + + + function stop(event) { + Event.extend(event); + event.preventDefault(); + event.stopPropagation(); + + event.stopped = true; + } + + + Event.Methods = { + isLeftClick: isLeftClick, + isMiddleClick: isMiddleClick, + isRightClick: isRightClick, + + element: element, + findElement: findElement, + + pointer: pointer, + pointerX: pointerX, + pointerY: pointerY, + + stop: stop + }; + + var methods = Object.keys(Event.Methods).inject({ }, function(m, name) { + m[name] = Event.Methods[name].methodize(); + return m; + }); + + if (window.attachEvent) { + function _relatedTarget(event) { + var element; + switch (event.type) { + case 'mouseover': + case 'mouseenter': + element = event.fromElement; + break; + case 'mouseout': + case 'mouseleave': + element = event.toElement; + break; + default: + return null; + } + return Element.extend(element); + } + + var additionalMethods = { + stopPropagation: function() { this.cancelBubble = true }, + preventDefault: function() { this.returnValue = false }, + inspect: function() { return '[object Event]' } + }; + + Event.extend = function(event, element) { + if (!event) return false; + + if (!isIELegacyEvent(event)) return event; + + if (event._extendedByPrototype) return event; + event._extendedByPrototype = Prototype.emptyFunction; + + var pointer = Event.pointer(event); + + Object.extend(event, { + target: event.srcElement || element, + relatedTarget: _relatedTarget(event), + pageX: pointer.x, + pageY: pointer.y + }); + + Object.extend(event, methods); + Object.extend(event, additionalMethods); + + return event; + }; + } else { + Event.extend = Prototype.K; + } + + if (window.addEventListener) { + Event.prototype = window.Event.prototype || document.createEvent('HTMLEvents').__proto__; + Object.extend(Event.prototype, methods); + } + + function _createResponder(element, eventName, handler) { + var registry = Element.retrieve(element, 'prototype_event_registry'); + + if (Object.isUndefined(registry)) { + CACHE.push(element); + registry = Element.retrieve(element, 'prototype_event_registry', $H()); + } + + var respondersForEvent = registry.get(eventName); + if (Object.isUndefined(respondersForEvent)) { + respondersForEvent = []; + registry.set(eventName, respondersForEvent); + } + + if (respondersForEvent.pluck('handler').include(handler)) return false; + + var responder; + if (eventName.include(":")) { + responder = function(event) { + if (Object.isUndefined(event.eventName)) + return false; + + if (event.eventName !== eventName) + return false; + + Event.extend(event, element); + handler.call(element, event); + }; + } else { + if (!MOUSEENTER_MOUSELEAVE_EVENTS_SUPPORTED && + (eventName === "mouseenter" || eventName === "mouseleave")) { + if (eventName === "mouseenter" || eventName === "mouseleave") { + responder = function(event) { + Event.extend(event, element); + + var parent = event.relatedTarget; + while (parent && parent !== element) { + try { parent = parent.parentNode; } + catch(e) { parent = element; } + } + + if (parent === element) return; + + handler.call(element, event); + }; + } + } else { + responder = function(event) { + Event.extend(event, element); + handler.call(element, event); + }; + } + } + + responder.handler = handler; + respondersForEvent.push(responder); + return responder; + } + + function _destroyCache() { + for (var i = 0, length = CACHE.length; i < length; i++) { + Event.stopObserving(CACHE[i]); + CACHE[i] = null; + } + } + + var CACHE = []; + + if (Prototype.Browser.IE) + window.attachEvent('onunload', _destroyCache); + + if (Prototype.Browser.WebKit) + window.addEventListener('unload', Prototype.emptyFunction, false); + + + var _getDOMEventName = Prototype.K, + translations = { mouseenter: "mouseover", mouseleave: "mouseout" }; + + if (!MOUSEENTER_MOUSELEAVE_EVENTS_SUPPORTED) { + _getDOMEventName = function(eventName) { + return (translations[eventName] || eventName); + }; + } + + function observe(element, eventName, handler) { + element = $(element); + + var responder = _createResponder(element, eventName, handler); + + if (!responder) return element; + + if (eventName.include(':')) { + if (element.addEventListener) + element.addEventListener("dataavailable", responder, false); + else { + element.attachEvent("ondataavailable", responder); + element.attachEvent("onlosecapture", responder); + } + } else { + var actualEventName = _getDOMEventName(eventName); + + if (element.addEventListener) + element.addEventListener(actualEventName, responder, false); + else + element.attachEvent("on" + actualEventName, responder); + } + + return element; + } + + function stopObserving(element, eventName, handler) { + element = $(element); + + var registry = Element.retrieve(element, 'prototype_event_registry'); + if (!registry) return element; + + if (!eventName) { + registry.each( function(pair) { + var eventName = pair.key; + stopObserving(element, eventName); + }); + return element; + } + + var responders = registry.get(eventName); + if (!responders) return element; + + if (!handler) { + responders.each(function(r) { + stopObserving(element, eventName, r.handler); + }); + return element; + } + + var i = responders.length, responder; + while (i--) { + if (responders[i].handler === handler) { + responder = responders[i]; + break; + } + } + if (!responder) return element; + + if (eventName.include(':')) { + if (element.removeEventListener) + element.removeEventListener("dataavailable", responder, false); + else { + element.detachEvent("ondataavailable", responder); + element.detachEvent("onlosecapture", responder); + } + } else { + var actualEventName = _getDOMEventName(eventName); + if (element.removeEventListener) + element.removeEventListener(actualEventName, responder, false); + else + element.detachEvent('on' + actualEventName, responder); + } + + registry.set(eventName, responders.without(responder)); + + return element; + } + + function fire(element, eventName, memo, bubble) { + element = $(element); + + if (Object.isUndefined(bubble)) + bubble = true; + + if (element == document && document.createEvent && !element.dispatchEvent) + element = document.documentElement; + + var event; + if (document.createEvent) { + event = document.createEvent('HTMLEvents'); + event.initEvent('dataavailable', bubble, true); + } else { + event = document.createEventObject(); + event.eventType = bubble ? 'ondataavailable' : 'onlosecapture'; + } + + event.eventName = eventName; + event.memo = memo || { }; + + if (document.createEvent) + element.dispatchEvent(event); + else + element.fireEvent(event.eventType, event); + + return Event.extend(event); + } + + Event.Handler = Class.create({ + initialize: function(element, eventName, selector, callback) { + this.element = $(element); + this.eventName = eventName; + this.selector = selector; + this.callback = callback; + this.handler = this.handleEvent.bind(this); + }, + + start: function() { + Event.observe(this.element, this.eventName, this.handler); + return this; + }, + + stop: function() { + Event.stopObserving(this.element, this.eventName, this.handler); + return this; + }, + + handleEvent: function(event) { + var element = Event.findElement(event, this.selector); + if (element) this.callback.call(this.element, event, element); + } + }); + + function on(element, eventName, selector, callback) { + element = $(element); + if (Object.isFunction(selector) && Object.isUndefined(callback)) { + callback = selector, selector = null; + } + + return new Event.Handler(element, eventName, selector, callback).start(); + } + + Object.extend(Event, Event.Methods); + + Object.extend(Event, { + fire: fire, + observe: observe, + stopObserving: stopObserving, + on: on + }); + + Element.addMethods({ + fire: fire, + + observe: observe, + + stopObserving: stopObserving, + + on: on + }); + + Object.extend(document, { + fire: fire.methodize(), + + observe: observe.methodize(), + + stopObserving: stopObserving.methodize(), + + on: on.methodize(), + + loaded: false + }); + + if (window.Event) Object.extend(window.Event, Event); + else window.Event = Event; +})(); + +(function() { + /* Support for the DOMContentLoaded event is based on work by Dan Webb, + Matthias Miller, Dean Edwards, John Resig, and Diego Perini. */ + + var timer; + + function fireContentLoadedEvent() { + if (document.loaded) return; + if (timer) window.clearTimeout(timer); + document.loaded = true; + document.fire('dom:loaded'); + } + + function checkReadyState() { + if (document.readyState === 'complete') { + document.stopObserving('readystatechange', checkReadyState); + fireContentLoadedEvent(); + } + } + + function pollDoScroll() { + try { document.documentElement.doScroll('left'); } + catch(e) { + timer = pollDoScroll.defer(); + return; + } + fireContentLoadedEvent(); + } + + if (document.addEventListener) { + document.addEventListener('DOMContentLoaded', fireContentLoadedEvent, false); + } else { + document.observe('readystatechange', checkReadyState); + if (window == top) + timer = pollDoScroll.defer(); + } + + Event.observe(window, 'load', fireContentLoadedEvent); +})(); + + +Element.addMethods(); +/*------------------------------- DEPRECATED -------------------------------*/ + +Hash.toQueryString = Object.toQueryString; + +var Toggle = { display: Element.toggle }; + +Element.Methods.childOf = Element.Methods.descendantOf; + +var Insertion = { + Before: function(element, content) { + return Element.insert(element, {before:content}); + }, + + Top: function(element, content) { + return Element.insert(element, {top:content}); + }, + + Bottom: function(element, content) { + return Element.insert(element, {bottom:content}); + }, + + After: function(element, content) { + return Element.insert(element, {after:content}); + } +}; + +var $continue = new Error('"throw $continue" is deprecated, use "return" instead'); + +var Position = { + includeScrollOffsets: false, + + prepare: function() { + this.deltaX = window.pageXOffset + || document.documentElement.scrollLeft + || document.body.scrollLeft + || 0; + this.deltaY = window.pageYOffset + || document.documentElement.scrollTop + || document.body.scrollTop + || 0; + }, + + within: function(element, x, y) { + if (this.includeScrollOffsets) + return this.withinIncludingScrolloffsets(element, x, y); + this.xcomp = x; + this.ycomp = y; + this.offset = Element.cumulativeOffset(element); + + return (y >= this.offset[1] && + y < this.offset[1] + element.offsetHeight && + x >= this.offset[0] && + x < this.offset[0] + element.offsetWidth); + }, + + withinIncludingScrolloffsets: function(element, x, y) { + var offsetcache = Element.cumulativeScrollOffset(element); + + this.xcomp = x + offsetcache[0] - this.deltaX; + this.ycomp = y + offsetcache[1] - this.deltaY; + this.offset = Element.cumulativeOffset(element); + + return (this.ycomp >= this.offset[1] && + this.ycomp < this.offset[1] + element.offsetHeight && + this.xcomp >= this.offset[0] && + this.xcomp < this.offset[0] + element.offsetWidth); + }, + + overlap: function(mode, element) { + if (!mode) return 0; + if (mode == 'vertical') + return ((this.offset[1] + element.offsetHeight) - this.ycomp) / + element.offsetHeight; + if (mode == 'horizontal') + return ((this.offset[0] + element.offsetWidth) - this.xcomp) / + element.offsetWidth; + }, + + + cumulativeOffset: Element.Methods.cumulativeOffset, + + positionedOffset: Element.Methods.positionedOffset, + + absolutize: function(element) { + Position.prepare(); + return Element.absolutize(element); + }, + + relativize: function(element) { + Position.prepare(); + return Element.relativize(element); + }, + + realOffset: Element.Methods.cumulativeScrollOffset, + + offsetParent: Element.Methods.getOffsetParent, + + page: Element.Methods.viewportOffset, + + clone: function(source, target, options) { + options = options || { }; + return Element.clonePosition(target, source, options); + } +}; + +/*--------------------------------------------------------------------------*/ + +if (!document.getElementsByClassName) document.getElementsByClassName = function(instanceMethods){ + function iter(name) { + return name.blank() ? null : "[contains(concat(' ', @class, ' '), ' " + name + " ')]"; + } + + instanceMethods.getElementsByClassName = Prototype.BrowserFeatures.XPath ? + function(element, className) { + className = className.toString().strip(); + var cond = /\s/.test(className) ? $w(className).map(iter).join('') : iter(className); + return cond ? document._getElementsByXPath('.//*' + cond, element) : []; + } : function(element, className) { + className = className.toString().strip(); + var elements = [], classNames = (/\s/.test(className) ? $w(className) : null); + if (!classNames && !className) return elements; + + var nodes = $(element).getElementsByTagName('*'); + className = ' ' + className + ' '; + + for (var i = 0, child, cn; child = nodes[i]; i++) { + if (child.className && (cn = ' ' + child.className + ' ') && (cn.include(className) || + (classNames && classNames.all(function(name) { + return !name.toString().blank() && cn.include(' ' + name + ' '); + })))) + elements.push(Element.extend(child)); + } + return elements; + }; + + return function(className, parentElement) { + return $(parentElement || document.body).getElementsByClassName(className); + }; +}(Element.Methods); + +/*--------------------------------------------------------------------------*/ + +Element.ClassNames = Class.create(); +Element.ClassNames.prototype = { + initialize: function(element) { + this.element = $(element); + }, + + _each: function(iterator) { + this.element.className.split(/\s+/).select(function(name) { + return name.length > 0; + })._each(iterator); + }, + + set: function(className) { + this.element.className = className; + }, + + add: function(classNameToAdd) { + if (this.include(classNameToAdd)) return; + this.set($A(this).concat(classNameToAdd).join(' ')); + }, + + remove: function(classNameToRemove) { + if (!this.include(classNameToRemove)) return; + this.set($A(this).without(classNameToRemove).join(' ')); + }, + + toString: function() { + return $A(this).join(' '); + } +}; + +Object.extend(Element.ClassNames.prototype, Enumerable); + +/*--------------------------------------------------------------------------*/ + +(function() { + window.Selector = Class.create({ + initialize: function(expression) { + this.expression = expression.strip(); + }, + + findElements: function(rootElement) { + return Prototype.Selector.select(this.expression, rootElement); + }, + + match: function(element) { + return Prototype.Selector.match(element, this.expression); + }, + + toString: function() { + return this.expression; + }, + + inspect: function() { + return "#"; + } + }); + + Object.extend(Selector, { + matchElements: function(elements, expression) { + var match = Prototype.Selector.match, + results = []; + + for (var i = 0, length = elements.length; i < length; i++) { + var element = elements[i]; + if (match(element, expression)) { + results.push(Element.extend(element)); + } + } + return results; + }, + + findElement: function(elements, expression, index) { + index = index || 0; + var matchIndex = 0, element; + for (var i = 0, length = elements.length; i < length; i++) { + element = elements[i]; + if (Prototype.Selector.match(element, expression) && index === matchIndex++) { + return Element.extend(element); + } + } + }, + + findChildElements: function(element, expressions) { + var selector = expressions.toArray().join(', '); + return Prototype.Selector.select(selector, element || document); + } + }); +})(); diff --git a/tests/javascript/small.js b/tests/javascript/small.js new file mode 100644 index 0000000..b0f8a3c --- /dev/null +++ b/tests/javascript/small.js @@ -0,0 +1,9 @@ +var app = { + foo: function(){}, + bar: function(){} +} + +var dsads = { + fdfsd: function(){}, + dsadas: function(){} +} diff --git a/tests/jptest 天使のたまご水に棲む/test.cpp b/tests/jptest 天使のたまご水に棲む/test.cpp new file mode 100644 index 0000000..45103f9 --- /dev/null +++ b/tests/jptest 天使のたまご水に棲む/test.cpp @@ -0,0 +1,597 @@ +/* Test file for C++ language. + * Attempt to include as many aspects of the C++ language as possible. + * Do not include things tested in test.c since that shares the + * same language. + * + * $Id: test.cpp,v 1.22 2008/05/17 20:12:55 zappo Exp $ + * + */ + +/* An include test */ +#include + +#include + +#include "c++-test.hh" + +#include + +double var1 = 1.2; + +int simple1(int a) { + +} + +struct foo1 { + int test; +}; + +struct foo2 : public foo1 { + const int foo21(int a, int b); + const int foo22(int a, int b) { return 1 } +}; + +/* Classes */ +class class1 { +private: + int var11; + struct foo1 var12; +public: + int p_var11; + struct foo p_var12; +}; + +class i_class1 : public class1 { +private: + int var11; + struct foo var12; +public: + int p_var11; + struct foo p_var12; +}; + +class class2 { +private: + int var21; + struct foo var22; +public: + int p_var21; + struct foo p_var22; +}; + +class i_class2 : public class1, public class2 { +private: + int var21; + struct foo var22; +protected: + int pt_var21; +public: + int p_var21; + struct foo p_var22; +}; + +class class3 { + /* A class with strange things in it */ +public: + class3(); /* A constructor */ + enum embedded_foo_enum { + a, b, c + } embed1; + struct embedded_bar_struct { + int a; + int b; + } embed2; + class embedded_baz_class { + embedded_baz_class(); + ~embedded_baz_class(); + } embed3; + ~class3(); /* destructor */ + + /* Methods */ + int method_for_class3(int a, char b); + + int inline_method(int c) { return c; } + + /* Operators */ + class3& operator^= (const class3& something); + + /* Funny declmods */ + const class3 * const method_const_ptr_ptr(const int * const argconst) const = 0; +}; + +class3::class3() +{ + /* Constructor outside the definition. */ +} + +int class3::method_for_class3(int a, char b) +{ +} + +int class3::method1_for_class3( int a, int &b) +{ + int cvariablename; + class3 fooy[]; + class3 moose = new class3; + + // Complktion testing line should find external members. + a = fooy[1].me ; + b = cv ; + + if (fooy.emb) { + simple1(c); + } + + cos(10); + abs(10); + + return 1; +} + +char class3::method2_for_class3( int a, int b) throw ( exception1 ) +{ + return 'a'; +} + +void *class3::method3_for_class3( int a, int b) throw ( exception1, exception2 ) +{ + int q = a; + return "Moose"; +} + +void *class3::method31_for_class3( int a, int b) throw ( ) +{ + int q = a; + return "Moose"; +} + +void *class3::method4_for_class3( int a, int b) reentrant +{ + class3 ct; + + ct.method5_for_class3(1,a); + + pritf(); +} + +/* + * A method on class3. + */ +void *class3::method5_for_class3( int a, int b) const +{ +} + +/* + * Namespace parsing tests + */ +namespace NS { + class class_in_namespace { + int equiv(const NS::class_in_namespace *) const; + }; +} + +int NS::class_in_namespace::equiv(const NS::class_in_namespace *cin) const +{ + return 0; +} + +// Stuff Klaus found. +// Inheritance w/out a specifying for public. +class class4 : class1 { + // Pure virtual methods. + void virtual print () const = 0; + +public: + // The whacky constructor type + class4() + try : class1(args) + { + // constructor body + } + catch () + { + + } + + +}; + +class class5 : public virtual class4 { + // Virtual inheritance +}; + +class class6 : class1 { + // Mutable + mutable int i; +}; + +/* Namespaces */ +namespace namespace1 { + void ns_method1() { } + + class n_class1 { + public: + void method11(int a) { } + }; + + /* This shouldn't parse due to missing semicolon. */ + class _n_class2 : public n_class1 { + void n_c2_method1(int a, int b) { } + }; + + // Macros in the namespace +#define NSMACRO 1 + + // Template in the namespace + template T nsti1(const Foo& foo); + template<> int nsti1(const Foo& foo); + +} + +namespace namespace2 { + + using namespace1::n_class1; + +} + +/* Initializers */ +void tinitializers1(): inita1(False), + inita2(False) +{ + inita1= 1; +} + +/* How about Extern C type things. */ +int funny_prototype(int ,int b,float c) +{ + +} + +extern "C" +int extern_c_1(int a, int b) +{ + + funny_prototype(1,2,3.4); + + printf("Moose", ); + + return 1; +} + +extern "C" { + + int extern_c_2(int a, int b) + { + return 1; + } + +} + +// Some operator stuff +class Action +{ + // Problems!! operator() and operator[] can not be parsed with semantic + // 1.4.2 but with latest c.by + virtual void operator()(int i, char *p ) = 0; + virtual String& operator[]() = 0; + virtual void operator!() = 0; + virtual void operator->() = 0; + virtual T& operator+=(); + virtual T& operator*(); + virtual T& operator*=(); +}; + +// class with namespace qualified parents +class Multiinherit : public virtual POA::Parent, + public virtual POA::Parent1, + Parent +{ +private: + int i; + +public: + Multiinherit(); + ~Multiinherit(); + + // method with a list of qualified exceptions + void* throwtest() + throw(Exception0, + Testnamespace::Exception1, + Testnamespace::Excpetion2, + Testnamespace::testnamespace1::Exception3); + +}; + +void* +Multiinherit::throwtest() + throw (Exception0, + Testnamespace::Exception1, + Testnamespace::Excpetion2, + Testnamespace::testnamespace1::Exception3) +{ + return; +} + +// Jens Rock : Nested classes or structs defined +// outside of the containing class/struct. +class container +{ + public: + struct contained; + container(); + ~container(); +}; + +struct container::contained +{ + public: + contained(); + ~contained(); +}; + +/* + * Ok, how about some template stuff. + */ +template > +const CT& max (const CT& a, const CT& b) +{ + return a < b ? b : a; +} + +// Arne Schmitz found this one +std::vector &a, &b, &c; + +class TemplateUsingClass +{ + typedef TestClassMap::iterator iterator; + typedef map TestClassMap; + + // typedefs with const and volatile + typedef const map const_TestClassMap; + typedef TestClassMap::iterator volatile volatile_iterator; + + map mapclassvarthingy; +}; + +template T ti1(const Foo& foo); +template<> int ti1(const Foo& foo); + + +// ----------------------------------- +// Now some namespace and related stuff +// ----------------------------------- + +using CORBA::LEX::get_token; +using Namespace1; + +using namespace POA::std; +using namespace Test; + + + +namespace Parser +{ + namespace + { + using Lexer::get_test; + string str = ""; + } + + namespace XXX + { + + class Foobar : public virtual POA::Parent, + public virtual POA::Parent1, + private POA::list, + private map + { + int i; + list >::const_iterator l; + public: + + Foobar(); + ~Foobar(); + }; + } + + + void test_function(int i); + +}; + +// unnamed namespaces - even nested +namespace +{ + namespace + { + using Lexer::get_test; + string str = ""; + class FooClass + { + FooClass(); + }; + } + + // some builtin types + long long ll = 0; + long double d = 0.0; + unsigned test; + unsigned long int **uli = 0; + signed si = 0; + signed short ss = 0; + short int i = 0; + long int li = 0; + + // expressions with namespace/class-qualifyiers + ORB_var cGlobalOrb = ORB::_nil(); + ORB_var1 cGlobalOrb1 = ORB::_test; + + class Testclass + { + #define TEST 0 + ini i; + + public: + + Testclass(); + ~Testclass(); + }; + + static void test_function(unsigned int i); + +}; + + +// outside method implementations which should be grouped to type Test +XXX& +Test::waiting() +{ + return; +} + +void +Test::print() +{ + return; +} + +// outside method implementations with namespaces which should be grouped to +// their complete (incl. namespace) types +void* +Parser::XXX::Foobar::wait(int i, const char const * const * p) +{ + return; +} + +void* +Namespace1::Test::wait1(int i) +{ + return; +} + +int +Namespace1::Test::waiting(int i) +{ + return; +} + +// a class with some outside implementations which should all be grouped to +// this class declaration +class ClassWithExternals +{ +private: + int i; + +public: + ClassWithExternals(); + ~ClassWithExternals(); + void non_nil(); +}; + + +// Foobar is not displayed; seems that semantic tries to add this to the class +// Foobar but can not find/display it, because contained in the namespace above. +void +Foobar::non_nil() +{ + return; +} + +// are correctly grouped to the ClassWithExternals class +void +ClassWithExternals::non_nil() +{ + String s = "ldfjg dlfgkdlfkgjdl"; + return; +} + +ClassWithExternals::ClassWithExternals() +{ + return; +} + +void +ClassWithExternals::~ClassWithExternals() +{ + return; +} + + +// ------------------------------- +// Now some macro and define stuff +// ------------------------------- + +#define TEST 0 +#define TEST1 "String" + +// The first backslash makes this macro unmatched syntax with semantic 1.4.2! +// With flexing \+newline as nothing all is working fine! +#define MZK_ENTER(METHOD) \ +{ \ + CzkMethodLog lMethodLog(METHOD,"Framework");\ +} + +#define ZK_ASSERTM(METHOD,ASSERTION,MESSAGE) \ + { if(!(ASSERTION))\ + {\ + std::ostringstream lMesgStream; \ + lMesgStream << "Assertion failed: " \ + << MESSAGE; \ + CzkLogManager::doLog(CzkLogManager::FATAL,"",METHOD, \ + "Assert",lMesgStream); \ + assert(ASSERTION);\ + }\ + } + +// Test if not newline-backslashes are handled correctly +string s = "My \"quoted\" string"; + +// parsed fine as macro +#define FOO (arg) method(arg, "foo"); + +// With semantic 1.4.2 this parsed as macro BAR *and* function method. +// With latest c.bnf at least one-liner macros can be parsed correctly. +#define BAR (arg) CzkMessageLog method(arg, "bar"); + +// some const and volatile stuff +char * p1 = "Hello"; // 1. variable Pointer, variable Data +const char * p2 = "Hello"; // 2. variable pointer, constant data +char * const p3 = "Hello"; // 3. constant pointer, variable data +const char * const p4 = "Hello"; // 4. constant pointer, constant data + +// Case 2 and 4 can exchange first "const" and "char" +char const * p21 = "Hello"; // variable pointer, constant data +char const * const p41 = "Hello"; // constant pointer, constant data + +char volatile a = 0; // a volatile char +void foo(bar const &arg); // a reference to a const bar +int foobar(bar const * const p); // a const pointer to a const bar +int foobar(bar const volatile * const p); // a const pointer to a const bar +int foobar3(char* p); // a const pointer to a const bar + +// Should not be parsed because this is invalid code +int const & const r3 = i; + +boolean i = 0; +boolean & r1 = i; +boolean const & r2 = i; + +// const * sequences can be very long in C++ ;-) +char const * const * const * const * ppp; + +// complex function declarationen with named pointer-arguments +const char** foobar1(volatile char const * const **p); +const char** foobar11(volatile Test::Namespace::Char const * const **p); + +// complex function declarationen with unnamed pointer-arguments +const char* foobar2(const char***); +const char* foobar21(const Test::Namespace::Char***); + +// string literal parsing even with wchar_t +char const *p = "string1"; +char const *q = "string1" "str\"ing2" "string3"; +wchar_t testc = L'a'; + +wchar_t const *wp = L"string with a \" in it"; +wchar_t const *wq = L"string \n\t\"test" L"string2"; +wchar_t const *wr = L"string L"; diff --git a/tests/mxml/main.mxml b/tests/mxml/main.mxml new file mode 100644 index 0000000..9f3defb --- /dev/null +++ b/tests/mxml/main.mxml @@ -0,0 +1,65 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/ocaml/fifteen.ml b/tests/ocaml/fifteen.ml new file mode 100644 index 0000000..8160726 --- /dev/null +++ b/tests/ocaml/fifteen.ml @@ -0,0 +1,98 @@ +(* $Id: fifteen.ml,v 1.8 2001/09/06 08:47:55 garrigue Exp $ *) + +open StdLabels +open Gaux +open Gtk +open GObj +open GMain + +class position ~init_x ~init_y ~min_x ~min_y ~max_x ~max_y = object + val mutable x = init_x + val mutable y = init_y + method current = (x, y) + method up () = if y > min_y then y <- y-1 else (); (x, y) + method down () = if y < max_y then y <- y+1 else (); (x, y) + method left () = if x > min_x then x <- x-1 else (); (x, y) + method right () = if x < max_x then x <- x+1 else (); (x, y) +end + +let game_init () = (* generate initial puzzle state *) + let rec game_aux acc rest n_invert = + let len = List.length rest in + if len=0 then + if n_invert mod 2 = 0 then + acc (* to be solvable, n_invert must be even *) + else + (List.hd (List.tl acc))::(List.hd acc)::(List.tl (List.tl acc)) + else begin + let rec extract n xs = + if (n=0) then (List.hd xs, List.tl xs) + else + let (ans, ys) = extract (n-1) (List.tl xs) in + (ans, List.hd xs :: ys) in + let ran = Random.int len in + let (elm, rest1) = extract ran rest in + let rec count p xs = match xs with + [] -> 0 + | y :: ys -> let acc = count p ys in + if p y then 1+acc else acc + in + let new_n_invert = count (fun x -> elm > x) acc in + game_aux (elm :: acc) rest1 (n_invert+new_n_invert) + end in + let rec from n = if n=0 then [] else n :: from (n-1) in + game_aux [] (from 15) 0 + +let _ = Random.init (int_of_float (Sys.time () *. 1000.)) +let window = GWindow.window () +let _ = window#connect#destroy ~callback:GMain.Main.quit + +let tbl = GPack.table ~rows:4 ~columns:4 ~homogeneous:true ~packing:window#add () +let dummy = GMisc.label ~text:"" ~packing:(tbl#attach ~left:3 ~top:3) () +let arr = Array.create_matrix ~dimx:4 ~dimy:4 dummy +let init = game_init () +let _ = + for i = 0 to 15 do + let j = i mod 4 in + let k = i/4 in + let frame = + GBin.frame ~shadow_type:`OUT ~width:32 ~height:32 + ~packing:(tbl#attach ~left:j ~top:k) () in + if i < 15 then + arr.(j).(k) <- + GMisc.label ~text:(string_of_int (List.nth init i)) + ~packing:frame#add () + done +let pos = new position ~init_x:3 ~init_y:3 ~min_x:0 ~min_y:0 ~max_x:3 ~max_y:3 + +open GdkKeysyms + +let _ = + window#event#connect#key_press ~callback: + begin fun ev -> + let (x0, y0) = pos#current in + let wid0 = arr.(x0).(y0) in + let key = GdkEvent.Key.keyval ev in + if key = _q || key = _Escape then (Main.quit (); exit 0) else + let (x1, y1) = + if key = _h || key = _Left then + pos#right () + else if key = _j || key = _Down then + pos#up () + else if key = _k || key = _Up then + pos#down () + else if key = _l || key = _Right then + pos#left () + else (x0, y0) + in + let wid1 = arr.(x1).(y1) in + wid0#set_text (wid1#text); + wid1#set_text ""; + true + end + +let main () = + window#show (); + Main.main () + +let _ = main () diff --git a/tests/ocaml/heap.ml b/tests/ocaml/heap.ml new file mode 100644 index 0000000..50a9855 --- /dev/null +++ b/tests/ocaml/heap.ml @@ -0,0 +1,153 @@ +(************************************************************************) +(* v * The Coq Proof Assistant / The Coq Development Team *) +(* t -> int +end + +module type S =sig + + (* Type of functional heaps *) + type t + + (* Type of elements *) + type elt + + (* The empty heap *) + val empty : t + + (* [add x h] returns a new heap containing the elements of [h], plus [x]; + complexity $O(log(n))$ *) + val add : elt -> t -> t + + (* [maximum h] returns the maximum element of [h]; raises [EmptyHeap] + when [h] is empty; complexity $O(1)$ *) + val maximum : t -> elt + + (* [remove h] returns a new heap containing the elements of [h], except + the maximum of [h]; raises [EmptyHeap] when [h] is empty; + complexity $O(log(n))$ *) + val remove : t -> t + + (* usual iterators and combinators; elements are presented in + arbitrary order *) + val iter : (elt -> unit) -> t -> unit + + val fold : (elt -> 'a -> 'a) -> t -> 'a -> 'a + +end + +exception EmptyHeap + +(*s Functional implementation *) + +module Functional(X : Ordered) = struct + + (* Heaps are encoded as complete binary trees, i.e., binary trees + which are full expect, may be, on the bottom level where it is filled + from the left. + These trees also enjoy the heap property, namely the value of any node + is greater or equal than those of its left and right subtrees. + + There are 4 kinds of complete binary trees, denoted by 4 constructors: + [FFF] for a full binary tree (and thus 2 full subtrees); + [PPF] for a partial tree with a partial left subtree and a full + right subtree; + [PFF] for a partial tree with a full left subtree and a full right subtree + (but of different heights); + and [PFP] for a partial tree with a full left subtree and a partial + right subtree. *) + + type t = + | Empty + | FFF of t * X.t * t (* full (full, full) *) + | PPF of t * X.t * t (* partial (partial, full) *) + | PFF of t * X.t * t (* partial (full, full) *) + | PFP of t * X.t * t (* partial (full, partial) *) + + type elt = X.t + + let empty = Empty + + (* smart constructors for insertion *) + let p_f l x r = match l with + | Empty | FFF _ -> PFF (l, x, r) + | _ -> PPF (l, x, r) + + let pf_ l x = function + | Empty | FFF _ as r -> FFF (l, x, r) + | r -> PFP (l, x, r) + + let rec add x = function + | Empty -> + FFF (Empty, x, Empty) + (* insertion to the left *) + | FFF (l, y, r) | PPF (l, y, r) -> + if X.compare x y > 0 then p_f (add y l) x r else p_f (add x l) y r + (* insertion to the right *) + | PFF (l, y, r) | PFP (l, y, r) -> + if X.compare x y > 0 then pf_ l x (add y r) else pf_ l y (add x r) + + let maximum = function + | Empty -> raise EmptyHeap + | FFF (_, x, _) | PPF (_, x, _) | PFF (_, x, _) | PFP (_, x, _) -> x + + (* smart constructors for removal; note that they are different + from the ones for insertion! *) + let p_f l x r = match l with + | Empty | FFF _ -> FFF (l, x, r) + | _ -> PPF (l, x, r) + + let pf_ l x = function + | Empty | FFF _ as r -> PFF (l, x, r) + | r -> PFP (l, x, r) + + let rec remove = function + | Empty -> + raise EmptyHeap + | FFF (Empty, _, Empty) -> + Empty + | PFF (l, _, Empty) -> + l + (* remove on the left *) + | PPF (l, x, r) | PFF (l, x, r) -> + let xl = maximum l in + let xr = maximum r in + let l' = remove l in + if X.compare xl xr >= 0 then + p_f l' xl r + else + p_f l' xr (add xl (remove r)) + (* remove on the right *) + | FFF (l, x, r) | PFP (l, x, r) -> + let xl = maximum l in + let xr = maximum r in + let r' = remove r in + if X.compare xl xr > 0 then + pf_ (add xr (remove l)) xl r' + else + pf_ l xr r' + + let rec iter f = function + | Empty -> + () + | FFF (l, x, r) | PPF (l, x, r) | PFF (l, x, r) | PFP (l, x, r) -> + iter f l; f x; iter f r + + let rec fold f h x0 = match h with + | Empty -> + x0 + | FFF (l, x, r) | PPF (l, x, r) | PFF (l, x, r) | PFP (l, x, r) -> + fold f l (fold f r (f x x0)) + +end diff --git a/tests/ocaml/kb.ml b/tests/ocaml/kb.ml new file mode 100644 index 0000000..093d918 --- /dev/null +++ b/tests/ocaml/kb.ml @@ -0,0 +1,294 @@ +(* ========================================================================= *) +(* Knuth-Bendix completion done by HOL inference. John Harrison 2005 *) +(* *) +(* This was written by fairly mechanical modification of the code at *) +(* *) +(* http://www.cl.cam.ac.uk/users/jrh/atp/order.ml *) +(* http://www.cl.cam.ac.uk/users/jrh/atp/completion.ml *) +(* *) +(* for HOL's slightly different term structure, with ad hoc term *) +(* manipulations replaced by inference on equational theorems. We also have *) +(* the optimization of throwing left-reducible rules back into the set of *) +(* critical pairs. However, we don't prioritize smaller critical pairs or *) +(* anything like that; this is still a very naive implementation. *) +(* *) +(* For something very similar done 15 years ago, see Konrad Slind's Master's *) +(* thesis: "An Implementation of Higher Order Logic", U Calgary 1991. *) +(* ========================================================================= *) + +let is_realvar w x = is_var x & not(mem x w);; + +let rec real_strip w tm = + if mem tm w then tm,[] else + let l,r = dest_comb tm in + let f,args = real_strip w l in f,args@[r];; + +(* ------------------------------------------------------------------------- *) +(* Construct a weighting function. *) +(* ------------------------------------------------------------------------- *) + +let weight lis (f,n) (g,m) = + let i = index f lis and j = index g lis in + i > j or i = j & n > m;; + +(* ------------------------------------------------------------------------- *) +(* Generic lexicographic ordering function. *) +(* ------------------------------------------------------------------------- *) + +let rec lexord ord l1 l2 = + match (l1,l2) with + (h1::t1,h2::t2) -> if ord h1 h2 then length t1 = length t2 + else h1 = h2 & lexord ord t1 t2 + | _ -> false;; + +(* ------------------------------------------------------------------------- *) +(* Lexicographic path ordering. Note that we also use the weights *) +(* to define the set of constants, so they don't literally have to be *) +(* constants in the HOL sense. *) +(* ------------------------------------------------------------------------- *) + +let rec lpo_gt w s t = + if is_realvar w t then not(s = t) & mem t (frees s) + else if is_realvar w s or is_abs s or is_abs t then false else + let f,fargs = real_strip w s and g,gargs = real_strip w t in + exists (fun si -> lpo_ge w si t) fargs or + forall (lpo_gt w s) gargs & + (f = g & lexord (lpo_gt w) fargs gargs or + weight w (f,length fargs) (g,length gargs)) +and lpo_ge w s t = (s = t) or lpo_gt w s t;; + +(* ------------------------------------------------------------------------- *) +(* Unification. Again we have the weights "w" fixing the set of constants. *) +(* ------------------------------------------------------------------------- *) + +let rec istriv w env x t = + if is_realvar w t then t = x or defined env t & istriv w env x (apply env t) + else if is_const t then false else + let f,args = strip_comb t in + exists (istriv w env x) args & failwith "cyclic";; + +let rec unify w env tp = + match tp with + ((Var(_,_) as x),t) | (t,(Var(_,_) as x)) when not(mem x w) -> + if defined env x then unify w env (apply env x,t) + else if istriv w env x t then env else (x|->t) env + | (Comb(f,x),Comb(g,y)) -> unify w (unify w env (x,y)) (f,g) + | (s,t) -> if s = t then env else failwith "unify: not unifiable";; + +(* ------------------------------------------------------------------------- *) +(* Full unification, unravelling graph into HOL-style instantiation list. *) +(* ------------------------------------------------------------------------- *) + +let fullunify w (s,t) = + let env = unify w undefined (s,t) in + let th = map (fun (x,t) -> (t,x)) (graph env) in + let rec subs t = + let t' = vsubst th t in + if t' = t then t else subs t' in + map (fun (t,x) -> (subs t,x)) th;; + +(* ------------------------------------------------------------------------- *) +(* Construct "overlaps": ways of rewriting subterms using unification. *) +(* ------------------------------------------------------------------------- *) + +let LIST_MK_COMB f ths = rev_itlist (fun s t -> MK_COMB(t,s)) ths (REFL f);; + +let rec listcases fn rfn lis acc = + match lis with + [] -> acc + | h::t -> fn h (fun i h' -> rfn i (h'::map REFL t)) @ + listcases fn (fun i t' -> rfn i (REFL h::t')) t acc;; + +let rec overlaps w th tm rfn = + let l,r = dest_eq(concl th) in + if not (is_comb tm) then [] else + let f,args = strip_comb tm in + listcases (overlaps w th) (fun i a -> rfn i (LIST_MK_COMB f a)) args + (try [rfn (fullunify w (l,tm)) th] with Failure _ -> []);; + +(* ------------------------------------------------------------------------- *) +(* Rename variables canonically to avoid clashes or remove redundancy. *) +(* ------------------------------------------------------------------------- *) + +let fixvariables s th = + let fvs = subtract (frees(concl th)) (freesl(hyp th)) in + let gvs = map2 (fun v n -> mk_var(s^string_of_int n,type_of v)) + fvs (1--(length fvs)) in + INST (zip gvs fvs) th;; + +let renamepair (th1,th2) = fixvariables "x" th1,fixvariables "y" th2;; + +(* ------------------------------------------------------------------------- *) +(* Find all critical pairs. *) +(* ------------------------------------------------------------------------- *) + +let crit1 w eq1 eq2 = + let l1,r1 = dest_eq(concl eq1) + and l2,r2 = dest_eq(concl eq2) in + overlaps w eq1 l2 (fun i th -> TRANS (SYM(INST i th)) (INST i eq2));; + +let thm_union l1 l2 = + itlist (fun th ths -> let th' = fixvariables "x" th in + let tm = concl th' in + if exists (fun th'' -> concl th'' = tm) ths then ths + else th'::ths) + l1 l2;; + +let critical_pairs w tha thb = + let th1,th2 = renamepair (tha,thb) in + if concl th1 = concl th2 then crit1 w th1 th2 else + filter (fun th -> let l,r = dest_eq(concl th) in l <> r) + (thm_union (crit1 w th1 th2) (thm_union (crit1 w th2 th1) []));; + +(* ------------------------------------------------------------------------- *) +(* Normalize an equation and try to orient it. *) +(* ------------------------------------------------------------------------- *) + +let normalize_and_orient w eqs th = + let th' = GEN_REWRITE_RULE TOP_DEPTH_CONV eqs th in + let s',t' = dest_eq(concl th') in + if lpo_ge w s' t' then th' else if lpo_ge w t' s' then SYM th' + else failwith "Can't orient equation";; + +(* ------------------------------------------------------------------------- *) +(* Print out status report to reduce user boredom. *) +(* ------------------------------------------------------------------------- *) + +let status(eqs,crs) eqs0 = + if eqs = eqs0 & (length crs) mod 1000 <> 0 then () else + (print_string(string_of_int(length eqs)^" equations and "^ + string_of_int(length crs)^" pending critical pairs"); + print_newline());; + +(* ------------------------------------------------------------------------- *) +(* Basic completion, throwing back left-reducible rules. *) +(* ------------------------------------------------------------------------- *) + +let left_reducible eqs eq = + can (CHANGED_CONV(GEN_REWRITE_CONV (LAND_CONV o ONCE_DEPTH_CONV) eqs)) + (concl eq);; + +let rec complete w (eqs,crits) = + match crits with + (eq::ocrits) -> + let trip = + try let eq' = normalize_and_orient w eqs eq in + let s',t' = dest_eq(concl eq') in + if s' = t' then (eqs,ocrits) else + let crits',eqs' = partition(left_reducible [eq']) eqs in + let eqs'' = eq'::eqs' in + eqs'', + ocrits @ crits' @ itlist ((@) o critical_pairs w eq') eqs'' [] + with Failure _ -> + if exists (can (normalize_and_orient w eqs)) ocrits + then (eqs,ocrits@[eq]) + else failwith "complete: no orientable equations" in + status trip eqs; complete w trip + | [] -> eqs;; + +(* ------------------------------------------------------------------------- *) +(* Overall completion. *) +(* ------------------------------------------------------------------------- *) + +let complete_equations wts eqs = + let eqs' = map (normalize_and_orient wts []) eqs in + complete wts ([],eqs');; + +(* ------------------------------------------------------------------------- *) +(* Knuth-Bendix example 4: the inverse property. *) +(* ------------------------------------------------------------------------- *) + +complete_equations [`1`; `(*):num->num->num`; `i:num->num`] + [SPEC_ALL(ASSUME `!a b. i(a) * a * b = b`)];; + +(* ------------------------------------------------------------------------- *) +(* Knuth-Bendix example 6: central groupoids. *) +(* ------------------------------------------------------------------------- *) + +complete_equations [`(*):num->num->num`] + [SPEC_ALL(ASSUME `!a b c. (a * b) * (b * c) = b`)];; + +(* ------------------------------------------------------------------------- *) +(* Knuth-Bendix example 9: cancellation law. *) +(* ------------------------------------------------------------------------- *) + +complete_equations + [`1`; `( * ):num->num->num`; `(+):num->num->num`; `(-):num->num->num`] + (map SPEC_ALL (CONJUNCTS (ASSUME + `(!a b:num. a - a * b = b) /\ + (!a b:num. a * b - b = a) /\ + (!a. a * 1 = a) /\ + (!a. 1 * a = a)`)));; + +(* ------------------------------------------------------------------------- *) +(* Another example: pure congruence closure (no variables). *) +(* ------------------------------------------------------------------------- *) + +complete_equations [`c:A`; `f:A->A`] + (map SPEC_ALL (CONJUNCTS (ASSUME + `((f(f(f(f(f c))))) = c:A) /\ (f(f(f c)) = c)`)));; + +(* ------------------------------------------------------------------------- *) +(* Knuth-Bendix example 1: group theory. *) +(* ------------------------------------------------------------------------- *) + +let eqs = map SPEC_ALL (CONJUNCTS (ASSUME + `(!x. 1 * x = x) /\ (!x. i(x) * x = 1) /\ + (!x y z. (x * y) * z = x * y * z)`));; + +complete_equations [`1`; `(*):num->num->num`; `i:num->num`] eqs;; + +(* ------------------------------------------------------------------------- *) +(* Near-rings (from Aichinger's Diplomarbeit). *) +(* ------------------------------------------------------------------------- *) + +let eqs = map SPEC_ALL (CONJUNCTS (ASSUME + `(!x. 0 + x = x) /\ + (!x. neg x + x = 0) /\ + (!x y z. (x + y) + z = x + y + z) /\ + (!x y z. (x * y) * z = x * y * z) /\ + (!x y z. (x + y) * z = (x * z) + (y * z))`));; + +let nreqs = +complete_equations + [`0`; `(+):num->num->num`; `neg:num->num`; `( * ):num->num->num`] eqs;; + +(*** This weighting also works OK, though the system is a bit bigger + +let nreqs = +complete_equations + [`0`; `(+):num->num->num`; `( * ):num->num->num`; `INV`] eqs;; + +****) + +(* ------------------------------------------------------------------------- *) +(* A "completion" tactic. *) +(* ------------------------------------------------------------------------- *) + +let COMPLETE_TAC w th = + let eqs = map SPEC_ALL (CONJUNCTS(SPEC_ALL th)) in + let eqs' = complete_equations w eqs in + MAP_EVERY (ASSUME_TAC o GEN_ALL) eqs';; + +(* ------------------------------------------------------------------------- *) +(* Solve example problems in gr *) + +g `(!x. 1 * x = x) /\ + (!x. i(x) * x = 1) /\ + (!x y z. (x * y) * z = x * y * z) + ==> !x y. i(y) * i(i(i(x * i(y)))) * x = 1`;; + +e (DISCH_THEN(COMPLETE_TAC [`1`; `(*):num->num->num`; `i:num->num`]));; +e (ASM_REWRITE_TAC[]);; + +g `(!x. 0 + x = x) /\ + (!x. neg x + x = 0) /\ + (!x y z. (x + y) + z = x + y + z) /\ + (!x y z. (x * y) * z = x * y * z) /\ + (!x y z. (x + y) * z = (x * z) + (y * z)) + ==> (neg 0 * (x * y + z + neg(neg(w + z))) + neg(neg b + neg a) = + a + b)`;; + +e (DISCH_THEN(COMPLETE_TAC + [`0`; `(+):num->num->num`; `neg:num->num`; `( * ):num->num->num`]));; +e (ASM_REWRITE_TAC[]);; diff --git a/tests/ocaml/xmlParser.mli b/tests/ocaml/xmlParser.mli new file mode 100644 index 0000000..941bce3 --- /dev/null +++ b/tests/ocaml/xmlParser.mli @@ -0,0 +1,82 @@ +(* + * Xml Light, an small Xml parser/printer with DTD support. + * Copyright (C) 2003 Nicolas Cannasse (ncannasse@motion-twin.com) + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library has the special exception on linking described in file + * README. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA + *) + +(** Xml Light Parser + + While basic parsing functions can be used in the {!Xml} module, this module + is providing a way to create, configure and run an Xml parser. + +*) + +(** Abstract type for an Xml parser. *) +type t + +(** Several kind of resources can contain Xml documents. *) +type source = + | SFile of string + | SChannel of in_channel + | SString of string + | SLexbuf of Lexing.lexbuf + +(** This function returns a new parser with default options. *) +val make : unit -> t + +(** This function enable or disable automatic DTD proving with the parser. + Note that Xml documents having no reference to a DTD are never proved + when parsed (but you can prove them later using the {!Dtd} module + {i (by default, prove is true)}. *) +val prove : t -> bool -> unit + +(** When parsing an Xml document from a file using the {!Xml.parse_file} + function, the DTD file if declared by the Xml document has to be in the + same directory as the xml file. When using other parsing functions, + such as on a string or on a channel, the parser will raise everytime + {!Xml.File_not_found} if a DTD file is needed and prove enabled. To enable + the DTD loading of the file, the user have to configure the Xml parser + with a [resolve] function which is taking as argument the DTD filename and + is returning a checked DTD. The user can then implement any kind of DTD + loading strategy, and can use the {!Dtd} module functions to parse and check + the DTD file {i (by default, the resolve function is raising} + {!Xml.File_not_found}). *) +val resolve : t -> (string -> Dtd.checked) -> unit + +(** When a Xml document is parsed, the parser will check that the end of the + document is reached, so for example parsing [""] will fail instead + of returning only the A element. You can turn off this check by setting + [check_eof] to [false] {i (by default, check_eof is true)}. *) +val check_eof : t -> bool -> unit + +(** Once the parser is configurated, you can run the parser on a any kind + of xml document source to parse its contents into an Xml data structure. *) +val parse : t -> source -> Xml.xml + +(** When several PCData elements are separed by a \n (or \r\n), you can + either split the PCData in two distincts PCData or merge them with \n + as seperator into one PCData. The default behavior is to concat the + PCData, but this can be changed for a given parser with this flag. *) +val concat_pcdata : t -> bool -> unit + +(**/**) + +(* internal usage only... *) +val _raises : (Xml.error_msg -> Lexing.lexbuf -> exn) -> (string -> exn) -> (Dtd.parse_error_msg -> Lexing.lexbuf -> exn) -> unit diff --git a/tests/php/Crawler.php b/tests/php/Crawler.php new file mode 100644 index 0000000..b408d7d --- /dev/null +++ b/tests/php/Crawler.php @@ -0,0 +1,721 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DomCrawler; + +use Symfony\Component\CssSelector\CssSelector; + +/** + * Crawler eases navigation of a list of \DOMNode objects. + * + * @author Fabien Potencier + * + * @api + */ +class Crawler extends \SplObjectStorage +{ + /** + * @var string The current URI or the base href value + */ + private $uri; + + /** + * Constructor. + * + * @param mixed $node A Node to use as the base for the crawling + * @param string $uri The current URI or the base href value + * + * @api + */ + public function __construct($node = null, $uri = null) + { + $this->uri = $uri; + + $this->add($node); + } + + /** + * Removes all the nodes. + * + * @api + */ + public function clear() + { + $this->removeAll($this); + } + + /** + * Adds a node to the current list of nodes. + * + * This method uses the appropriate specialized add*() method based + * on the type of the argument. + * + * @param null|\DOMNodeList|array|\DOMNode $node A node + * + * @api + */ + public function add($node) + { + if ($node instanceof \DOMNodeList) { + $this->addNodeList($node); + } elseif (is_array($node)) { + $this->addNodes($node); + } elseif (is_string($node)) { + $this->addContent($node); + } elseif (is_object($node)) { + $this->addNode($node); + } + } + + /** + * Adds HTML/XML content. + * + * @param string $content A string to parse as HTML/XML + * @param null|string $type The content type of the string + * + * @return null|void + */ + public function addContent($content, $type = null) + { + if (empty($type)) { + $type = 'text/html'; + } + + // DOM only for HTML/XML content + if (!preg_match('/(x|ht)ml/i', $type, $matches)) { + return null; + } + + $charset = 'ISO-8859-1'; + if (false !== $pos = strpos($type, 'charset=')) { + $charset = substr($type, $pos + 8); + if (false !== $pos = strpos($charset, ';')) { + $charset = substr($charset, 0, $pos); + } + } + + if ('x' === $matches[1]) { + $this->addXmlContent($content, $charset); + } else { + $this->addHtmlContent($content, $charset); + } + } + + /** + * Adds an HTML content to the list of nodes. + * + * The libxml errors are disabled when the content is parsed. + * + * If you want to get parsing errors, be sure to enable + * internal errors via libxml_use_internal_errors(true) + * and then, get the errors via libxml_get_errors(). Be + * sure to clear errors with libxml_clear_errors() afterward. + * + * @param string $content The HTML content + * @param string $charset The charset + * + * @api + */ + public function addHtmlContent($content, $charset = 'UTF-8') + { + $dom = new \DOMDocument('1.0', $charset); + $dom->validateOnParse = true; + + if (function_exists('mb_convert_encoding')) { + $content = mb_convert_encoding($content, 'HTML-ENTITIES', $charset); + } + + $current = libxml_use_internal_errors(true); + @$dom->loadHTML($content); + libxml_use_internal_errors($current); + + $this->addDocument($dom); + + $base = $this->filterXPath('descendant-or-self::base')->extract(array('href')); + + if (count($base)) { + $this->uri = current($base); + } + } + + /** + * Adds an XML content to the list of nodes. + * + * The libxml errors are disabled when the content is parsed. + * + * If you want to get parsing errors, be sure to enable + * internal errors via libxml_use_internal_errors(true) + * and then, get the errors via libxml_get_errors(). Be + * sure to clear errors with libxml_clear_errors() afterward. + * + * @param string $content The XML content + * @param string $charset The charset + * + * @api + */ + public function addXmlContent($content, $charset = 'UTF-8') + { + $dom = new \DOMDocument('1.0', $charset); + $dom->validateOnParse = true; + + // remove the default namespace to make XPath expressions simpler + $current = libxml_use_internal_errors(true); + @$dom->loadXML(str_replace('xmlns', 'ns', $content)); + libxml_use_internal_errors($current); + + $this->addDocument($dom); + } + + /** + * Adds a \DOMDocument to the list of nodes. + * + * @param \DOMDocument $dom A \DOMDocument instance + * + * @api + */ + public function addDocument(\DOMDocument $dom) + { + if ($dom->documentElement) { + $this->addNode($dom->documentElement); + } + } + + /** + * Adds a \DOMNodeList to the list of nodes. + * + * @param \DOMNodeList $nodes A \DOMNodeList instance + * + * @api + */ + public function addNodeList(\DOMNodeList $nodes) + { + foreach ($nodes as $node) { + $this->addNode($node); + } + } + + /** + * Adds an array of \DOMNode instances to the list of nodes. + * + * @param array $nodes An array of \DOMNode instances + * + * @api + */ + public function addNodes(array $nodes) + { + foreach ($nodes as $node) { + $this->add($node); + } + } + + /** + * Adds a \DOMNode instance to the list of nodes. + * + * @param \DOMNode $node A \DOMNode instance + * + * @api + */ + public function addNode(\DOMNode $node) + { + if ($node instanceof \DOMDocument) { + $this->attach($node->documentElement); + } else { + $this->attach($node); + } + } + + /** + * Returns a node given its position in the node list. + * + * @param integer $position The position + * + * @return Crawler A new instance of the Crawler with the selected node, or an empty Crawler if it does not exist. + * + * @api + */ + public function eq($position) + { + foreach ($this as $i => $node) { + if ($i == $position) { + return new static($node, $this->uri); + } + } + + return new static(null, $this->uri); + } + + /** + * Calls an anonymous function on each node of the list. + * + * The anonymous function receives the position and the node as arguments. + * + * Example: + * + * $crawler->filter('h1')->each(function ($node, $i) + * { + * return $node->nodeValue; + * }); + * + * @param \Closure $closure An anonymous function + * + * @return array An array of values returned by the anonymous function + * + * @api + */ + public function each(\Closure $closure) + { + $data = array(); + foreach ($this as $i => $node) { + $data[] = $closure($node, $i); + } + + return $data; + } + + /** + * Reduces the list of nodes by calling an anonymous function. + * + * To remove a node from the list, the anonymous function must return false. + * + * @param \Closure $closure An anonymous function + * + * @return Crawler A Crawler instance with the selected nodes. + * + * @api + */ + public function reduce(\Closure $closure) + { + $nodes = array(); + foreach ($this as $i => $node) { + if (false !== $closure($node, $i)) { + $nodes[] = $node; + } + } + + return new static($nodes, $this->uri); + } + + /** + * Returns the first node of the current selection + * + * @return Crawler A Crawler instance with the first selected node + * + * @api + */ + public function first() + { + return $this->eq(0); + } + + /** + * Returns the last node of the current selection + * + * @return Crawler A Crawler instance with the last selected node + * + * @api + */ + public function last() + { + return $this->eq(count($this) - 1); + } + + /** + * Returns the siblings nodes of the current selection + * + * @return Crawler A Crawler instance with the sibling nodes + * + * @throws \InvalidArgumentException When current node is empty + * + * @api + */ + public function siblings() + { + if (!count($this)) { + throw new \InvalidArgumentException('The current node list is empty.'); + } + + return new static($this->sibling($this->getNode(0)->parentNode->firstChild), $this->uri); + } + + /** + * Returns the next siblings nodes of the current selection + * + * @return Crawler A Crawler instance with the next sibling nodes + * + * @throws \InvalidArgumentException When current node is empty + * + * @api + */ + public function nextAll() + { + if (!count($this)) { + throw new \InvalidArgumentException('The current node list is empty.'); + } + + return new static($this->sibling($this->getNode(0)), $this->uri); + } + + /** + * Returns the previous sibling nodes of the current selection + * + * @return Crawler A Crawler instance with the previous sibling nodes + * + * @api + */ + public function previousAll() + { + if (!count($this)) { + throw new \InvalidArgumentException('The current node list is empty.'); + } + + return new static($this->sibling($this->getNode(0), 'previousSibling'), $this->uri); + } + + /** + * Returns the parents nodes of the current selection + * + * @return Crawler A Crawler instance with the parents nodes of the current selection + * + * @throws \InvalidArgumentException When current node is empty + * + * @api + */ + public function parents() + { + if (!count($this)) { + throw new \InvalidArgumentException('The current node list is empty.'); + } + + $node = $this->getNode(0); + $nodes = array(); + + while ($node = $node->parentNode) { + if (1 === $node->nodeType && '_root' !== $node->nodeName) { + $nodes[] = $node; + } + } + + return new static($nodes, $this->uri); + } + + /** + * Returns the children nodes of the current selection + * + * @return Crawler A Crawler instance with the children nodes + * + * @throws \InvalidArgumentException When current node is empty + * + * @api + */ + public function children() + { + if (!count($this)) { + throw new \InvalidArgumentException('The current node list is empty.'); + } + + return new static($this->sibling($this->getNode(0)->firstChild), $this->uri); + } + + /** + * Returns the attribute value of the first node of the list. + * + * @param string $attribute The attribute name + * + * @return string The attribute value + * + * @throws \InvalidArgumentException When current node is empty + * + * @api + */ + public function attr($attribute) + { + if (!count($this)) { + throw new \InvalidArgumentException('The current node list is empty.'); + } + + return $this->getNode(0)->getAttribute($attribute); + } + + /** + * Returns the node value of the first node of the list. + * + * @return string The node value + * + * @throws \InvalidArgumentException When current node is empty + * + * @api + */ + public function text() + { + if (!count($this)) { + throw new \InvalidArgumentException('The current node list is empty.'); + } + + return $this->getNode(0)->nodeValue; + } + + /** + * Extracts information from the list of nodes. + * + * You can extract attributes or/and the node value (_text). + * + * Example: + * + * $crawler->filter('h1 a')->extract(array('_text', 'href')); + * + * @param array $attributes An array of attributes + * + * @return array An array of extracted values + * + * @api + */ + public function extract($attributes) + { + $attributes = (array) $attributes; + + $data = array(); + foreach ($this as $node) { + $elements = array(); + foreach ($attributes as $attribute) { + if ('_text' === $attribute) { + $elements[] = $node->nodeValue; + } else { + $elements[] = $node->getAttribute($attribute); + } + } + + $data[] = count($attributes) > 1 ? $elements : $elements[0]; + } + + return $data; + } + + /** + * Filters the list of nodes with an XPath expression. + * + * @param string $xpath An XPath expression + * + * @return Crawler A new instance of Crawler with the filtered list of nodes + * + * @api + */ + public function filterXPath($xpath) + { + $document = new \DOMDocument('1.0', 'UTF-8'); + $root = $document->appendChild($document->createElement('_root')); + foreach ($this as $node) { + $root->appendChild($document->importNode($node, true)); + } + + $domxpath = new \DOMXPath($document); + + return new static($domxpath->query($xpath), $this->uri); + } + + /** + * Filters the list of nodes with a CSS selector. + * + * This method only works if you have installed the CssSelector Symfony Component. + * + * @param string $selector A CSS selector + * + * @return Crawler A new instance of Crawler with the filtered list of nodes + * + * @throws \RuntimeException if the CssSelector Component is not available + * + * @api + */ + public function filter($selector) + { + if (!class_exists('Symfony\\Component\\CssSelector\\CssSelector')) { + // @codeCoverageIgnoreStart + throw new \RuntimeException('Unable to filter with a CSS selector as the Symfony CssSelector is not installed (you can use filterXPath instead).'); + // @codeCoverageIgnoreEnd + } + + return $this->filterXPath(CssSelector::toXPath($selector)); + } + + /** + * Selects links by name or alt value for clickable images. + * + * @param string $value The link text + * + * @return Crawler A new instance of Crawler with the filtered list of nodes + * + * @api + */ + public function selectLink($value) + { + $xpath = sprintf('//a[contains(concat(\' \', normalize-space(string(.)), \' \'), %s)] ', static::xpathLiteral(' '.$value.' ')). + sprintf('| //a/img[contains(concat(\' \', normalize-space(string(@alt)), \' \'), %s)]/ancestor::a', static::xpathLiteral(' '.$value.' ')); + + return $this->filterXPath($xpath); + } + + /** + * Selects a button by name or alt value for images. + * + * @param string $value The button text + * + * @return Crawler A new instance of Crawler with the filtered list of nodes + * + * @api + */ + public function selectButton($value) + { + $xpath = sprintf('//input[((@type="submit" or @type="button") and contains(concat(\' \', normalize-space(string(@value)), \' \'), %s)) ', static::xpathLiteral(' '.$value.' ')). + sprintf('or (@type="image" and contains(concat(\' \', normalize-space(string(@alt)), \' \'), %s)) or @id="%s" or @name="%s"] ', static::xpathLiteral(' '.$value.' '), $value, $value). + sprintf('| //button[contains(concat(\' \', normalize-space(string(.)), \' \'), %s) or @id="%s" or @name="%s"]', static::xpathLiteral(' '.$value.' '), $value, $value); + + return $this->filterXPath($xpath); + } + + /** + * Returns a Link object for the first node in the list. + * + * @param string $method The method for the link (get by default) + * + * @return Link A Link instance + * + * @throws \InvalidArgumentException If the current node list is empty + * + * @api + */ + public function link($method = 'get') + { + if (!count($this)) { + throw new \InvalidArgumentException('The current node list is empty.'); + } + + $node = $this->getNode(0); + + return new Link($node, $this->uri, $method); + } + + /** + * Returns an array of Link objects for the nodes in the list. + * + * @return array An array of Link instances + * + * @api + */ + public function links() + { + $links = array(); + foreach ($this as $node) { + $links[] = new Link($node, $this->uri, 'get'); + } + + return $links; + } + + /** + * Returns a Form object for the first node in the list. + * + * @param array $values An array of values for the form fields + * @param string $method The method for the form + * + * @return Form A Form instance + * + * @throws \InvalidArgumentException If the current node list is empty + * + * @api + */ + public function form(array $values = null, $method = null) + { + if (!count($this)) { + throw new \InvalidArgumentException('The current node list is empty.'); + } + + $form = new Form($this->getNode(0), $this->uri, $method); + + if (null !== $values) { + $form->setValues($values); + } + + return $form; + } + + /** + * Converts string for XPath expressions. + * + * Escaped characters are: quotes (") and apostrophe ('). + * + * Examples: + * + * echo Crawler::xpathLiteral('foo " bar'); + * //prints 'foo " bar' + * + * echo Crawler::xpathLiteral("foo ' bar"); + * //prints "foo ' bar" + * + * echo Crawler::xpathLiteral('a\'b"c'); + * //prints concat('a', "'", 'b"c') + * + * + * @param string $s String to be escaped + * + * @return string Converted string + * + */ + public static function xpathLiteral($s) + { + if (false === strpos($s, "'")) { + return sprintf("'%s'", $s); + } + + if (false === strpos($s, '"')) { + return sprintf('"%s"', $s); + } + + $string = $s; + $parts = array(); + while (true) { + if (false !== $pos = strpos($string, "'")) { + $parts[] = sprintf("'%s'", substr($string, 0, $pos)); + $parts[] = "\"'\""; + $string = substr($string, $pos + 1); + } else { + $parts[] = "'$string'"; + break; + } + } + + return sprintf("concat(%s)", implode($parts, ', ')); + } + + private function getNode($position) + { + foreach ($this as $i => $node) { + if ($i == $position) { + return $node; + } + // @codeCoverageIgnoreStart + } + + return null; + // @codeCoverageIgnoreEnd + } + + private function sibling($node, $siblingDir = 'nextSibling') + { + $nodes = array(); + + do { + if ($node !== $this->getNode(0) && $node->nodeType === 1) { + $nodes[] = $node; + } + } while ($node = $node->$siblingDir); + + return $nodes; + } +} diff --git a/tests/php/Link.php b/tests/php/Link.php new file mode 100644 index 0000000..1804111 --- /dev/null +++ b/tests/php/Link.php @@ -0,0 +1,157 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DomCrawler; + +/** + * Link represents an HTML link (an HTML a tag). + * + * @author Fabien Potencier + * + * @api + */ +class Link +{ + /** + * @var \DOMNode A \DOMNode instance + */ + protected $node; + /** + * @var string The method to use for the link + */ + protected $method; + /** + * @var string The URI of the page where the link is embedded (or the base href) + */ + protected $currentUri; + + /** + * Constructor. + * + * @param \DOMNode $node A \DOMNode instance + * @param string $currentUri The URI of the page where the link is embedded (or the base href) + * @param string $method The method to use for the link (get by default) + * + * @throws \InvalidArgumentException if the node is not a link + * + * @api + */ + public function __construct(\DOMNode $node, $currentUri, $method = 'GET') + { + if (!in_array(substr($currentUri, 0, 4), array('http', 'file'))) { + throw new \InvalidArgumentException(sprintf('Current URI must be an absolute URL ("%s").', $currentUri)); + } + + $this->setNode($node); + $this->method = $method ? strtoupper($method) : null; + $this->currentUri = $currentUri; + } + + /** + * Gets the node associated with this link. + * + * @return \DOMNode A \DOMNode instance + */ + public function getNode() + { + return $this->node; + } + + /** + * Gets the method associated with this link. + * + * @return string The method + * + * @api + */ + public function getMethod() + { + return $this->method; + } + + /** + * Gets the URI associated with this link. + * + * @return string The URI + * + * @api + */ + public function getUri() + { + $uri = trim($this->getRawUri()); + + // absolute URL? + if (0 === strpos($uri, 'http')) { + return $uri; + } + + // empty URI + if (!$uri) { + return $this->currentUri; + } + + // only an anchor + if ('#' === $uri[0]) { + $baseUri = $this->currentUri; + if (false !== $pos = strpos($baseUri, '#')) { + $baseUri = substr($baseUri, 0, $pos); + } + + return $baseUri.$uri; + } + + // only a query string + if ('?' === $uri[0]) { + $baseUri = $this->currentUri; + + // remove the query string from the current uri + if (false !== $pos = strpos($baseUri, '?')) { + $baseUri = substr($baseUri, 0, $pos); + } + + return $baseUri.$uri; + } + + // absolute path + if ('/' === $uri[0]) { + return preg_replace('#^(.*?//[^/]+)(?:\/.*)?$#', '$1', $this->currentUri).$uri; + } + + // relative path + return substr($this->currentUri, 0, strrpos($this->currentUri, '/') + 1).$uri; + } + + /** + * Returns raw uri data + * + * @return string + */ + protected function getRawUri() + { + return $this->node->getAttribute('href'); + } + + /** + * Sets current \DOMNode instance + * + * @param \DOMNode $node A \DOMNode instance + * + * @throws \LogicException If given node is not an anchor + */ + protected function setNode(\DOMNode $node) + { + if ('a' != $node->nodeName) { + throw new \LogicException(sprintf('Unable to click on a "%s" tag.', $node->nodeName)); + } + + $this->node = $node; + } +} diff --git a/tests/php/gda-clean.php b/tests/php/gda-clean.php new file mode 100644 index 0000000..ea8b1d2 --- /dev/null +++ b/tests/php/gda-clean.php @@ -0,0 +1,23 @@ +"); +$reply->addChild ("status", "OK"); +echo gda_add_hash ($init_shared, $reply->asXml()); +session_destroy (); + +?> diff --git a/tests/python/bcontroller.py b/tests/python/bcontroller.py new file mode 100755 index 0000000..ea7b8b3 --- /dev/null +++ b/tests/python/bcontroller.py @@ -0,0 +1,238 @@ +#!/usr/bin/env python +# +# ***** BEGIN LICENSE BLOCK ***** +# Version: MPL 1.1/GPL 2.0/LGPL 2.1 +# +# The contents of this file are subject to the Mozilla Public License Version +# 1.1 (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# http://www.mozilla.org/MPL/ +# +# Software distributed under the License is distributed on an "AS IS" basis, +# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License +# for the specific language governing rights and limitations under the +# License. +# +# The Original Code is standalone Firefox Windows performance test. +# +# The Initial Developer of the Original Code is Google Inc. +# Portions created by the Initial Developer are Copyright (C) 2006 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# Alice Nodelman (original author) +# +# Alternatively, the contents of this file may be used under the terms of +# either the GNU General Public License Version 2 or later (the "GPL"), or +# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), +# in which case the provisions of the GPL or the LGPL are applicable instead +# of those above. If you wish to allow use of your version of this file only +# under the terms of either the GPL or the LGPL, and not to allow others to +# use your version of this file under the terms of the MPL, indicate your +# decision by deleting the provisions above and replace them with the notice +# and other provisions required by the GPL or the LGPL. If you do not delete +# the provisions above, a recipient may use your version of this file under +# the terms of any one of the MPL, the GPL or the LGPL. +# +# ***** END LICENSE BLOCK ***** + +__author__ = 'anodelman@mozilla.com (Alice Nodelman)' + +import os +import time +import subprocess +import threading +import platform +from ffprocess_linux import LinuxProcess +from ffprocess_mac import MacProcess +from ffprocess_win32 import Win32Process +from utils import talosError +import sys +import getopt + +import stat + + +if platform.system() == "Linux": + platform_type = 'linux_' + ffprocess = LinuxProcess() +elif platform.system() in ("Windows", "Microsoft"): + import win32pdh + import win32api + import win32event + import win32con + platform_type = 'win_' + ffprocess = Win32Process() +elif platform.system() == "Darwin": + platform_type = 'mac_' + ffprocess = MacProcess() + +class BrowserWaiter(threading.Thread): + + def __init__(self, command, log, mod, deviceManager = None): + self.command = command + self.log = log + self.mod = mod + self.endTime = -1 + self.returncode = -1 + self.deviceManager = deviceManager + threading.Thread.__init__(self) + self.start() + + def run(self): + if self.mod: + if (self.deviceManager.__class__.__name__ == "WinmoProcess"): + if (self.mod == "str(int(time.time()*1000))"): + self.command += self.deviceManager.getCurrentTime() + else: + self.command = self.command + eval(self.mod) + + if (self.deviceManager.__class__.__name__ == "WinmoProcess"): + retVal = self.deviceManager.launchProcess(self.command, timeout=600) + if retVal <> None: + self.deviceManager.getFile(retVal, self.log) + self.returncode = 0 + else: + self.returncode = 1 + else: #blocking call to system + self.returncode = os.system(self.command + " > " + self.log) + + self.endTime = int(time.time()*1000) + + def hasTime(self): + return self.endTime > -1 + + def getTime(self): + return self.endTime + + def getReturn(self): + def innerMethod(self): + pass + return self.returncode + + def testMethod2(self): + class InnerClass: + def innerInnerMethod(self): + pass + return + + class Test: + def testMethod(self): + pass + +class BrowserController: + + def __init__(self, command, mod, name, child_process, + timeout, log, host='', port=20701, root=''): + global ffprocess + self.command = command + self.mod = mod + self.process_name = name + self.child_process = child_process + self.browser_wait = timeout + self.log = log + self.timeout = 1200 #no output from the browser in 20 minutes = failure + self.host = host + self.port = port + self.root = root + if (host <> ''): + from ffprocess_winmo import WinmoProcess + platform_type = 'win_' + ffprocess = WinmoProcess(host, port, root) + + self.ffprocess = ffprocess + + def run(self): + self.bwaiter = BrowserWaiter(self.command, self.log, self.mod, self.ffprocess) + noise = 0 + prev_size = 0 + while not self.bwaiter.hasTime(): + if noise > self.timeout: # check for frozen browser + try: + ffprocess.cleanupProcesses(self.process_name, self.child_process, self.browser_wait) + except talosError, te: + os.abort() #kill myself off because something horrible has happened + os.chmod(self.log, 0777) + results_file = open(self.log, "a") + results_file.write("\n__FAILbrowser frozen__FAIL\n") + results_file.close() + return + time.sleep(1) + try: + open(self.log, "r").close() #HACK FOR WINDOWS: refresh the file information + size = os.path.getsize(self.log) + except: + size = 0 + + if size > prev_size: + prev_size = size + noise = 0 + else: + noise += 1 + + results_file = open(self.log, "a") + if self.bwaiter.getReturn() != 0: #the browser shutdown, but not cleanly + results_file.write("\n__FAILbrowser non-zero return code (%d)__FAIL\n" % self.bwaiter.getReturn()) + return + results_file.write("__startSecondTimestamp%d__endSecondTimestamp\n" % self.bwaiter.getTime()) + results_file.close() + return + + +def main(argv=None): + + command = "" + name = "firefox" #default + child_process = "plugin-container" #default + timeout = "" + log = "" + mod = "" + host = "" + deviceRoot = "" + port = 20701 + + if argv is None: + argv = sys.argv + opts, args = getopt.getopt(argv[1:], "c:t:n:p:l:m:h:r:o", ["command=", "timeout=", "name=", "child_process=", "log=", "mod=", "host=", "deviceRoot=", "port="]) + + # option processing + for option, value in opts: + if option in ("-c", "--command"): + command = value + if option in ("-t", "--timeout"): + timeout = int(value) + if option in ("-n", "--name"): + name = value + if option in ("-p", "--child_process"): + child_process = value + if option in ("-l", "--log"): + log = value + if option in ("-m", "--mod"): + mod = value + if option in ("-h", "--host"): + host = value + if option in ("-r", "--deviceRoot"): + deviceRoot = value + if option in ("-o", "--port"): + port = value + + if command and timeout and log: + bcontroller = BrowserController(command, mod, name, child_process, timeout, log, host, port, deviceRoot) + bcontroller.run() + else: + print "\nFAIL: no command\n" + sys.stdout.flush() + + class mainClass: + def mainClassMethod(self): + pass + pass + + def mainMethod(self): + class mainMethodClass: + pass + pass + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/tests/python/functions.py b/tests/python/functions.py new file mode 100644 index 0000000..32ec0eb --- /dev/null +++ b/tests/python/functions.py @@ -0,0 +1,697 @@ +""" +Makefile functions. +""" + +import parser, util +import subprocess, os, logging +from globrelative import glob +from cStringIO import StringIO + +log = logging.getLogger('pymake.data') + +class Function(object): + """ + An object that represents a function call. This class is always subclassed + with the following methods and attributes: + + minargs = minimum # of arguments + maxargs = maximum # of arguments (0 means unlimited) + + def resolve(self, makefile, variables, fd, setting) + Calls the function + calls fd.write() with strings + """ + + __slots__ = ('_arguments', 'loc') + + def __init__(self, loc): + self._arguments = [] + self.loc = loc + assert self.minargs > 0 + + def __getitem__(self, key): + return self._arguments[key] + + def setup(self): + argc = len(self._arguments) + + if argc < self.minargs: + raise data.DataError("Not enough arguments to function %s, requires %s" % (self.name, self.minargs), self.loc) + + assert self.maxargs == 0 or argc <= self.maxargs, "Parser screwed up, gave us too many args" + + def append(self, arg): + assert isinstance(arg, (data.Expansion, data.StringExpansion)) + self._arguments.append(arg) + + def __len__(self): + return len(self._arguments) + +class VariableRef(Function): + __slots__ = ('vname', 'loc') + + def __init__(self, loc, vname): + self.loc = loc + assert isinstance(vname, (data.Expansion, data.StringExpansion)) + self.vname = vname + + def setup(self): + assert False, "Shouldn't get here" + + def resolve(self, makefile, variables, fd, setting): + vname = self.vname.resolvestr(makefile, variables, setting) + if vname in setting: + raise data.DataError("Setting variable '%s' recursively references itself." % (vname,), self.loc) + + flavor, source, value = variables.get(vname) + if value is None: + log.debug("%s: variable '%s' was not set" % (self.loc, vname)) + return + + value.resolve(makefile, variables, fd, setting + [vname]) + +class SubstitutionRef(Function): + """$(VARNAME:.c=.o) and $(VARNAME:%.c=%.o)""" + + __slots__ = ('loc', 'vname', 'substfrom', 'substto') + + def __init__(self, loc, varname, substfrom, substto): + self.loc = loc + self.vname = varname + self.substfrom = substfrom + self.substto = substto + + def setup(self): + assert False, "Shouldn't get here" + + def resolve(self, makefile, variables, fd, setting): + vname = self.vname.resolvestr(makefile, variables, setting) + if vname in setting: + raise data.DataError("Setting variable '%s' recursively references itself." % (vname,), self.loc) + + substfrom = self.substfrom.resolvestr(makefile, variables, setting) + substto = self.substto.resolvestr(makefile, variables, setting) + + flavor, source, value = variables.get(vname) + if value is None: + log.debug("%s: variable '%s' was not set" % (self.loc, vname)) + return + + f = data.Pattern(substfrom) + if not f.ispattern(): + f = data.Pattern('%' + substfrom) + substto = '%' + substto + + fd.write(' '.join([f.subst(substto, word, False) + for word in value.resolvesplit(makefile, variables, setting + [vname])])) + +class SubstFunction(Function): + name = 'subst' + minargs = 3 + maxargs = 3 + + __slots__ = Function.__slots__ + + def resolve(self, makefile, variables, fd, setting): + s = self._arguments[0].resolvestr(makefile, variables, setting) + r = self._arguments[1].resolvestr(makefile, variables, setting) + d = self._arguments[2].resolvestr(makefile, variables, setting) + fd.write(d.replace(s, r)) + +class PatSubstFunction(Function): + name = 'patsubst' + minargs = 3 + maxargs = 3 + + __slots__ = Function.__slots__ + + def resolve(self, makefile, variables, fd, setting): + s = self._arguments[0].resolvestr(makefile, variables, setting) + r = self._arguments[1].resolvestr(makefile, variables, setting) + + p = data.Pattern(s) + fd.write(' '.join([p.subst(r, word, False) + for word in self._arguments[2].resolvesplit(makefile, variables, setting)])) + +class StripFunction(Function): + name = 'strip' + minargs = 1 + maxargs = 1 + + __slots__ = Function.__slots__ + + def resolve(self, makefile, variables, fd, setting): + util.joiniter(fd, self._arguments[0].resolvesplit(makefile, variables, setting)) + +class FindstringFunction(Function): + name = 'findstring' + minargs = 2 + maxargs = 2 + + __slots__ = Function.__slots__ + + def resolve(self, makefile, variables, fd, setting): + s = self._arguments[0].resolvestr(makefile, variables, setting) + r = self._arguments[1].resolvestr(makefile, variables, setting) + if r.find(s) == -1: + return + fd.write(s) + +class FilterFunction(Function): + name = 'filter' + minargs = 2 + maxargs = 2 + + __slots__ = Function.__slots__ + + def resolve(self, makefile, variables, fd, setting): + plist = [data.Pattern(p) + for p in self._arguments[0].resolvesplit(makefile, variables, setting)] + + fd.write(' '.join([w for w in self._arguments[1].resolvesplit(makefile, variables, setting) + if util.any((p.match(w) for p in plist))])) + +class FilteroutFunction(Function): + name = 'filter-out' + minargs = 2 + maxargs = 2 + + __slots__ = Function.__slots__ + + def resolve(self, makefile, variables, fd, setting): + plist = [data.Pattern(p) + for p in self._arguments[0].resolvesplit(makefile, variables, setting)] + + fd.write(' '.join([w for w in self._arguments[1].resolvesplit(makefile, variables, setting) + if not util.any((p.match(w) for p in plist))])) + +class SortFunction(Function): + name = 'sort' + minargs = 1 + maxargs = 1 + + __slots__ = Function.__slots__ + + def resolve(self, makefile, variables, fd, setting): + d = list(self._arguments[0].resolvesplit(makefile, variables, setting)) + d.sort() + util.joiniter(fd, d) + +class WordFunction(Function): + name = 'word' + minargs = 2 + maxargs = 2 + + __slots__ = Function.__slots__ + + def resolve(self, makefile, variables, fd, setting): + n = self._arguments[0].resolvestr(makefile, variables, setting) + # TODO: provide better error if this doesn't convert + n = int(n) + words = list(self._arguments[1].resolvesplit(makefile, variables, setting)) + if n < 1 or n > len(words): + return + fd.write(words[n - 1]) + +class WordlistFunction(Function): + name = 'wordlist' + minargs = 3 + maxargs = 3 + + __slots__ = Function.__slots__ + + def resolve(self, makefile, variables, fd, setting): + nfrom = self._arguments[0].resolvestr(makefile, variables, setting) + nto = self._arguments[1].resolvestr(makefile, variables, setting) + # TODO: provide better errors if this doesn't convert + nfrom = int(nfrom) + nto = int(nto) + + words = list(self._arguments[2].resolvesplit(makefile, variables, setting)) + + if nfrom < 1: + nfrom = 1 + if nto < 1: + nto = 1 + + util.joiniter(fd, words[nfrom - 1:nto]) + +class WordsFunction(Function): + name = 'words' + minargs = 1 + maxargs = 1 + + __slots__ = Function.__slots__ + + def resolve(self, makefile, variables, fd, setting): + fd.write(str(len(self._arguments[0].resolvesplit(makefile, variables, setting)))) + +class FirstWordFunction(Function): + name = 'firstword' + minargs = 1 + maxargs = 1 + + __slots__ = Function.__slots__ + + def resolve(self, makefile, variables, fd, setting): + l = self._arguments[0].resolvesplit(makefile, variables, setting) + if len(l): + fd.write(l[0]) + +class LastWordFunction(Function): + name = 'lastword' + minargs = 1 + maxargs = 1 + + __slots__ = Function.__slots__ + + def resolve(self, makefile, variables, fd, setting): + l = self._arguments[0].resolvesplit(makefile, variables, setting) + if len(l): + fd.write(l[-1]) + +def pathsplit(path, default='./'): + """ + Splits a path into dirpart, filepart on the last slash. If there is no slash, dirpart + is ./ + """ + dir, slash, file = util.strrpartition(path, '/') + if dir == '': + return default, file + + return dir + slash, file + +class DirFunction(Function): + name = 'dir' + minargs = 1 + maxargs = 1 + + def resolve(self, makefile, variables, fd, setting): + fd.write(' '.join([pathsplit(path)[0] + for path in self._arguments[0].resolvesplit(makefile, variables, setting)])) + +class NotDirFunction(Function): + name = 'notdir' + minargs = 1 + maxargs = 1 + + __slots__ = Function.__slots__ + + def resolve(self, makefile, variables, fd, setting): + fd.write(' '.join([pathsplit(path)[1] + for path in self._arguments[0].resolvesplit(makefile, variables, setting)])) + +class SuffixFunction(Function): + name = 'suffix' + minargs = 1 + maxargs = 1 + + __slots__ = Function.__slots__ + + @staticmethod + def suffixes(words): + for w in words: + dir, file = pathsplit(w) + base, dot, suffix = util.strrpartition(file, '.') + if base != '': + yield dot + suffix + + def resolve(self, makefile, variables, fd, setting): + util.joiniter(fd, self.suffixes(self._arguments[0].resolvesplit(makefile, variables, setting))) + +class BasenameFunction(Function): + name = 'basename' + minargs = 1 + maxargs = 1 + + __slots__ = Function.__slots__ + + @staticmethod + def basenames(words): + for w in words: + dir, file = pathsplit(w, '') + base, dot, suffix = util.strrpartition(file, '.') + if dot == '': + base = suffix + + yield dir + base + + def resolve(self, makefile, variables, fd, setting): + util.joiniter(fd, self.basenames(self._arguments[0].resolvesplit(makefile, variables, setting))) + +class AddSuffixFunction(Function): + name = 'addprefix' + minargs = 2 + maxargs = 2 + + __slots__ = Function.__slots__ + + def resolve(self, makefile, variables, fd, setting): + suffix = self._arguments[0].resolvestr(makefile, variables, setting) + + fd.write(' '.join([w + suffix for w in self._arguments[1].resolvesplit(makefile, variables, setting)])) + +class AddPrefixFunction(Function): + name = 'addsuffix' + minargs = 2 + maxargs = 2 + + def resolve(self, makefile, variables, fd, setting): + prefix = self._arguments[0].resolvestr(makefile, variables, setting) + + fd.write(' '.join([prefix + w for w in self._arguments[1].resolvesplit(makefile, variables, setting)])) + +class JoinFunction(Function): + name = 'join' + minargs = 2 + maxargs = 2 + + __slots__ = Function.__slots__ + + @staticmethod + def iterjoin(l1, l2): + for i in xrange(0, max(len(l1), len(l2))): + i1 = i < len(l1) and l1[i] or '' + i2 = i < len(l2) and l2[i] or '' + yield i1 + i2 + + def resolve(self, makefile, variables, fd, setting): + list1 = list(self._arguments[0].resolvesplit(makefile, variables, setting)) + list2 = list(self._arguments[1].resolvesplit(makefile, variables, setting)) + + util.joiniter(fd, self.iterjoin(list1, list2)) + +class WildcardFunction(Function): + name = 'wildcard' + minargs = 1 + maxargs = 1 + + __slots__ = Function.__slots__ + + def resolve(self, makefile, variables, fd, setting): + patterns = self._arguments[0].resolvesplit(makefile, variables, setting) + + fd.write(' '.join([x.replace('\\','/') + for p in patterns + for x in glob(makefile.workdir, p)])) + + __slots__ = Function.__slots__ + +class RealpathFunction(Function): + name = 'realpath' + minargs = 1 + maxargs = 1 + + def resolve(self, makefile, variables, fd, setting): + fd.write(' '.join([os.path.realpath(os.path.join(makefile.workdir, path)).replace('\\', '/') + for path in self._arguments[0].resolvesplit(makefile, variables, setting)])) + +class AbspathFunction(Function): + name = 'abspath' + minargs = 1 + maxargs = 1 + + __slots__ = Function.__slots__ + + def resolve(self, makefile, variables, fd, setting): + assert os.path.isabs(makefile.workdir) + fd.write(' '.join([util.normaljoin(makefile.workdir, path).replace('\\', '/') + for path in self._arguments[0].resolvesplit(makefile, variables, setting)])) + +class IfFunction(Function): + name = 'if' + minargs = 1 + maxargs = 3 + + __slots__ = Function.__slots__ + + def setup(self): + Function.setup(self) + self._arguments[0].lstrip() + self._arguments[0].rstrip() + + def resolve(self, makefile, variables, fd, setting): + condition = self._arguments[0].resolvestr(makefile, variables, setting) + + if len(condition): + self._arguments[1].resolve(makefile, variables, fd, setting) + elif len(self._arguments) > 2: + return self._arguments[2].resolve(makefile, variables, fd, setting) + +class OrFunction(Function): + name = 'or' + minargs = 1 + maxargs = 0 + + __slots__ = Function.__slots__ + + def resolve(self, makefile, variables, fd, setting): + for arg in self._arguments: + r = arg.resolvestr(makefile, variables, setting) + if r != '': + fd.write(r) + return + +class AndFunction(Function): + name = 'and' + minargs = 1 + maxargs = 0 + + __slots__ = Function.__slots__ + + def resolve(self, makefile, variables, fd, setting): + r = '' + + for arg in self._arguments: + r = arg.resolvestr(makefile, variables, setting) + if r == '': + return + + fd.write(r) + +class ForEachFunction(Function): + name = 'foreach' + minargs = 3 + maxargs = 3 + + __slots__ = Function.__slots__ + + def resolve(self, makefile, variables, fd, setting): + vname = self._arguments[0].resolvestr(makefile, variables, setting) + e = self._arguments[2] + + v = data.Variables(parent=variables) + firstword = True + + for w in self._arguments[1].resolvesplit(makefile, variables, setting): + if firstword: + firstword = False + else: + fd.write(' ') + + v.set(vname, data.Variables.FLAVOR_SIMPLE, data.Variables.SOURCE_AUTOMATIC, w) + e.resolve(makefile, v, fd, setting) + +class CallFunction(Function): + name = 'call' + minargs = 1 + maxargs = 0 + + __slots__ = Function.__slots__ + + def resolve(self, makefile, variables, fd, setting): + vname = self._arguments[0].resolvestr(makefile, variables, setting) + if vname in setting: + raise data.DataError("Recursively setting variable '%s'" % (vname,)) + + v = data.Variables(parent=variables) + v.set('0', data.Variables.FLAVOR_SIMPLE, data.Variables.SOURCE_AUTOMATIC, vname) + for i in xrange(1, len(self._arguments)): + param = self._arguments[i].resolvestr(makefile, variables, setting) + v.set(str(i), data.Variables.FLAVOR_SIMPLE, data.Variables.SOURCE_AUTOMATIC, param) + + flavor, source, e = variables.get(vname) + + if e is None: + return + + if flavor == data.Variables.FLAVOR_SIMPLE: + log.warning("%s: calling variable '%s' which is simply-expanded" % (self.loc, vname)) + + # but we'll do it anyway + e.resolve(makefile, v, fd, setting + [vname]) + +class ValueFunction(Function): + name = 'value' + minargs = 1 + maxargs = 1 + + __slots__ = Function.__slots__ + + def resolve(self, makefile, variables, fd, setting): + varname = self._arguments[0].resolvestr(makefile, variables, setting) + + flavor, source, value = variables.get(varname, expand=False) + if value is not None: + fd.write(value) + +class EvalFunction(Function): + name = 'eval' + minargs = 1 + maxargs = 1 + + def resolve(self, makefile, variables, fd, setting): + if makefile.parsingfinished: + # GNU make allows variables to be set by recursive expansion during + # command execution. This seems really dumb to me, so I don't! + raise data.DataError("$(eval) not allowed via recursive expansion after parsing is finished", self.loc) + + stmts = parser.parsestring(self._arguments[0].resolvestr(makefile, variables, setting), + 'evaluation from %s' % self.loc) + stmts.execute(makefile) + +class OriginFunction(Function): + name = 'origin' + minargs = 1 + maxargs = 1 + + __slots__ = Function.__slots__ + + def resolve(self, makefile, variables, fd, setting): + vname = self._arguments[0].resolvestr(makefile, variables, setting) + + flavor, source, value = variables.get(vname) + if source is None: + r = 'undefined' + elif source == data.Variables.SOURCE_OVERRIDE: + r = 'override' + + elif source == data.Variables.SOURCE_MAKEFILE: + r = 'file' + elif source == data.Variables.SOURCE_ENVIRONMENT: + r = 'environment' + elif source == data.Variables.SOURCE_COMMANDLINE: + r = 'command line' + elif source == data.Variables.SOURCE_AUTOMATIC: + r = 'automatic' + elif source == data.Variables.SOURCE_IMPLICIT: + r = 'default' + + fd.write(r) + +class FlavorFunction(Function): + name = 'flavor' + minargs = 1 + maxargs = 1 + + __slots__ = Function.__slots__ + + def resolve(self, makefile, variables, fd, setting): + varname = self._arguments[0].resolvestr(makefile, variables, setting) + + flavor, source, value = variables.get(varname) + if flavor is None: + r = 'undefined' + elif flavor == data.Variables.FLAVOR_RECURSIVE: + r = 'recursive' + elif flavor == data.Variables.FLAVOR_SIMPLE: + r = 'simple' + fd.write(r) + +class ShellFunction(Function): + name = 'shell' + minargs = 1 + maxargs = 1 + + __slots__ = Function.__slots__ + + def resolve(self, makefile, variables, fd, setting): + #TODO: call this once up-front somewhere and save the result? + shell, msys = util.checkmsyscompat() + cline = self._arguments[0].resolvestr(makefile, variables, setting) + + log.debug("%s: running shell command '%s'" % (self.loc, cline)) + if msys: + cline = [shell, "-c", cline] + p = subprocess.Popen(cline, shell=not msys, stdout=subprocess.PIPE, cwd=makefile.workdir) + stdout, stderr = p.communicate() + + stdout = stdout.replace('\r\n', '\n') + if stdout.endswith('\n'): + stdout = stdout[:-1] + stdout = stdout.replace('\n', ' ') + + fd.write(stdout) + +class ErrorFunction(Function): + name = 'error' + minargs = 1 + maxargs = 1 + + __slots__ = Function.__slots__ + + def resolve(self, makefile, variables, fd, setting): + v = self._arguments[0].resolvestr(makefile, variables, setting) + raise data.DataError(v, self.loc) + +class WarningFunction(Function): + name = 'warning' + minargs = 1 + maxargs = 1 + + __slots__ = Function.__slots__ + + def resolve(self, makefile, variables, fd, setting): + v = self._arguments[0].resolvestr(makefile, variables, setting) + log.warning(v) + +class InfoFunction(Function): + name = 'info' + minargs = 1 + maxargs = 1 + + __slots__ = Function.__slots__ + + def resolve(self, makefile, variables, fd, setting): + v = self._arguments[0].resolvestr(makefile, variables, setting) + print v + +functionmap = { + 'subst': SubstFunction, + 'patsubst': PatSubstFunction, + 'strip': StripFunction, + 'findstring': FindstringFunction, + 'filter': FilterFunction, + 'filter-out': FilteroutFunction, + 'sort': SortFunction, + 'word': WordFunction, + 'wordlist': WordlistFunction, + 'words': WordsFunction, + 'firstword': FirstWordFunction, + 'lastword': LastWordFunction, + 'dir': DirFunction, + 'notdir': NotDirFunction, + 'suffix': SuffixFunction, + 'basename': BasenameFunction, + 'addsuffix': AddSuffixFunction, + 'addprefix': AddPrefixFunction, + 'join': JoinFunction, + 'wildcard': WildcardFunction, + 'realpath': RealpathFunction, + 'abspath': AbspathFunction, + 'if': IfFunction, + 'or': OrFunction, + 'and': AndFunction, + 'foreach': ForEachFunction, + 'call': CallFunction, + 'value': ValueFunction, + 'eval': EvalFunction, + 'origin': OriginFunction, + 'flavor': FlavorFunction, + 'shell': ShellFunction, + 'error': ErrorFunction, + 'warning': WarningFunction, + 'info': InfoFunction, +} + +import data diff --git a/tests/python/mlstring.py b/tests/python/mlstring.py new file mode 100755 index 0000000..71b2b9e --- /dev/null +++ b/tests/python/mlstring.py @@ -0,0 +1,13 @@ +#!/usr/bin/env python + +class ClassName(object): + """docstring for ClassName""" + def __init__(self, arg): + super(ClassName, self).__init__() + self.arg = arg + def function1(self): + foo = """ +foostring + """ + def function2(self): + pass diff --git a/tests/python/models.py b/tests/python/models.py new file mode 100644 index 0000000..c33c9b6 --- /dev/null +++ b/tests/python/models.py @@ -0,0 +1,50 @@ +from django.db import models +from django.contrib.auth.models import User +from django.contrib import admin +from string import join +from settings import MEDIA_ROOT + +class Forum(models.Model): + title = models.CharField(max_length=60) + def __unicode__(self): + return self.title + +class Thread(models.Model): + title = models.CharField(max_length=60) + created = models.DateTimeField(auto_now_add=True) + creator = models.ForeignKey(User, blank=True, null=True) + forum = models.ForeignKey(Forum) + + def __unicode__(self): + return unicode(self.creator) + " - " + self.title + +class Post(models.Model): + title = models.CharField(max_length=60) + created = models.DateTimeField(auto_now_add=True) + creator = models.ForeignKey(User, blank=True, null=True) + thread = models.ForeignKey(Thread) + body = models.TextField(max_length=10000) + + def __unicode__(self): + return u"%s - %s - %s" % (self.creator, self.thread, self.title) + + def short(self): + return u"%s - %s\n%s" % (self.creator, self.title, self.created.strftime("%b %d, %I:%M %p")) + short.allow_tags = True + +### Admin + +class ForumAdmin(admin.ModelAdmin): + pass + +class ThreadAdmin(admin.ModelAdmin): + list_display = ["title", "forum", "creator", "created"] + list_filter = ["forum", "creator"] + +class PostAdmin(admin.ModelAdmin): + search_fields = ["title", "creator"] + list_display = ["title", "thread", "creator", "created"] + +admin.site.register(Forum, ForumAdmin) +admin.site.register(Thread, ThreadAdmin) +admin.site.register(Post, PostAdmin) diff --git a/tests/python/multiline.py b/tests/python/multiline.py new file mode 100644 index 0000000..f27bd9f --- /dev/null +++ b/tests/python/multiline.py @@ -0,0 +1,3 @@ +def foo(a, b, \ + c, d): + pass diff --git a/tests/python/tabindent.py b/tests/python/tabindent.py new file mode 100644 index 0000000..6f3f9d9 --- /dev/null +++ b/tests/python/tabindent.py @@ -0,0 +1,3 @@ +def find_heading(self, position=0, direction=Direction.FORWARD, \ + heading=Heading, connect_with_document=True): + pass diff --git a/tests/python/test.py b/tests/python/test.py new file mode 100644 index 0000000..41f6fa2 --- /dev/null +++ b/tests/python/test.py @@ -0,0 +1,23 @@ +import foo +import bar + +defaultdict(lambda: 0) +classofhello = 0 + +class test(foo, bar): + class Inner: +# pass + def foo(self): + print "Inner" +# pass + +def test(): + class Inner2: + def bar(self): + print "Inner2" +# t = Inner2() +# pass + +#print "Test" +#r = test.Inner2() +#test() diff --git a/tests/python/test2.py b/tests/python/test2.py new file mode 100644 index 0000000..83f9c7c --- /dev/null +++ b/tests/python/test2.py @@ -0,0 +1,3 @@ +from test import * + +r = test.Inner() diff --git a/tests/python/vis.py b/tests/python/vis.py new file mode 100755 index 0000000..4e8f82a --- /dev/null +++ b/tests/python/vis.py @@ -0,0 +1,225 @@ +#!/usr/bin/env python2.6 + +from __future__ import division + +import gtk + +import numpy as np +import matplotlib +matplotlib.use('GTKAgg') +import matplotlib.cm as cm +import matplotlib.pyplot as plt +from matplotlib.widgets import Button, RadioButtons +from matplotlib.backends.backend_gtkagg import FigureCanvasGTKAgg as FigureCanvas + +win = gtk.Window() +win.connect("destroy", gtk.main_quit) +win.set_default_size(600,600) +win.set_title("Resource Visualisation") + +fig = plt.figure(figsize=(8,8)) +ax = fig.add_axes([0.1, 0.2, 0.8, 0.75], projection='polar') + +rax = fig.add_axes([0.7, 0.05, 0.2, 0.05]) +rax.grid(False) +rax.set_xticks([]) +rax.set_yticks([]) + +moax = fig.add_axes([0.1, 0.02, 0.25, 0.1]) +moax.grid(False) +moax.set_xticks([]) +moax.set_yticks([]) + +logax = fig.add_axes([0.4, 0.02, 0.25, 0.1]) +logax.grid(False) +logax.set_xticks([]) +logax.set_yticks([]) + +canvas = FigureCanvas(fig) +win.add(canvas) + +class ResVis(object): + def __init__(self): + self.dirdata = self.load_data('dirs.dat') + self.userdata = self.load_data('users.dat') + + self.mode = 'dir' + self.log = True + self.draw_dir('root') + + def load_data(self, filename): + data = {} + for line in open(filename, 'r').readlines(): + entry = line.split(None, 3) + + if len(entry) > 3: # current entry has subentries + entry[3] = entry[3].split() + entry[3].sort(key=str.lower) + else: + entry.append([]) + + data[entry[0]] = [entry[0], float(entry[1]), float(entry[2]), + entry[3]] + return data + + def load_dir(self, dirname): + curdirdata = [] + for d in self.dirdata[dirname][3]: + curdirdata.append(self.dirdata[d]) + return curdirdata + + def load_user(self, username): + curdata = [] + for u in self.userdata[username][3]: + curdata.append(self.userdata[u]) + return curdata + + def draw_dir(self, dirname='root'): + self.curdir = dirname + self.reset_ax() + ax.set_title('Directory size') + ax.set_xlabel(dirname, weight='bold') + + curdir = self.load_dir(dirname) + self.draw_data(curdir) + + def draw_user(self, username='root'): + self.curuser = username + self.reset_ax() + ax.set_title('Resource usage') + ax.set_xlabel(username, weight='bold') + + user = self.load_user(username) + self.draw_data(user) + + def reset_ax(self): + ax.cla() + + #ax.axis('off') + #ax.set_axis_off() + #ax.set_yscale('log') + ax.grid(False) + ax.set_xticks([]) # edge + ax.set_yticks([]) # radius + #ax.set_xlabel('Size') + #ax.set_ylabel('Number of files') + + def draw_data(self, data): + totalsize = sum(zip(*data)[1]) # get sum of subentry sizes + unit = 1.5 * np.pi / totalsize + + angle = 0.5 * np.pi + if self.log: + maxy = max(map(np.log2, zip(*data)[2])) + else: + maxy = max(zip(*data)[2]) + for d in data: + relangle = unit * d[1] + + if len(d[3]) > 0 or self.mode == 'user': + # scale colours since the legend occupies a quarter + scaledangle = (angle - 0.5*np.pi) * (2 / 1.5) + colour = cm.hsv(scaledangle/(2*np.pi)) + else: +# colour = cm.Greys(scaledangle/(2*np.pi)) + colour = "#999999" + + if self.log: + # take logarithm to accomodate for big differences + y = np.log2(d[2]) + else: + y = d[2] + + bar = ax.bar(angle, y, width=relangle, bottom=maxy*0.2, + color=colour, label=d[0], + picker=True) + angle += relangle + + if self.mode == 'dir': + desc = '{0}\n{1}G'.format(d[0], d[1]) + elif self.mode == 'user': + desc = '{0}\n{1}%'.format(d[0], d[1]) + self.draw_desc(bar[0], d, desc) + + self.draw_legend(maxy) + + fig.canvas.draw() + + def draw_desc(self, bar, data, text): + # show description in center of bar + bbox = bar.get_bbox() + x = bbox.xmin + (bbox.xmax - bbox.xmin) / 2 + y = bbox.ymin + (bbox.ymax - bbox.ymin) / 2 + ax.text(x, y, text, horizontalalignment='center', + verticalalignment='center', weight='bold') + + def draw_legend(self, maxy): + ax.annotate('', xy=(0, maxy*0.3), xytext=(0.5*np.pi, maxy*0.3), + arrowprops=dict( + arrowstyle='<->', + connectionstyle='angle3,angleA=0,angleB=-90', + linewidth=3)) + ax.annotate('', xy=(0.04*np.pi, maxy*0.35), xytext=(0.04*np.pi, maxy), + arrowprops=dict( + arrowstyle='<->', + connectionstyle='arc3', + linewidth=3)) + + if self.mode == 'dir': + xtext = 'Size' + ytext = 'Number of files' + elif self.mode == 'user': + xtext = 'Processor usage' + ytext = 'Memory usage' + + ax.text(0.3*np.pi, maxy*0.35, xtext, weight='normal') + ax.text(0.06*np.pi, maxy*0.4, ytext, weight='normal', rotation=8) + + if self.mode == 'dir': + ax.text(0.3*np.pi, maxy*0.6, 'Grey dirs do not\nhave subdirs.') + + def on_pick(self, event): + clicked = event.artist.get_label() + + if self.mode == 'dir' and len(self.dirdata[clicked][3]) > 0: + self.draw_dir(clicked) + elif self.mode == 'user' and len(self.userdata[clicked][3]) > 0: + self.draw_user(clicked) + + def on_rootclick(self, event): + if self.mode == 'dir': + self.draw_dir('root') + elif self.mode == 'user': + self.draw_user('root') + + def on_modeclick(self, mode): + if mode == 'Directory size': + self.mode = 'dir' + self.draw_dir('root') + elif mode == 'Resource usage': + self.mode = 'user' + self.draw_user('root') + + def on_logclick(self, mode): + self.log = mode == 'Logarithmic' + if self.mode == 'dir': + self.draw_dir(self.curdir) + if self.mode == 'user': + self.draw_user(self.curuser) + +vis = ResVis() + +root = Button(rax, 'Home') +root.on_clicked(vis.on_rootclick) +mode = RadioButtons(moax, ('Directory size', 'Resource usage')) +mode.on_clicked(vis.on_modeclick) +log = RadioButtons(logax, ('Logarithmic', 'Linear')) +log.on_clicked(vis.on_logclick) + +fig.canvas.mpl_connect('pick_event', vis.on_pick) + +#plt.show() + +win.show_all() +gtk.main() + diff --git a/tests/ruby/cache_key.rb b/tests/ruby/cache_key.rb new file mode 100644 index 0000000..3bd3c21 --- /dev/null +++ b/tests/ruby/cache_key.rb @@ -0,0 +1,10 @@ +class Class2 + def self.fun1() + value=if foo + bar + end + end + + def self.fun2() + end +end diff --git a/tests/ruby/singleton.rb b/tests/ruby/singleton.rb new file mode 100644 index 0000000..bb454ee --- /dev/null +++ b/tests/ruby/singleton.rb @@ -0,0 +1,13 @@ +class Foo + def a + end + + def b + end + + def Foo.c(item) + end + + def self.d + end +end diff --git a/tests/ruby/testcase.rb b/tests/ruby/testcase.rb new file mode 100644 index 0000000..5a1c2b2 --- /dev/null +++ b/tests/ruby/testcase.rb @@ -0,0 +1,10 @@ +class Class1 + def fun1() + foo = if bar + baz + end + end + + def fun2() + end +end diff --git a/tests/sql/backup.sql b/tests/sql/backup.sql new file mode 100644 index 0000000..ff8e371 --- /dev/null +++ b/tests/sql/backup.sql @@ -0,0 +1,27 @@ +DECLARE @name VARCHAR(50) -- database name +DECLARE @path VARCHAR(256) -- path for backup files +DECLARE @fileName VARCHAR(256) -- filename for backup +DECLARE @fileDate VARCHAR(20) -- used for file name + +SET @path = 'C:\Backup\' + +SELECT @fileDate = CONVERT(VARCHAR(20),GETDATE(),112) + +DECLARE db_cursor CURSOR FOR +SELECT name +FROM master.dbo.sysdatabases +WHERE name NOT IN ('master','model','msdb','tempdb') + +OPEN db_cursor +FETCH NEXT FROM db_cursor INTO @name + +WHILE @@FETCH_STATUS = 0 +BEGIN + SET @fileName = @path + @name + '_' + @fileDate + '.BAK' + BACKUP DATABASE @name TO DISK = @fileName + + FETCH NEXT FROM db_cursor INTO @name +END + +CLOSE db_cursor +DEALLOCATE db_cursor diff --git a/tests/sql/ex.sql b/tests/sql/ex.sql new file mode 100644 index 0000000..26ff4a2 --- /dev/null +++ b/tests/sql/ex.sql @@ -0,0 +1,17 @@ +select user_id, count(*) as how_many +from bboard +where not exists (select 1 from + bboard_authorized_maintainers bam + where bam.user_id = bboard.user_id) +and posting_time + 60 > sysdate +group by user_id +order by how_many desc; + +select user_id, count(*) as how_many +from bboard +where posting_time + 60 > sysdate +group by user_id +having not exists (select 1 from + bboard_authorized_maintainers bam + where bam.user_id = bboard.user_id) +order by how_many desc; diff --git a/tests/tex/nonascii.c b/tests/tex/nonascii.c new file mode 100644 index 0000000..aed03e1 --- /dev/null +++ b/tests/tex/nonascii.c @@ -0,0 +1,4 @@ +int Plodenie = 0; +char *Klíčenie = "blah"; +short Zrenie; +long Žatva; diff --git a/tests/tex/nonascii.tex b/tests/tex/nonascii.tex new file mode 100644 index 0000000..d0cabe2 --- /dev/null +++ b/tests/tex/nonascii.tex @@ -0,0 +1,24 @@ +\documentclass{book} + +\usepackage[slovak]{babel} +\usepackage[utf8]{inputenc} + +\begin{document} + +\chapter{Plodenie} +Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod +tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, + +\chapter{Klíčenie} +Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod +tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, + +\chapter{Zrenie} +Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod +tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, + +\chapter{Žatva} +Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod +tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, + +\end{document} diff --git a/tests/tex/outputtest.txt b/tests/tex/outputtest.txt new file mode 100644 index 0000000..5c776f6 --- /dev/null +++ b/tests/tex/outputtest.txt @@ -0,0 +1,6 @@ + +ctags-exuberant: Warning: Unsupported parameter 'l' for --tex-kinds option +Kl enie /tmp/vPIhyz9/3.tex /^\\chapter{Klíčenie}$/;" c line:12 +Plodenie /tmp/vPIhyz9/3.tex /^\\chapter{Plodenie}$/;" c line:8 +Zrenie /tmp/vPIhyz9/3.tex /^\\chapter{Zrenie}$/;" c line:16 +atva /tmp/vPIhyz9/3.tex /^\\chapter{Žatva}$/;" c line:20 diff --git a/tests/tex/test.txt b/tests/tex/test.txt new file mode 100644 index 0000000..f448889 --- /dev/null +++ b/tests/tex/test.txt @@ -0,0 +1,4 @@ +CHAP Plodenie nonascii.tex /^\\chapter{Plodenie}$/;" s line:8 +CHAP Klíčenie nonascii.tex /^\\chapter{Klíčenie}$/;" s line:12 +CHAP Zrenie nonascii.tex /^\\chapter{Zrenie}$/;" s line:16 +CHAP Žatva nonascii.tex /^\\chapter{Žatva}$/;" s line:20 diff --git a/tests/vala/closures.vala b/tests/vala/closures.vala new file mode 100644 index 0000000..d2be8e0 --- /dev/null +++ b/tests/vala/closures.vala @@ -0,0 +1,23 @@ +delegate int Func (); + +[CCode (has_target = false)] +delegate void NoTargetFunc (); + +int A (int k, Func x1, Func x2, Func x3, Func x4, Func x5) { + Func B = null; + B = () => { + k = k - 1; + return A (k, B, x1, x2, x3, x4); + }; + return k <= 0 ? x4 () + x5 () : B (); +} + +void B ([CCode (array_length = false, array_null_terminated = true)] int[] array, NoTargetFunc func) { + Func C = () => { array = null; func (); return 0; }; +} + +void main () { + int result = A (10, () => 1, () => -1, () => -1, () => 1, () => 0); + assert (result == -67); +} + diff --git a/tests/vala/generics.vala b/tests/vala/generics.vala new file mode 100644 index 0000000..b465523 --- /dev/null +++ b/tests/vala/generics.vala @@ -0,0 +1,22 @@ +interface Foo : Object { + public void foo (owned T bar) { + bar = null; + } +} + +class Baz : Object, Foo { +} + +void foo (owned T bar) { + bar = null; +} + +void main () { + var bar = new Object (); + foo (bar); + assert (bar.ref_count == 1); + + var baz = new Baz (); + baz.foo (bar); + assert (baz.ref_count == 1); +} diff --git a/tests/vala/gnomine.vala b/tests/vala/gnomine.vala new file mode 100644 index 0000000..1d4b6f8 --- /dev/null +++ b/tests/vala/gnomine.vala @@ -0,0 +1,874 @@ +public class GnoMine : Gtk.Application +{ + /* Settings keys */ + private Settings settings; + private const string KEY_XSIZE = "xsize"; + private const int XSIZE_MIN = 4; + private const int XSIZE_MAX = 100; + private const string KEY_YSIZE = "ysize"; + private const int YSIZE_MIN = 4; + private const int YSIZE_MAX = 100; + private const string KEY_NMINES = "nmines"; + private const string KEY_MODE = "mode"; + private const string KEY_USE_QUESTION_MARKS = "use-question-marks"; + private const string KEY_USE_OVERMINE_WARNING = "use-overmine-warning"; + private const string KEY_USE_AUTOFLAG = "use-autoflag"; + + /* Faces for new game button */ + private Gtk.ToolButton face_button; + private Gtk.Image win_face_image; + private Gtk.Image sad_face_image; + private Gtk.Image smile_face_image; + private Gtk.Image cool_face_image; + private Gtk.Image worried_face_image; + + /* Main window */ + private Gtk.Window window; + + /* Minefield being played */ + private Minefield minefield; + + /* Minefield widget */ + private MinefieldView minefield_view; + + private Gtk.Dialog? pref_dialog = null; + private Gtk.Label flag_label; + private Gtk.SpinButton n_mines_spin; + private GnomeGamesSupport.Clock clock; + private SimpleAction pause; + private SimpleAction hint; + private Gtk.Action hint_action; + private GnomeGamesSupport.FullscreenAction fullscreen_action; + private GnomeGamesSupport.PauseAction pause_action; + private Gtk.AspectFrame new_game_screen; + private Gtk.AspectFrame custom_game_screen; + private bool is_new_game_screen; + + private const GLib.ActionEntry[] action_entries = + { + { "new-game", new_game_cb }, + { "hint", hint_cb }, + { "pause", toggle_pause_cb }, + { "fullscreen", fullscreen_cb }, + { "scores", scores_cb }, + { "preferences", preferences_cb }, + { "quit", quit_cb }, + { "help", help_cb }, + { "about", about_cb } + }; + + private const GnomeGamesSupport.ScoresCategory scorecats[] = + { + {"Small", NC_("board size", "Small") }, + {"Medium", NC_("board size", "Medium") }, + {"Large", NC_("board size", "Large") }, + {"Custom", NC_("board size", "Custom") } + }; + + private GnomeGamesSupport.Scores highscores; + + public GnoMine () + { + Object (application_id: "org.gnome.gnomine", flags: ApplicationFlags.FLAGS_NONE); + } + + protected override void startup () + { + base.startup (); + + Environment.set_application_name (_("Mines")); + + settings = new Settings ("org.gnome.gnomine"); + + highscores = new GnomeGamesSupport.Scores ("gnomine", scorecats, "board size", null, 0 /* default category */, GnomeGamesSupport.ScoreStyle.TIME_ASCENDING); + + Gtk.Window.set_default_icon_name ("gnome-mines"); + + add_action_entries (action_entries, this); + hint = lookup_action ("hint") as SimpleAction; + hint.set_enabled (false); + pause = lookup_action ("pause") as SimpleAction; + pause.set_enabled (false); + + var builder = new Gtk.Builder (); + try + { + builder.add_from_file (Path.build_filename (DATA_DIRECTORY, "gnomine.ui")); + } + catch (Error e) + { + error ("Unable to build menus: %s", e.message); + } + set_app_menu (builder.get_object ("gnomine-menu") as MenuModel); + + window = new Gtk.ApplicationWindow (this); + window.title = _("Mines"); + + GnomeGamesSupport.settings_bind_window_state ("/org/gnome/gnomine/", window); + add_window (window); + + GnomeGamesSupport.stock_init (); + + var main_vbox = new Gtk.Box (Gtk.Orientation.VERTICAL, 0); + window.add (main_vbox); + main_vbox.show (); + + var action_group = new Gtk.ActionGroup ("group"); + action_group.set_translation_domain (GETTEXT_PACKAGE); + action_group.add_actions (actions, this); + + var ui_manager = new Gtk.UIManager (); + ui_manager.insert_action_group (action_group, 0); + try + { + ui_manager.add_ui_from_string (ui_description, -1); + } + catch (Error e) + { + } + hint_action = action_group.get_action ("Hint"); + hint_action.is_important = true; + hint_action.set_sensitive (false); + + action_group.get_action ("NewGame").is_important = true; + + fullscreen_action = new GnomeGamesSupport.FullscreenAction ("Fullscreen", window); + action_group.add_action_with_accel (fullscreen_action, null); + + pause_action = new GnomeGamesSupport.PauseAction ("PauseGame"); + pause_action.set_sensitive (false); + pause_action.state_changed.connect (pause_cb); + action_group.add_action_with_accel (pause_action, null); + + window.add_accel_group (ui_manager.get_accel_group ()); + + var status_box = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 10); + status_box.show (); + + /* show the numbers of total and remaining mines */ + flag_label = new Gtk.Label (""); + flag_label.show (); + + status_box.pack_start (flag_label, false, false, 0); + + /* game clock */ + var box = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 0); + box.show (); + status_box.pack_start (box, false, false, 0); + + var label = new Gtk.Label (_("Time: ")); + box.pack_start (label, false, false, 0); + label.show (); + + clock = new GnomeGamesSupport.Clock (); + clock.show (); + box.pack_start (clock, false, false, 0); + + var status_alignment = new Gtk.Alignment (1.0f, 0.5f, 0.0f, 0.0f); + status_alignment.add (status_box); + status_alignment.show(); + + var status_item = new Gtk.ToolItem (); + status_item.set_expand (true); + status_item.add (status_alignment); + status_item.show(); + + /* create fancy faces */ + win_face_image = load_face_image ("face-win.svg"); + sad_face_image = load_face_image ("face-sad.svg"); + smile_face_image = load_face_image ("face-smile.svg"); + cool_face_image = load_face_image ("face-cool.svg"); + worried_face_image = load_face_image ("face-worried.svg"); + + /* initialize toolbar */ + var toolbar = (Gtk.Toolbar) ui_manager.get_widget ("/Toolbar"); + toolbar.show_arrow = false; + face_button = (Gtk.ToolButton) ui_manager.get_widget ("/Toolbar/NewGame"); + toolbar.get_style_context ().add_class (Gtk.STYLE_CLASS_PRIMARY_TOOLBAR); + /* replace the dull new-game icon with fancy faces */ + set_face_image (smile_face_image); + + toolbar.insert (status_item, -1); + main_vbox.pack_start (toolbar, false, false, 0); + + var view_box = new Gtk.Box (Gtk.Orientation.VERTICAL, 0); + view_box.border_width = 3; + view_box.show (); + main_vbox.pack_start (view_box, true, true, 0); + + minefield_view = new MinefieldView (); + minefield_view.set_use_question_marks (settings.get_boolean (KEY_USE_QUESTION_MARKS)); + minefield_view.set_use_overmine_warning (settings.get_boolean (KEY_USE_OVERMINE_WARNING)); + minefield_view.set_use_autoflag (settings.get_boolean (KEY_USE_AUTOFLAG)); + minefield_view.button_press_event.connect (view_button_press_event); + minefield_view.look.connect (look_cb); + minefield_view.unlook.connect (unlook_cb); + view_box.pack_start (minefield_view, true, true, 0); + + /* New game screen */ + new_game_screen = new Gtk.AspectFrame (_("Field Size"), 0.5f, 0.5f, 1.0f, false); + new_game_screen.set_shadow_type (Gtk.ShadowType.NONE); + new_game_screen.set_size_request(200, 200); + + var new_game_table = new Gtk.Table (2, 2, true); + new_game_screen.add (new_game_table); + + var button_small = new Gtk.Button (); + new_game_table.attach_defaults (button_small, 0, 1, 0, 1); + button_small.clicked.connect (small_size_clicked_cb); + + label = new Gtk.Label (null); + label.set_markup (make_minefield_description ("#0000ff", 8, 8, 10)); + label.set_justify (Gtk.Justification.CENTER); + button_small.add (label); + + var button_medium = new Gtk.Button (); + new_game_table.attach_defaults (button_medium, 1, 2, 0, 1); + button_medium.clicked.connect (medium_size_clicked_cb); + + label = new Gtk.Label (null); + label.set_markup (make_minefield_description ("#00a000", 16, 16, 40)); + label.set_justify (Gtk.Justification.CENTER); + button_medium.add (label); + + var button_large = new Gtk.Button (); + new_game_table.attach_defaults (button_large, 0, 1, 1, 2); + button_large.clicked.connect (large_size_clicked_cb); + + label = new Gtk.Label (null); + label.set_markup (make_minefield_description ("#ff0000", 30, 16, 99)); + label.set_justify (Gtk.Justification.CENTER); + button_large.add (label); + + var button_custom = new Gtk.Button (); + new_game_table.attach_defaults (button_custom, 1, 2, 1, 2); + button_custom.clicked.connect (show_custom_game_screen); + + label = new Gtk.Label (null); + label.set_markup_with_mnemonic ("?\n" + dpgettext2 (null, "board size", "_Custom") + ""); + label.set_justify (Gtk.Justification.CENTER); + button_custom.add (label); + + new_game_screen.show_all (); + view_box.pack_start (new_game_screen, true, true, 0); + + /* Custom game screen */ + custom_game_screen = new Gtk.AspectFrame ("", 0.5f, 0.5f, 0.0f, true); + custom_game_screen.set_shadow_type (Gtk.ShadowType.NONE); + + var custom_game_frame = new GnomeGamesSupport.Frame (_("Custom Size")); + custom_game_screen.add (custom_game_frame); + + var custom_field_grid = new Gtk.Grid (); + custom_field_grid.set_row_spacing (6); + custom_field_grid.set_column_spacing (12); + custom_game_frame.add (custom_field_grid); + + label = new Gtk.Label.with_mnemonic (_("H_orizontal:")); + label.set_alignment (0, 0.5f); + custom_field_grid.attach (label, 0, 0, 1, 1); + + var field_width_entry = new Gtk.SpinButton.with_range (XSIZE_MIN, XSIZE_MAX, 1); + field_width_entry.value_changed.connect (xsize_spin_cb); + field_width_entry.set_value (settings.get_int (KEY_XSIZE)); + custom_field_grid.attach (field_width_entry, 1, 0, 1, 1); + label.set_mnemonic_widget (field_width_entry); + + label = new Gtk.Label.with_mnemonic (_("_Vertical:")); + label.set_alignment (0, 0.5f); + custom_field_grid.attach (label, 0, 1, 1, 1); + + var field_height_entry = new Gtk.SpinButton.with_range (YSIZE_MIN, YSIZE_MAX, 1); + field_height_entry.value_changed.connect (ysize_spin_cb); + field_height_entry.set_value (settings.get_int (KEY_YSIZE)); + custom_field_grid.attach (field_height_entry, 1, 1, 1, 1); + label.set_mnemonic_widget (field_height_entry); + + label = new Gtk.Label.with_mnemonic (_("_Number of mines:")); + label.set_alignment (0, 0.5f); + custom_field_grid.attach (label, 0, 2, 1, 1); + + n_mines_spin = new Gtk.SpinButton.with_range (1, XSIZE_MAX * YSIZE_MAX, 1); + n_mines_spin.value_changed.connect (n_mines_spin_cb); + n_mines_spin.set_value (settings.get_int (KEY_NMINES)); + custom_field_grid.attach (n_mines_spin, 1, 2, 1, 1); + + set_n_mines_limit (); + label.set_mnemonic_widget (n_mines_spin); + + var hbox = new Gtk.HBox (false, 5); + custom_field_grid.attach (hbox, 0, 3, 2, 1); + + var button_back = new Gtk.Button.from_stock (Gtk.Stock.CANCEL); + button_back.clicked.connect (show_new_game_screen); + hbox.pack_start (button_back, true, true); + + button_custom = new Gtk.Button.with_mnemonic ("_Play Game"); + button_custom.set_image (new Gtk.Image.from_stock (Gtk.Stock.GO_FORWARD, Gtk.IconSize.BUTTON)); + button_custom.clicked.connect (custom_size_clicked_cb); + hbox.pack_start (button_custom, true, true); + + custom_game_screen.show_all (); + custom_game_screen.hide (); + view_box.pack_start (custom_game_screen, true, false); + } + + private string make_minefield_description (string color, int width, int height, int n_mines) + { + var size_label = "%d × %d".printf (width, height); + var mines_label = ngettext ("%d mine", "%d mines", n_mines).printf (n_mines); + return "%s\n%s".printf (color, size_label, mines_label); + } + + private const Gtk.ActionEntry actions[] = + { + {"NewGame", GnomeGamesSupport.STOCK_NEW_GAME, null, null, N_("Start a new game"), new_game_cb}, + {"Hint", GnomeGamesSupport.STOCK_HINT, null, null, N_("Show a hint"), hint_cb} + }; + + private const string ui_description = + "" + + " " + + " " + + " " + + " " + + " " + + " " + + ""; + + public void start () + { + window.show (); + show_new_game_screen (); + set_face_image (smile_face_image); + } + + public override void activate () + { + window.show (); + } + + private Gtk.Image load_face_image (string name) + { + var image = new Gtk.Image (); + var filename = Path.build_filename (DATA_DIRECTORY, name); + + if (filename != null) + image.set_from_file (filename); + + image.show (); + + return image; + } + + private void set_face_image (Gtk.Image face_image) + { + face_button.set_icon_widget (face_image); + } + + private bool view_button_press_event (Gtk.Widget widget, Gdk.EventButton event) + { + /* Cancel pause on click */ + if (pause_action.get_is_paused ()) + { + pause_action.set_is_paused (false); + return true; + } + + return false; + } + + private void quit_cb () + { + window.destroy (); + } + + private void update_flag_label () + { + flag_label.set_text ("Flags: %u/%u".printf (minefield.n_flags, minefield.n_mines)); + } + + /* Show the high scores dialog - creating it if necessary. If pos is + * greater than 0 the appropriate score is highlighted. If the score isn't + * a high score and this isn't a direct request to see the scores, we + * only show a simple dialog. */ + private int show_scores (int pos, bool endofgame) + { + if (endofgame && (pos <= 0)) + { + var dialog = new Gtk.MessageDialog.with_markup (window, + Gtk.DialogFlags.DESTROY_WITH_PARENT, + Gtk.MessageType.INFO, + Gtk.ButtonsType.NONE, + "%s\n%s", + _("The Mines Have Been Cleared!"), + _("Great work, but unfortunately your score did not make the top ten.")); + dialog.add_buttons (Gtk.Stock.QUIT, Gtk.ResponseType.REJECT, + _("_New Game"), Gtk.ResponseType.ACCEPT, null); + dialog.set_default_response (Gtk.ResponseType.ACCEPT); + dialog.set_title (""); + var result = dialog.run (); + dialog.destroy (); + return result; + } + else + { + var dialog = new GnomeGamesSupport.ScoresDialog (window, highscores, _("Mines Scores")); + dialog.set_category_description (_("Size:")); + + if (pos > 0) + { + dialog.set_hilight (pos); + var message = "%s\n\n%s".printf (_("Congratulations!"), pos == 1 ? _("Your score is the best!") : _("Your score has made the top ten.")); + dialog.set_message (message); + } + else + dialog.set_message (null); + + if (endofgame) + dialog.set_buttons (GnomeGamesSupport.ScoresButtons.QUIT_BUTTON | GnomeGamesSupport.ScoresButtons.NEW_GAME_BUTTON); + else + dialog.set_buttons (0); + var result = dialog.run (); + dialog.destroy (); + return result; + } + } + + private void fullscreen_cb () + { + fullscreen_action.set_is_fullscreen (!fullscreen_action.get_is_fullscreen ()); + } + + private void scores_cb () + { + show_scores (0, false); + } + + private void show_custom_game_screen () + { + is_new_game_screen = false; + custom_game_screen.show (); + minefield_view.hide (); + new_game_screen.hide (); + } + + private void show_new_game_screen () + { + if (is_new_game_screen) + return; + + if (minefield != null && minefield.n_cleared > 0 && !minefield.exploded && !minefield.is_complete) + { + var dialog = new Gtk.MessageDialog (window, Gtk.DialogFlags.MODAL, Gtk.MessageType.QUESTION, Gtk.ButtonsType.NONE, "%s", _("Cancel current game?")); + dialog.add_buttons (_("Start New Game"), Gtk.ResponseType.ACCEPT, + _("Keep Current Game"), Gtk.ResponseType.REJECT, + null); + var result = dialog.run (); + dialog.destroy (); + if (result == Gtk.ResponseType.REJECT) + return; + } + + minefield = null; + + is_new_game_screen = true; + custom_game_screen.hide (); + minefield_view.hide (); + new_game_screen.show (); + flag_label.set_text(""); + clock.stop (); + clock.reset (); + set_face_image (smile_face_image); + + hint.set_enabled (false); + hint_action.set_sensitive (false); + pause.set_enabled (false); + pause_action.set_sensitive (false); + + minefield_view.paused = false; + pause_action.set_is_paused (false); + } + + private void new_game () + { + is_new_game_screen = false; + custom_game_screen.hide (); + minefield_view.show (); + new_game_screen.hide (); + + clock.reset (); + set_face_image (smile_face_image); + + int x, y, n; + var score_key = ""; + switch (settings.get_int (KEY_MODE)) + { + case 0: + x = 8; + y = 8; + n = 10; + score_key = "Small"; + break; + case 1: + x = 16; + y = 16; + n = 40; + score_key = "Medium"; + break; + case 2: + x = 30; + y = 16; + n = 99; + score_key = "Large"; + break; + default: + case 3: + x = settings.get_int (KEY_XSIZE).clamp (XSIZE_MIN, XSIZE_MAX); + y = settings.get_int (KEY_YSIZE).clamp (YSIZE_MIN, YSIZE_MAX); + n = settings.get_int (KEY_NMINES).clamp (1, x * y - 10); + score_key = "Custom"; + break; + } + + highscores.set_category (score_key); + if (minefield != null) + SignalHandler.disconnect_by_func (minefield, null, this); + minefield = new Minefield (x, y, n); + minefield.marks_changed.connect (marks_changed_cb); + minefield.explode.connect (explode_cb); + minefield.cleared.connect (cleared_cb); + + minefield_view.minefield = minefield; + + update_flag_label (); + + hint.set_enabled (true); + hint_action.set_sensitive (true); + pause.set_enabled (true); + pause_action.set_sensitive (true); + + minefield_view.paused = false; + pause_action.set_is_paused (false); + } + + private void hint_cb () + { + uint x, y; + minefield.hint (out x, out y); + + /* There is a ten second penalty for accepting a hint. */ + minefield.clear_mine (x, y); + clock.add_seconds (10); + } + + private void new_game_cb () + { + if (is_new_game_screen) + new_game (); + else + show_new_game_screen (); + } + + private void toggle_pause_cb () + { + pause_action.set_is_paused (!pause_action.get_is_paused ()); + } + + private void pause_cb () + { + if (pause_action.get_is_paused ()) + { + minefield_view.paused = true; + hint.set_enabled (false); + hint_action.set_sensitive (false); + clock.stop (); + } + else + { + minefield_view.paused = false; + hint.set_enabled (true); + hint_action.set_sensitive (true); + clock.start (); + } + } + + private void marks_changed_cb (Minefield minefield) + { + update_flag_label (); + clock.start (); + } + + private void explode_cb (Minefield minefield) + { + set_face_image (sad_face_image); + hint.set_enabled (false); + hint_action.set_sensitive (false); + pause.set_enabled (false); + pause_action.set_sensitive (false); + clock.stop (); + } + + private void cleared_cb (Minefield minefield) + { + clock.stop (); + + set_face_image (win_face_image); + + var seconds = clock.get_seconds (); + var pos = highscores.add_time_score ((float) (seconds / 60) + (float) (seconds % 60) / 100); + + if (show_scores (pos, true) == Gtk.ResponseType.REJECT) + window.destroy (); + else + show_new_game_screen (); + } + + private void look_cb (MinefieldView minefield_view) + { + set_face_image (worried_face_image); + clock.start (); + } + + private void unlook_cb (MinefieldView minefield_view) + { + set_face_image (cool_face_image); + } + + private void about_cb () + { + string[] authors = + { + _("Main game:"), + "Pista", + "Szekeres Istvan", + "Robert Ancell", + "", + _("Score:"), + "Horacio J. Pe\xc3\xb1a", + "", + _("Resizing and SVG support:"), + "Steve Chaplin", + "Callum McKenzie", + null + }; + + string[] artists = + { + _("Faces:"), + "tigert", + "Lapo Calamandrei and Ulisse Perusin", + "", + _("Graphics:"), + "Richard Hoelscher", + null + }; + + string[] documenters = + { + "Callum McKenzie", + null + }; + + Gtk.show_about_dialog (window, + "name", _("Mines"), + "version", VERSION, + "comments", + _("The popular logic puzzle minesweeper. Clear mines from a board using hints from squares you have already uncovered.\n\nMines is a part of GNOME Games."), + "copyright", + "Copyright \xc2\xa9 1997-2008 Free Software Foundation, Inc.", + "license", GnomeGamesSupport.get_license (_("Mines")), + "authors", authors, + "artists", artists, + "documenters", documenters, + "translator-credits", _("translator-credits"), + "logo-icon-name", "gnomine", "website", + "http://www.gnome.org/projects/gnome-games/", + "website-label", _("GNOME Games web site"), + "wrap-license", true, null); + } + + private void set_n_mines_limit () + { + /* Fix up the maximum number of mines so that there is always at least + * ten free spaces. Nine are so we can clear at least the immediate + * eight neighbours at the start and one more so the game isn't over + * immediately. */ + var max_mines = settings.get_int (KEY_XSIZE) * settings.get_int (KEY_YSIZE) - 10; + if (settings.get_int (KEY_NMINES) > max_mines) + { + settings.set_int (KEY_NMINES, max_mines); + n_mines_spin.set_value (max_mines); + } + n_mines_spin.set_range (1, max_mines); + } + + private void xsize_spin_cb (Gtk.SpinButton spin) + { + var xsize = spin.get_value_as_int (); + if (xsize == settings.get_int (KEY_XSIZE)) + return; + + settings.set_int (KEY_XSIZE, xsize); + set_n_mines_limit (); + } + + private void ysize_spin_cb (Gtk.SpinButton spin) + { + var ysize = spin.get_value_as_int (); + if (ysize == settings.get_int (KEY_YSIZE)) + return; + + settings.set_int (KEY_YSIZE, ysize); + set_n_mines_limit (); + } + + private void n_mines_spin_cb (Gtk.SpinButton spin) + { + var n_mines = spin.get_value_as_int (); + if (n_mines == settings.get_int (KEY_NMINES)) + return; + + settings.set_int (KEY_NMINES, n_mines); + } + + private void use_question_toggle_cb (Gtk.ToggleButton button) + { + var use_question_marks = button.get_active (); + settings.set_boolean (KEY_USE_QUESTION_MARKS, use_question_marks); + minefield_view.set_use_question_marks (use_question_marks); + } + + private void use_overmine_toggle_cb (Gtk.ToggleButton button) + { + var use_overmine_warning = button.get_active (); + settings.set_boolean (KEY_USE_OVERMINE_WARNING, use_overmine_warning); + minefield_view.set_use_overmine_warning (use_overmine_warning); + } + + private Gtk.Dialog create_preferences () + { + var vbox = new Gtk.VBox (false, 5); + + var frame = new GnomeGamesSupport.Frame (_("Flags")); + vbox.pack_start (frame, false, false); + + var flag_options_vbox = new Gtk.Box (Gtk.Orientation.VERTICAL, 6); + flag_options_vbox.show (); + frame.add (flag_options_vbox); + + var question_toggle = new Gtk.CheckButton.with_mnemonic (_("_Use \"I'm not sure\" flags")); + question_toggle.toggled.connect (use_question_toggle_cb); + question_toggle.set_active (settings.get_boolean (KEY_USE_QUESTION_MARKS)); + flag_options_vbox.pack_start (question_toggle, false, true, 0); + + var overmine_toggle = new Gtk.CheckButton.with_mnemonic (_("_Warn if too many flags placed")); + overmine_toggle.toggled.connect (use_overmine_toggle_cb); + overmine_toggle.set_active (settings.get_boolean (KEY_USE_OVERMINE_WARNING)); + flag_options_vbox.pack_start (overmine_toggle, false, true, 0); + + var dialog = new Gtk.Dialog.with_buttons (_("Mines Preferences"), + window, + 0, + Gtk.Stock.CLOSE, + Gtk.ResponseType.CLOSE, null); + dialog.set_border_width (5); + dialog.set_resizable (false); + var box = (Gtk.Box) dialog.get_content_area (); + box.set_spacing (2); + box.pack_start (vbox, false, false, 0); + + dialog.response.connect (pref_response_cb); + dialog.delete_event.connect (pref_delete_event_cb); + + vbox.show_all (); + + return dialog; + } + + private void set_mode (int mode) + { + if (mode != settings.get_int (KEY_MODE)) + settings.set_int (KEY_MODE, mode); + + new_game (); + } + + private void small_size_clicked_cb () + { + set_mode (0); + } + + private void medium_size_clicked_cb () + { + set_mode (1); + } + + private void large_size_clicked_cb () + { + set_mode (2); + } + + private void custom_size_clicked_cb () + { + set_mode (3); + } + + private void pref_response_cb (Gtk.Dialog dialog, int response_id) + { + pref_dialog.hide (); + } + + private bool pref_delete_event_cb (Gtk.Widget widget, Gdk.EventAny event) + { + pref_dialog.hide (); + return true; + } + + private void preferences_cb () + { + if (pref_dialog == null) + pref_dialog = create_preferences (); + pref_dialog.present (); + } + + private void help_cb () + { + try + { + Gtk.show_uri (window.get_screen (), "help:gnomine", Gtk.get_current_event_time ()); + } + catch (Error e) + { + warning ("Failed to show help: %s", e.message); + } + } + + + public static int main (string[] args) + { + Intl.setlocale (LocaleCategory.ALL, ""); + Intl.bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR); + Intl.bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8"); + Intl.textdomain (GETTEXT_PACKAGE); + + GnomeGamesSupport.scores_startup (); + + var context = new OptionContext (""); + context.set_translation_domain (GETTEXT_PACKAGE); + context.add_group (Gtk.get_option_group (true)); + + try + { + context.parse (ref args); + } + catch (Error e) + { + stderr.printf ("%s\n", e.message); + return Posix.EXIT_FAILURE; + } + + var app = new GnoMine (); + return app.run (); + } +} diff --git a/tests/vala/hashset.vala b/tests/vala/hashset.vala new file mode 100644 index 0000000..01452c6 --- /dev/null +++ b/tests/vala/hashset.vala @@ -0,0 +1,206 @@ +/* hashset.vala + * + * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald + * Copyright (C) 1997-2000 GLib Team and others + * Copyright (C) 2007-2009 Jürg Billeter + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Author: + * Jürg Billeter + */ + +using GLib; + +/** + * Hashtable implementation of the Set interface. + */ +public class Vala.HashSet : Set { + public override int size { + get { return _nnodes; } + } + + public HashFunc hash_func { + set { _hash_func = value; } + } + + public EqualFunc equal_func { + set { _equal_func = value; } + } + + private int _array_size; + private int _nnodes; + private Node[] _nodes; + + // concurrent modification protection + private int _stamp = 0; + + private HashFunc _hash_func; + private EqualFunc _equal_func; + + private const int MIN_SIZE = 11; + private const int MAX_SIZE = 13845163; + + public HashSet (HashFunc hash_func = GLib.direct_hash, EqualFunc equal_func = GLib.direct_equal) { + this.hash_func = hash_func; + this.equal_func = equal_func; + _array_size = MIN_SIZE; + _nodes = new Node[_array_size]; + } + + private Node** lookup_node (G key) { + uint hash_value = _hash_func (key); + Node** node = &_nodes[hash_value % _array_size]; + while ((*node) != null && (hash_value != (*node)->key_hash || !_equal_func ((*node)->key, key))) { + node = &((*node)->next); + } + return node; + } + + public override bool contains (G key) { + Node** node = lookup_node (key); + return (*node != null); + } + + public override Type get_element_type () { + return typeof (G); + } + + public override Vala.Iterator iterator () { + return new Iterator (this); + } + + public override bool add (G key) { + Node** node = lookup_node (key); + if (*node != null) { + return false; + } else { + uint hash_value = _hash_func (key); + *node = new Node (key, hash_value); + _nnodes++; + resize (); + _stamp++; + return true; + } + } + + public override bool remove (G key) { + Node** node = lookup_node (key); + if (*node != null) { + Node next = (owned) (*node)->next; + + (*node)->key = null; + delete *node; + + *node = (owned) next; + + _nnodes--; + resize (); + _stamp++; + return true; + } + return false; + } + + public override void clear () { + for (int i = 0; i < _array_size; i++) { + Node node = (owned) _nodes[i]; + while (node != null) { + Node next = (owned) node.next; + node.key = null; + node = (owned) next; + } + } + _nnodes = 0; + resize (); + } + + private void resize () { + if ((_array_size >= 3 * _nnodes && _array_size >= MIN_SIZE) || + (3 * _array_size <= _nnodes && _array_size < MAX_SIZE)) { + int new_array_size = (int) SpacedPrimes.closest (_nnodes); + new_array_size = new_array_size.clamp (MIN_SIZE, MAX_SIZE); + + Node[] new_nodes = new Node[new_array_size]; + + for (int i = 0; i < _array_size; i++) { + Node node; + Node next = null; + for (node = (owned) _nodes[i]; node != null; node = (owned) next) { + next = (owned) node.next; + uint hash_val = node.key_hash % new_array_size; + node.next = (owned) new_nodes[hash_val]; + new_nodes[hash_val] = (owned) node; + } + } + _nodes = (owned) new_nodes; + _array_size = new_array_size; + } + } + + ~HashSet () { + clear (); + } + + [Compact] + private class Node { + public G key; + public Node next; + public uint key_hash; + + public Node (owned G k, uint hash) { + key = (owned) k; + key_hash = hash; + } + } + + private class Iterator : Vala.Iterator { + public HashSet set { + set { + _set = value; + _stamp = _set._stamp; + } + } + + private HashSet _set; + private int _index = -1; + private weak Node _node; + + // concurrent modification protection + private int _stamp = 0; + + public Iterator (HashSet set) { + this.set = set; + } + + public override bool next () { + if (_node != null) { + _node = _node.next; + } + while (_node == null && _index + 1 < _set._array_size) { + _index++; + _node = _set._nodes[_index]; + } + return (_node != null); + } + + public override G? get () { + assert (_stamp == _set._stamp); + assert (_node != null); + return _node.key; + } + } +} + diff --git a/tests/vala/namespaces.vala b/tests/vala/namespaces.vala new file mode 100644 index 0000000..b35db59 --- /dev/null +++ b/tests/vala/namespaces.vala @@ -0,0 +1,35 @@ +using Foo.Sub; + +public class GlobalTestClass { + public GlobalTestClass() { + } +} + +namespace Maman { + static int main () { + stdout.printf ("Namespace Test\n"); + + Bar.run (); + + new GlobalTestClass(); + + var obj = new ClassInNestedNamespace (); + + return 0; + } + + class Bar : Object { + public static void run () { + stdout.printf ("Class in Namespace Test\n"); + } + } +} + +public class Foo.Sub.ClassInNestedNamespace { +} + + +void main () { + Maman.main (); +} + diff --git a/tests/vala/structs.vala b/tests/vala/structs.vala new file mode 100644 index 0000000..543975a --- /dev/null +++ b/tests/vala/structs.vala @@ -0,0 +1,86 @@ +using GLib; + +struct SimpleStruct { + public int field; + public int array[10]; +} + +public struct PublicStruct { + public int field; +} + +struct StructWithPrivateField { + private int field; + + public void test () { + field = 1; + stdout.printf ("StructWithPrivateField: field = %d\n", field); + } +} + +struct StructWithCreationMethod { + public StructWithCreationMethod () { + stdout.printf ("StructWithCreationMethod\n"); + } + + public int field; +} + +struct StructWithNamedCreationMethod { + public StructWithNamedCreationMethod.named () { + stdout.printf ("StructWithNamedCreationMethod\n"); + } + + public int field; +} + +void test_in_parameter (SimpleStruct st) { + stdout.printf ("test_in_parameter: st.field = %d\n", st.field); +} + +void test_in_nullable_parameter (SimpleStruct? st) { + assert (st.field == 1); +} + +void test_ref_parameter (ref SimpleStruct st) { + stdout.printf ("test_ref_parameter: st.field = %d\n", st.field); + st.field++; + st.array[0] = 10; +} + +void test_out_parameter (out SimpleStruct st) { + st = SimpleStruct (); + st.field = 3; +} + +void main () { + stdout.printf ("Structs Test:\n"); + + stdout.printf ("new SimpleStruct ()\n"); + var simple_struct = SimpleStruct (); + stdout.printf ("new PublicStruct ()\n"); + var public_struct = PublicStruct (); + stdout.printf ("new StructWithCreationMethod ()\n"); + var struct_with_creation_method = StructWithCreationMethod (); + stdout.printf ("new StructWithNamedCreationMethod ()\n"); + var struct_with_named_creation_method = StructWithNamedCreationMethod.named (); + + stdout.printf ("new SimpleStruct () { field = 1 }\n"); + simple_struct = SimpleStruct () { field = 1 }; + stdout.printf ("simple_struct.field = %d\n", simple_struct.field); + + test_in_parameter (simple_struct); + test_in_parameter ({1}); + test_in_nullable_parameter (simple_struct); + test_ref_parameter (ref simple_struct); + stdout.printf ("after test_ref_parameter: st.field = %d\n", simple_struct.field); + assert (simple_struct.array[0] == 10); + test_out_parameter (out simple_struct); + stdout.printf ("after test_out_parameter: st.field = %d\n", simple_struct.field); + + var struct_with_private_field = StructWithPrivateField (); + struct_with_private_field.test (); + + stdout.printf (".\n"); +} + diff --git a/tests/vala/valacodewriter.vala b/tests/vala/valacodewriter.vala new file mode 100644 index 0000000..f79c2af --- /dev/null +++ b/tests/vala/valacodewriter.vala @@ -0,0 +1,1605 @@ +/* valacodewriter.vala + * + * Copyright (C) 2006-2010 Jürg Billeter + * Copyright (C) 2006-2008 Raffaele Sandrini + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Author: + * Jürg Billeter + * Raffaele Sandrini + */ + + +/** + * Code visitor generating Vala API file for the public interface. + */ +public class Vala.CodeWriter : CodeVisitor { + private CodeContext context; + + FileStream stream; + + int indent; + /* at begin of line */ + bool bol = true; + + Scope current_scope; + + CodeWriterType type; + + string? override_header = null; + string? header_to_override = null; + + public CodeWriter (CodeWriterType type = CodeWriterType.EXTERNAL) { + this.type = type; + } + + /** + * Allows overriding of a specific cheader in the output + * @param original orignal cheader to override + * @param replacement cheader to replace original with + */ + public void set_cheader_override (string original, string replacement) + { + header_to_override = original; + override_header = replacement; + } + + /** + * Writes the public interface of the specified code context into the + * specified file. + * + * @param context a code context + * @param filename a relative or absolute filename + */ + public void write_file (CodeContext context, string filename) { + var file_exists = FileUtils.test (filename, FileTest.EXISTS); + var temp_filename = filename + ".valatmp"; + this.context = context; + + if (file_exists) { + stream = FileStream.open (temp_filename, "w"); + } else { + stream = FileStream.open (filename, "w"); + } + + if (stream == null) { + Report.error (null, "unable to open `%s' for writing".printf (filename)); + return; + } + + var header = context.version_header ? + "/* %s generated by %s %s, do not modify. */".printf (Path.get_basename (filename), Environment.get_prgname (), Config.BUILD_VERSION) : + "/* %s generated by %s, do not modify. */".printf (Path.get_basename (filename), Environment.get_prgname ()); + write_string (header); + write_newline (); + write_newline (); + + current_scope = context.root.scope; + + context.accept (this); + + current_scope = null; + + stream = null; + + if (file_exists) { + var changed = true; + + try { + var old_file = new MappedFile (filename, false); + var new_file = new MappedFile (temp_filename, false); + var len = old_file.get_length (); + if (len == new_file.get_length ()) { + if (Memory.cmp (old_file.get_contents (), new_file.get_contents (), len) == 0) { + changed = false; + } + } + old_file = null; + new_file = null; + } catch (FileError e) { + // assume changed if mmap comparison doesn't work + } + + if (changed) { + FileUtils.rename (temp_filename, filename); + } else { + FileUtils.unlink (temp_filename); + } + } + + } + + public override void visit_using_directive (UsingDirective ns) { + if (type == CodeWriterType.FAST) { + write_string ("using %s;\n".printf (ns.namespace_symbol.name)); + } + } + + public override void visit_namespace (Namespace ns) { + if (ns.external_package) { + return; + } + + if (ns.name == null) { + ns.accept_children (this); + return; + } + + write_attributes (ns); + + write_indent (); + write_string ("namespace "); + write_identifier (ns.name); + write_begin_block (); + + current_scope = ns.scope; + + visit_sorted (ns.get_namespaces ()); + visit_sorted (ns.get_classes ()); + visit_sorted (ns.get_interfaces ()); + visit_sorted (ns.get_structs ()); + visit_sorted (ns.get_enums ()); + visit_sorted (ns.get_error_domains ()); + visit_sorted (ns.get_delegates ()); + visit_sorted (ns.get_fields ()); + visit_sorted (ns.get_constants ()); + visit_sorted (ns.get_methods ()); + + current_scope = current_scope.parent_scope; + + write_end_block (); + write_newline (); + } + + private string get_cheaders (Symbol sym) { + string cheaders = ""; + if (type != CodeWriterType.FAST && !sym.external_package) { + cheaders = sym.get_attribute_string ("CCode", "cheader_filename") ?? ""; + if (cheaders == "" && sym.parent_symbol != null && sym.parent_symbol != context.root) { + cheaders = get_cheaders (sym.parent_symbol); + } + if (cheaders == "" && sym.source_reference != null && !sym.external_package) { + cheaders = sym.source_reference.file.get_cinclude_filename (); + } + + if (header_to_override != null) { + cheaders = cheaders.replace (header_to_override, override_header).replace (",,", ","); + } + } + return cheaders; + } + + public override void visit_class (Class cl) { + if (cl.external_package) { + return; + } + + if (!check_accessibility (cl)) { + return; + } + + write_attributes (cl); + + write_indent (); + write_accessibility (cl); + if (cl.is_abstract) { + write_string ("abstract "); + } + write_string ("class "); + write_identifier (cl.name); + + write_type_parameters (cl.get_type_parameters ()); + + var base_types = cl.get_base_types (); + if (base_types.size > 0) { + write_string (" : "); + + bool first = true; + foreach (DataType base_type in base_types) { + if (!first) { + write_string (", "); + } else { + first = false; + } + write_type (base_type); + } + } + write_begin_block (); + + current_scope = cl.scope; + + visit_sorted (cl.get_classes ()); + visit_sorted (cl.get_structs ()); + visit_sorted (cl.get_enums ()); + visit_sorted (cl.get_delegates ()); + visit_sorted (cl.get_fields ()); + visit_sorted (cl.get_constants ()); + visit_sorted (cl.get_methods ()); + visit_sorted (cl.get_properties ()); + visit_sorted (cl.get_signals ()); + + if (cl.constructor != null) { + cl.constructor.accept (this); + } + + current_scope = current_scope.parent_scope; + + write_end_block (); + write_newline (); + } + + void visit_sorted (List symbols) { + if (type != CodeWriterType.EXTERNAL) { + // order of virtual methods matters for fast vapis + foreach (Symbol sym in symbols) { + sym.accept (this); + } + return; + } + + var sorted_symbols = new ArrayList (); + foreach (Symbol sym in symbols) { + int left = 0; + int right = sorted_symbols.size - 1; + if (left > right || sym.name < sorted_symbols[left].name) { + sorted_symbols.insert (0, sym); + } else if (sym.name > sorted_symbols[right].name) { + sorted_symbols.add (sym); + } else { + while (right - left > 1) { + int i = (right + left) / 2; + if (sym.name > sorted_symbols[i].name) { + left = i; + } else { + right = i; + } + } + sorted_symbols.insert (left + 1, sym); + } + } + foreach (Symbol sym in sorted_symbols) { + sym.accept (this); + } + } + + public override void visit_struct (Struct st) { + if (st.external_package) { + return; + } + + if (!check_accessibility (st)) { + return; + } + + write_attributes (st); + + write_indent (); + write_accessibility (st); + write_string ("struct "); + write_identifier (st.name); + + write_type_parameters (st.get_type_parameters ()); + + if (st.base_type != null) { + write_string (" : "); + write_type (st.base_type); + } + + write_begin_block (); + + current_scope = st.scope; + + foreach (Field field in st.get_fields ()) { + field.accept (this); + } + visit_sorted (st.get_constants ()); + visit_sorted (st.get_methods ()); + visit_sorted (st.get_properties ()); + + current_scope = current_scope.parent_scope; + + write_end_block (); + write_newline (); + } + + public override void visit_interface (Interface iface) { + if (iface.external_package) { + return; + } + + if (!check_accessibility (iface)) { + return; + } + + write_attributes (iface); + + write_indent (); + write_accessibility (iface); + write_string ("interface "); + write_identifier (iface.name); + + write_type_parameters (iface.get_type_parameters ()); + + var prerequisites = iface.get_prerequisites (); + if (prerequisites.size > 0) { + write_string (" : "); + + bool first = true; + foreach (DataType prerequisite in prerequisites) { + if (!first) { + write_string (", "); + } else { + first = false; + } + write_type (prerequisite); + } + } + write_begin_block (); + + current_scope = iface.scope; + + visit_sorted (iface.get_classes ()); + visit_sorted (iface.get_structs ()); + visit_sorted (iface.get_enums ()); + visit_sorted (iface.get_delegates ()); + visit_sorted (iface.get_fields ()); + visit_sorted (iface.get_constants ()); + visit_sorted (iface.get_methods ()); + visit_sorted (iface.get_properties ()); + visit_sorted (iface.get_signals ()); + + current_scope = current_scope.parent_scope; + + write_end_block (); + write_newline (); + } + + public override void visit_enum (Enum en) { + if (en.external_package) { + return; + } + + if (!check_accessibility (en)) { + return; + } + + write_attributes (en); + + write_indent (); + write_accessibility (en); + write_string ("enum "); + write_identifier (en.name); + write_begin_block (); + + bool first = true; + foreach (EnumValue ev in en.get_values ()) { + if (first) { + first = false; + } else { + write_string (","); + write_newline (); + } + + write_attributes (ev); + + write_indent (); + write_identifier (ev.name); + + if (type == CodeWriterType.FAST && ev.value != null) { + write_string(" = "); + ev.value.accept (this); + } + } + + if (!first) { + if (en.get_methods ().size > 0 || en.get_constants ().size > 0) { + write_string (";"); + } + write_newline (); + } + + current_scope = en.scope; + foreach (Method m in en.get_methods ()) { + m.accept (this); + } + foreach (Constant c in en.get_constants ()) { + c.accept (this); + } + current_scope = current_scope.parent_scope; + + write_end_block (); + write_newline (); + } + + public override void visit_error_domain (ErrorDomain edomain) { + if (edomain.external_package) { + return; + } + + if (!check_accessibility (edomain)) { + return; + } + + write_attributes (edomain); + + write_indent (); + write_accessibility (edomain); + write_string ("errordomain "); + write_identifier (edomain.name); + write_begin_block (); + + bool first = true; + foreach (ErrorCode ecode in edomain.get_codes ()) { + if (first) { + first = false; + } else { + write_string (","); + write_newline (); + } + + write_attributes (ecode); + + write_indent (); + write_identifier (ecode.name); + } + + if (!first) { + if (edomain.get_methods ().size > 0) { + write_string (";"); + } + write_newline (); + } + + current_scope = edomain.scope; + foreach (Method m in edomain.get_methods ()) { + m.accept (this); + } + current_scope = current_scope.parent_scope; + + write_end_block (); + write_newline (); + } + + public override void visit_constant (Constant c) { + if (c.external_package) { + return; + } + + if (!check_accessibility (c)) { + return; + } + + write_attributes (c); + + write_indent (); + write_accessibility (c); + write_string ("const "); + + write_type (c.type_reference); + + write_string (" "); + write_identifier (c.name); + if (type == CodeWriterType.FAST && c.value != null) { + write_string(" = "); + c.value.accept (this); + } + write_string (";"); + write_newline (); + } + + public override void visit_field (Field f) { + if (f.external_package) { + return; + } + + if (!check_accessibility (f)) { + return; + } + + write_attributes (f); + + write_indent (); + write_accessibility (f); + + if (f.binding == MemberBinding.STATIC) { + write_string ("static "); + } else if (f.binding == MemberBinding.CLASS) { + write_string ("class "); + } + + if (f.variable_type.is_weak ()) { + write_string ("weak "); + } + + write_type (f.variable_type); + + write_string (" "); + write_identifier (f.name); + write_string (";"); + write_newline (); + } + + private void write_error_domains (List error_domains) { + if (error_domains.size > 0) { + write_string (" throws "); + + bool first = true; + foreach (DataType type in error_domains) { + if (!first) { + write_string (", "); + } else { + first = false; + } + + write_type (type); + } + } + } + + private void write_params (List params) { + write_string ("("); + + int i = 1; + foreach (Parameter param in params) { + if (i > 1) { + write_string (", "); + } + + if (param.ellipsis) { + write_string ("..."); + continue; + } + + write_attributes (param); + + if (param.params_array) { + write_string ("params "); + } + + if (param.direction == ParameterDirection.IN) { + if (param.variable_type.value_owned) { + write_string ("owned "); + } + } else { + if (param.direction == ParameterDirection.REF) { + write_string ("ref "); + } else if (param.direction == ParameterDirection.OUT) { + write_string ("out "); + } + if (param.variable_type.is_weak ()) { + write_string ("unowned "); + } + } + + write_type (param.variable_type); + + write_string (" "); + write_identifier (param.name); + + if (param.initializer != null) { + write_string (" = "); + param.initializer.accept (this); + } + + i++; + } + + write_string (")"); + } + + public override void visit_delegate (Delegate cb) { + if (cb.external_package) { + return; + } + + if (!check_accessibility (cb)) { + return; + } + + write_attributes (cb); + + write_indent (); + + write_accessibility (cb); + write_string ("delegate "); + + write_return_type (cb.return_type); + + write_string (" "); + write_identifier (cb.name); + + write_type_parameters (cb.get_type_parameters ()); + + write_string (" "); + + write_params (cb.get_parameters ()); + + write_error_domains (cb.get_error_types ()); + + write_string (";"); + + write_newline (); + } + + public override void visit_constructor (Constructor c) { + if (type != CodeWriterType.DUMP) { + return; + } + + write_indent (); + write_string ("construct"); + write_code_block (c.body); + write_newline (); + } + + public override void visit_method (Method m) { + if (m.external_package) { + return; + } + + // don't write interface implementation unless it's an abstract or virtual method + if (!check_accessibility (m) || (m.base_interface_method != null && !m.is_abstract && !m.is_virtual)) { + if (type != CodeWriterType.DUMP) { + return; + } + } + + write_attributes (m); + + write_indent (); + write_accessibility (m); + + if (m is CreationMethod) { + if (m.coroutine) { + write_string ("async "); + } + + var datatype = (TypeSymbol) m.parent_symbol; + write_identifier (datatype.name); + if (m.name != ".new") { + write_string ("."); + write_identifier (m.name); + } + write_string (" "); + } else { + if (m.binding == MemberBinding.STATIC) { + write_string ("static "); + } else if (m.binding == MemberBinding.CLASS) { + write_string ("class "); + } else if (m.is_abstract) { + write_string ("abstract "); + } else if (m.is_virtual) { + write_string ("virtual "); + } else if (m.overrides) { + write_string ("override "); + } + + if (m.hides) { + write_string ("new "); + } + + if (m.coroutine) { + write_string ("async "); + } + + write_return_type (m.return_type); + write_string (" "); + + write_identifier (m.name); + + write_type_parameters (m.get_type_parameters ()); + + write_string (" "); + } + + write_params (m.get_parameters ()); + + if (context.profile != Profile.DOVA) { + write_error_domains (m.get_error_types ()); + } + + write_code_block (m.body); + + write_newline (); + } + + public override void visit_creation_method (CreationMethod m) { + visit_method (m); + } + + public override void visit_property (Property prop) { + if (!check_accessibility (prop) || (prop.base_interface_property != null && !prop.is_abstract && !prop.is_virtual)) { + return; + } + + write_attributes (prop); + + write_indent (); + write_accessibility (prop); + + if (prop.binding == MemberBinding.STATIC) { + write_string ("static "); + } else if (prop.is_abstract) { + write_string ("abstract "); + } else if (prop.is_virtual) { + write_string ("virtual "); + } else if (prop.overrides) { + write_string ("override "); + } + + write_type (prop.property_type); + + write_string (" "); + write_identifier (prop.name); + write_string (" {"); + if (prop.get_accessor != null) { + write_attributes (prop.get_accessor); + + write_property_accessor_accessibility (prop.get_accessor); + + if (context.profile != Profile.DOVA && prop.get_accessor.value_type.is_disposable ()) { + write_string (" owned"); + } + + write_string (" get"); + write_code_block (prop.get_accessor.body); + } + if (prop.set_accessor != null) { + write_attributes (prop.set_accessor); + + write_property_accessor_accessibility (prop.set_accessor); + + if (context.profile != Profile.DOVA && prop.set_accessor.value_type.value_owned) { + write_string (" owned"); + } + + if (prop.set_accessor.writable) { + write_string (" set"); + } + if (prop.set_accessor.construction) { + write_string (" construct"); + } + write_code_block (prop.set_accessor.body); + } + write_string (" }"); + write_newline (); + } + + public override void visit_signal (Signal sig) { + if (!check_accessibility (sig)) { + return; + } + + write_attributes (sig); + + write_indent (); + write_accessibility (sig); + + if (sig.is_virtual) { + write_string ("virtual "); + } + + write_string ("signal "); + + write_return_type (sig.return_type); + + write_string (" "); + write_identifier (sig.name); + + write_string (" "); + + write_params (sig.get_parameters ()); + + write_string (";"); + + write_newline (); + } + + public override void visit_block (Block b) { + write_begin_block (); + + foreach (Statement stmt in b.get_statements ()) { + stmt.accept (this); + } + + write_end_block (); + } + + public override void visit_empty_statement (EmptyStatement stmt) { + } + + public override void visit_declaration_statement (DeclarationStatement stmt) { + write_indent (); + stmt.declaration.accept (this); + write_string (";"); + write_newline (); + } + + public override void visit_local_variable (LocalVariable local) { + write_type (local.variable_type); + write_string (" "); + write_identifier (local.name); + if (local.initializer != null) { + write_string (" = "); + local.initializer.accept (this); + } + } + + public override void visit_initializer_list (InitializerList list) { + write_string ("{"); + + bool first = true; + foreach (Expression initializer in list.get_initializers ()) { + if (!first) { + write_string (", "); + } else { + write_string (" "); + } + first = false; + initializer.accept (this); + } + write_string (" }"); + } + + public override void visit_expression_statement (ExpressionStatement stmt) { + write_indent (); + stmt.expression.accept (this); + write_string (";"); + write_newline (); + } + + public override void visit_if_statement (IfStatement stmt) { + write_indent (); + write_string ("if ("); + stmt.condition.accept (this); + write_string (")"); + stmt.true_statement.accept (this); + if (stmt.false_statement != null) { + write_string (" else"); + stmt.false_statement.accept (this); + } + write_newline (); + } + + public override void visit_switch_statement (SwitchStatement stmt) { + write_indent (); + write_string ("switch ("); + stmt.expression.accept (this); + write_string (") {"); + write_newline (); + + foreach (SwitchSection section in stmt.get_sections ()) { + section.accept (this); + } + + write_indent (); + write_string ("}"); + write_newline (); + } + + public override void visit_switch_section (SwitchSection section) { + foreach (SwitchLabel label in section.get_labels ()) { + label.accept (this); + } + + visit_block (section); + } + + public override void visit_switch_label (SwitchLabel label) { + if (label.expression != null) { + write_indent (); + write_string ("case "); + label.expression.accept (this); + write_string (":"); + write_newline (); + } else { + write_indent (); + write_string ("default:"); + write_newline (); + } + } + + public override void visit_loop (Loop stmt) { + write_indent (); + write_string ("loop"); + stmt.body.accept (this); + write_newline (); + } + + public override void visit_while_statement (WhileStatement stmt) { + write_indent (); + write_string ("while ("); + stmt.condition.accept (this); + write_string (")"); + stmt.body.accept (this); + write_newline (); + } + + public override void visit_do_statement (DoStatement stmt) { + write_indent (); + write_string ("do"); + stmt.body.accept (this); + write_string ("while ("); + stmt.condition.accept (this); + write_string (");"); + write_newline (); + } + + public override void visit_for_statement (ForStatement stmt) { + write_indent (); + write_string ("for ("); + + bool first = true; + foreach (Expression initializer in stmt.get_initializer ()) { + if (!first) { + write_string (", "); + } + first = false; + initializer.accept (this); + } + write_string ("; "); + + stmt.condition.accept (this); + write_string ("; "); + + first = true; + foreach (Expression iterator in stmt.get_iterator ()) { + if (!first) { + write_string (", "); + } + first = false; + iterator.accept (this); + } + + write_string (")"); + stmt.body.accept (this); + write_newline (); + } + + public override void visit_foreach_statement (ForeachStatement stmt) { + } + + public override void visit_break_statement (BreakStatement stmt) { + write_indent (); + write_string ("break;"); + write_newline (); + } + + public override void visit_continue_statement (ContinueStatement stmt) { + write_indent (); + write_string ("continue;"); + write_newline (); + } + + public override void visit_return_statement (ReturnStatement stmt) { + write_indent (); + write_string ("return"); + if (stmt.return_expression != null) { + write_string (" "); + stmt.return_expression.accept (this); + } + write_string (";"); + write_newline (); + } + + public override void visit_yield_statement (YieldStatement y) { + write_indent (); + write_string ("yield"); + if (y.yield_expression != null) { + write_string (" "); + y.yield_expression.accept (this); + } + write_string (";"); + write_newline (); + } + + public override void visit_throw_statement (ThrowStatement stmt) { + write_indent (); + write_string ("throw"); + if (stmt.error_expression != null) { + write_string (" "); + stmt.error_expression.accept (this); + } + write_string (";"); + write_newline (); + } + + public override void visit_try_statement (TryStatement stmt) { + write_indent (); + write_string ("try"); + stmt.body.accept (this); + foreach (var clause in stmt.get_catch_clauses ()) { + clause.accept (this); + } + if (stmt.finally_body != null) { + write_string (" finally"); + stmt.finally_body.accept (this); + } + write_newline (); + } + + public override void visit_catch_clause (CatchClause clause) { + var type_name = clause.error_type == null ? "GLib.Error" : clause.error_type.to_string (); + var var_name = clause.variable_name == null ? "_" : clause.variable_name; + write_string (" catch (%s %s)".printf (type_name, var_name)); + clause.body.accept (this); + } + + public override void visit_lock_statement (LockStatement stmt) { + write_indent (); + write_string ("lock ("); + stmt.resource.accept (this); + write_string (")"); + if (stmt.body == null) { + write_string (";"); + } else { + stmt.body.accept (this); + } + write_newline (); + } + + public override void visit_delete_statement (DeleteStatement stmt) { + write_indent (); + write_string ("delete "); + stmt.expression.accept (this); + write_string (";"); + write_newline (); + } + + public override void visit_array_creation_expression (ArrayCreationExpression expr) { + write_string ("new "); + write_type (expr.element_type); + write_string ("["); + + bool first = true; + foreach (Expression size in expr.get_sizes ()) { + if (!first) { + write_string (", "); + } + first = false; + + size.accept (this); + } + + write_string ("]"); + + if (expr.initializer_list != null) { + write_string (" "); + expr.initializer_list.accept (this); + } + } + + public override void visit_boolean_literal (BooleanLiteral lit) { + write_string (lit.value.to_string ()); + } + + public override void visit_character_literal (CharacterLiteral lit) { + write_string (lit.value); + } + + public override void visit_integer_literal (IntegerLiteral lit) { + write_string (lit.value); + } + + public override void visit_real_literal (RealLiteral lit) { + write_string (lit.value); + } + + public override void visit_string_literal (StringLiteral lit) { + write_string (lit.value); + } + + public override void visit_null_literal (NullLiteral lit) { + write_string ("null"); + } + + public override void visit_member_access (MemberAccess expr) { + if (expr.inner != null) { + expr.inner.accept (this); + write_string ("."); + } + write_identifier (expr.member_name); + } + + public override void visit_method_call (MethodCall expr) { + expr.call.accept (this); + write_string (" ("); + + bool first = true; + foreach (Expression arg in expr.get_argument_list ()) { + if (!first) { + write_string (", "); + } + first = false; + + arg.accept (this); + } + + write_string (")"); + } + + public override void visit_element_access (ElementAccess expr) { + expr.container.accept (this); + write_string ("["); + + bool first = true; + foreach (Expression index in expr.get_indices ()) { + if (!first) { + write_string (", "); + } + first = false; + + index.accept (this); + } + + write_string ("]"); + } + + public override void visit_slice_expression (SliceExpression expr) { + expr.container.accept (this); + write_string ("["); + expr.start.accept (this); + write_string (":"); + expr.stop.accept (this); + write_string ("]"); + } + + public override void visit_base_access (BaseAccess expr) { + write_string ("base"); + } + + public override void visit_postfix_expression (PostfixExpression expr) { + expr.inner.accept (this); + if (expr.increment) { + write_string ("++"); + } else { + write_string ("--"); + } + } + + public override void visit_object_creation_expression (ObjectCreationExpression expr) { + if (!expr.struct_creation) { + write_string ("new "); + } + + write_type (expr.type_reference); + + if (expr.symbol_reference.name != ".new") { + write_string ("."); + write_string (expr.symbol_reference.name); + } + + write_string (" ("); + + bool first = true; + foreach (Expression arg in expr.get_argument_list ()) { + if (!first) { + write_string (", "); + } + first = false; + + arg.accept (this); + } + + write_string (")"); + } + + public override void visit_sizeof_expression (SizeofExpression expr) { + write_string ("sizeof ("); + write_type (expr.type_reference); + write_string (")"); + } + + public override void visit_typeof_expression (TypeofExpression expr) { + write_string ("typeof ("); + write_type (expr.type_reference); + write_string (")"); + } + + public override void visit_unary_expression (UnaryExpression expr) { + switch (expr.operator) { + case UnaryOperator.PLUS: + write_string ("+"); + break; + case UnaryOperator.MINUS: + write_string ("-"); + break; + case UnaryOperator.LOGICAL_NEGATION: + write_string ("!"); + break; + case UnaryOperator.BITWISE_COMPLEMENT: + write_string ("~"); + break; + case UnaryOperator.INCREMENT: + write_string ("++"); + break; + case UnaryOperator.DECREMENT: + write_string ("--"); + break; + case UnaryOperator.REF: + write_string ("ref "); + break; + case UnaryOperator.OUT: + write_string ("out "); + break; + default: + assert_not_reached (); + } + expr.inner.accept (this); + } + + public override void visit_cast_expression (CastExpression expr) { + if (expr.is_non_null_cast) { + write_string ("(!) "); + expr.inner.accept (this); + return; + } + + if (!expr.is_silent_cast) { + write_string ("("); + write_type (expr.type_reference); + write_string (") "); + } + + expr.inner.accept (this); + + if (expr.is_silent_cast) { + write_string (" as "); + write_type (expr.type_reference); + } + } + + public override void visit_pointer_indirection (PointerIndirection expr) { + write_string ("*"); + expr.inner.accept (this); + } + + public override void visit_addressof_expression (AddressofExpression expr) { + write_string ("&"); + expr.inner.accept (this); + } + + public override void visit_reference_transfer_expression (ReferenceTransferExpression expr) { + write_string ("(owned) "); + expr.inner.accept (this); + } + + public override void visit_binary_expression (BinaryExpression expr) { + expr.left.accept (this); + + switch (expr.operator) { + case BinaryOperator.PLUS: + write_string (" + "); + break; + case BinaryOperator.MINUS: + write_string (" - "); + break; + case BinaryOperator.MUL: + write_string (" * "); + break; + case BinaryOperator.DIV: + write_string (" / "); + break; + case BinaryOperator.MOD: + write_string (" % "); + break; + case BinaryOperator.SHIFT_LEFT: + write_string (" << "); + break; + case BinaryOperator.SHIFT_RIGHT: + write_string (" >> "); + break; + case BinaryOperator.LESS_THAN: + write_string (" < "); + break; + case BinaryOperator.GREATER_THAN: + write_string (" > "); + break; + case BinaryOperator.LESS_THAN_OR_EQUAL: + write_string (" <= "); + break; + case BinaryOperator.GREATER_THAN_OR_EQUAL: + write_string (" >= "); + break; + case BinaryOperator.EQUALITY: + write_string (" == "); + break; + case BinaryOperator.INEQUALITY: + write_string (" != "); + break; + case BinaryOperator.BITWISE_AND: + write_string (" & "); + break; + case BinaryOperator.BITWISE_OR: + write_string (" | "); + break; + case BinaryOperator.BITWISE_XOR: + write_string (" ^ "); + break; + case BinaryOperator.AND: + write_string (" && "); + break; + case BinaryOperator.OR: + write_string (" || "); + break; + case BinaryOperator.IN: + write_string (" in "); + break; + case BinaryOperator.COALESCE: + write_string (" ?? "); + break; + default: + assert_not_reached (); + } + + expr.right.accept (this); + } + + public override void visit_type_check (TypeCheck expr) { + expr.expression.accept (this); + write_string (" is "); + write_type (expr.type_reference); + } + + public override void visit_conditional_expression (ConditionalExpression expr) { + expr.condition.accept (this); + write_string ("?"); + expr.true_expression.accept (this); + write_string (":"); + expr.false_expression.accept (this); + } + + public override void visit_lambda_expression (LambdaExpression expr) { + write_string ("("); + var params = expr.get_parameters (); + int i = 1; + foreach (var param in params) { + if (i > 1) { + write_string (", "); + } + + if (param.direction == ParameterDirection.REF) { + write_string ("ref "); + } else if (param.direction == ParameterDirection.OUT) { + write_string ("out "); + } + + write_identifier (param.name); + + i++; + } + write_string (") =>"); + if (expr.statement_body != null) { + expr.statement_body.accept (this); + } else if (expr.expression_body != null) { + expr.expression_body.accept (this); + } + } + + public override void visit_assignment (Assignment a) { + a.left.accept (this); + write_string (" = "); + a.right.accept (this); + } + + private void write_indent () { + int i; + + if (!bol) { + stream.putc ('\n'); + } + + for (i = 0; i < indent; i++) { + stream.putc ('\t'); + } + + bol = false; + } + + private void write_identifier (string s) { + char* id = (char*)s; + int id_length = (int)s.length; + if (context.profile != Profile.DOVA && + (Vala.Scanner.get_identifier_or_keyword (id, id_length) != Vala.TokenType.IDENTIFIER || + s.get_char ().isdigit ())) { + stream.putc ('@'); + } + write_string (s); + } + + private void write_return_type (DataType type) { + if (type.is_weak ()) { + write_string ("unowned "); + } + + write_type (type); + } + + private void write_type (DataType type) { + write_string (type.to_qualified_string (current_scope)); + } + + private void write_string (string s) { + stream.printf ("%s", s); + bol = false; + } + + private void write_newline () { + stream.putc ('\n'); + bol = true; + } + + void write_code_block (Block? block) { + if (block == null || type != CodeWriterType.DUMP) { + write_string (";"); + return; + } + + block.accept (this); + } + + private void write_begin_block () { + if (!bol) { + stream.putc (' '); + } else { + write_indent (); + } + stream.putc ('{'); + write_newline (); + indent++; + } + + private void write_end_block () { + indent--; + write_indent (); + stream.printf ("}"); + } + + private bool check_accessibility (Symbol sym) { + switch (type) { + case CodeWriterType.EXTERNAL: + return sym.access == SymbolAccessibility.PUBLIC || + sym.access == SymbolAccessibility.PROTECTED; + + case CodeWriterType.INTERNAL: + case CodeWriterType.FAST: + return sym.access == SymbolAccessibility.INTERNAL || + sym.access == SymbolAccessibility.PUBLIC || + sym.access == SymbolAccessibility.PROTECTED; + + case CodeWriterType.DUMP: + return true; + + default: + assert_not_reached (); + } + } + + private void write_attributes (CodeNode node) { + var sym = node as Symbol; + + var need_cheaders = type != CodeWriterType.FAST && sym != null && !(sym is Namespace) && sym.parent_symbol is Namespace; + + var attributes = new GLib.Sequence (); + foreach (var attr in node.attributes) { + attributes.insert_sorted (attr, (a, b) => strcmp (a.name, b.name)); + } + if (need_cheaders && node.get_attribute ("CCode") == null) { + attributes.insert_sorted (new Attribute ("CCode"), (a, b) => strcmp (a.name, b.name)); + } + + var iter = attributes.get_begin_iter (); + while (!iter.is_end ()) { + unowned Attribute attr = iter.get (); + iter = iter.next (); + + var keys = new GLib.Sequence (); + foreach (var key in attr.args.get_keys ()) { + if (key == "cheader_filename" && sym is Namespace) { + continue; + } + keys.insert_sorted (key, (CompareDataFunc) strcmp); + } + if (need_cheaders && attr.name == "CCode" && !attr.has_argument ("cheader_filename")) { + keys.insert_sorted ("cheader_filename", (CompareDataFunc) strcmp); + } + + if (attr.name == "CCode" && keys.get_length () == 0) { + // only cheader_filename on namespace + continue; + } + + if (!(node is Parameter)) { + write_indent (); + } + + stream.printf ("[%s", attr.name); + if (keys.get_length () > 0) { + stream.printf (" ("); + + string separator = ""; + var arg_iter = keys.get_begin_iter (); + while (!arg_iter.is_end ()) { + unowned string arg_name = arg_iter.get (); + arg_iter = arg_iter.next (); + if (arg_name == "cheader_filename") { + stream.printf ("%scheader_filename = \"%s\"", separator, get_cheaders (sym)); + } else { + stream.printf ("%s%s = %s", separator, arg_name, attr.args.get (arg_name)); + } + separator = ", "; + } + + stream.printf (")"); + } + stream.printf ("]"); + if (node is Parameter) { + write_string (" "); + } else { + write_newline (); + } + } + } + + private void write_accessibility (Symbol sym) { + if (sym.access == SymbolAccessibility.PUBLIC) { + write_string ("public "); + } else if (sym.access == SymbolAccessibility.PROTECTED) { + write_string ("protected "); + } else if (sym.access == SymbolAccessibility.INTERNAL) { + write_string ("internal "); + } else if (sym.access == SymbolAccessibility.PRIVATE) { + write_string ("private "); + } + + if (type != CodeWriterType.EXTERNAL && sym.external && !sym.external_package) { + write_string ("extern "); + } + } + + void write_property_accessor_accessibility (Symbol sym) { + if (sym.access == SymbolAccessibility.PROTECTED) { + write_string (" protected"); + } else if (sym.access == SymbolAccessibility.INTERNAL) { + write_string (" internal"); + } else if (sym.access == SymbolAccessibility.PRIVATE) { + write_string (" private"); + } + } + + void write_type_parameters (List type_params) { + if (type_params.size > 0) { + write_string ("<"); + bool first = true; + foreach (TypeParameter type_param in type_params) { + if (first) { + first = false; + } else { + write_string (","); + } + write_identifier (type_param.name); + } + write_string (">"); + } + } +} + +public enum Vala.CodeWriterType { + EXTERNAL, + INTERNAL, + FAST, + DUMP +} diff --git a/tests/vala/valaparser.vala b/tests/vala/valaparser.vala new file mode 100644 index 0000000..76ea0b9 --- /dev/null +++ b/tests/vala/valaparser.vala @@ -0,0 +1,3595 @@ +/* valaparser.vala + * + * Copyright (C) 2006-2011 Jürg Billeter + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Author: + * Jürg Billeter + */ + +using GLib; + +/** + * Code visitor parsing all Vala source files. + */ +public class Vala.Parser : CodeVisitor { + Scanner scanner; + + CodeContext context; + + // token buffer + TokenInfo[] tokens; + // index of current token in buffer + int index; + // number of tokens in buffer + int size; + + Comment comment; + + const int BUFFER_SIZE = 32; + + static List _empty_type_parameter_list; + + struct TokenInfo { + public TokenType type; + public SourceLocation begin; + public SourceLocation end; + } + + enum ModifierFlags { + NONE, + ABSTRACT = 1 << 0, + CLASS = 1 << 1, + EXTERN = 1 << 2, + INLINE = 1 << 3, + NEW = 1 << 4, + OVERRIDE = 1 << 5, + STATIC = 1 << 6, + VIRTUAL = 1 << 7, + ASYNC = 1 << 8, + SEALED = 1 << 9 + } + + public Parser () { + tokens = new TokenInfo[BUFFER_SIZE]; + } + + /** + * Parses all .vala and .vapi source files in the specified code + * context and builds a code tree. + * + * @param context a code context + */ + public void parse (CodeContext context) { + this.context = context; + context.accept (this); + } + + public override void visit_source_file (SourceFile source_file) { + if (context.run_output || source_file.filename.has_suffix (".vala") || source_file.filename.has_suffix (".vapi")) { + parse_file (source_file); + } + } + + inline bool next () { + index = (index + 1) % BUFFER_SIZE; + size--; + if (size <= 0) { + SourceLocation begin, end; + TokenType type = scanner.read_token (out begin, out end); + tokens[index].type = type; + tokens[index].begin = begin; + tokens[index].end = end; + size = 1; + } + return (tokens[index].type != TokenType.EOF); + } + + inline void prev () { + index = (index - 1 + BUFFER_SIZE) % BUFFER_SIZE; + size++; + assert (size <= BUFFER_SIZE); + } + + inline TokenType current () { + return tokens[index].type; + } + + inline bool accept (TokenType type) { + if (current () == type) { + next (); + return true; + } + return false; + } + + string get_error (string msg) { + var begin = get_location (); + next (); + Report.error (get_src (begin), "syntax error, " + msg); + return msg; + } + + inline bool expect (TokenType type) throws ParseError { + if (accept (type)) { + return true; + } + + throw new ParseError.SYNTAX (get_error ("expected %s".printf (type.to_string ()))); + } + + inline SourceLocation get_location () { + return tokens[index].begin; + } + + string get_current_string () { + return ((string) tokens[index].begin.pos).substring (0, (int) (tokens[index].end.pos - tokens[index].begin.pos)); + } + + string get_last_string () { + int last_index = (index + BUFFER_SIZE - 1) % BUFFER_SIZE; + return ((string) tokens[last_index].begin.pos).substring (0, (int) (tokens[last_index].end.pos - tokens[last_index].begin.pos)); + } + + SourceReference get_src (SourceLocation begin) { + int last_index = (index + BUFFER_SIZE - 1) % BUFFER_SIZE; + + return new SourceReference (scanner.source_file, begin.line, begin.column, tokens[last_index].end.line, tokens[last_index].end.column); + } + + SourceReference get_current_src () { + return new SourceReference (scanner.source_file, tokens[index].begin.line, tokens[index].begin.column, tokens[index].end.line, tokens[index].end.column); + } + + SourceReference get_last_src () { + int last_index = (index + BUFFER_SIZE - 1) % BUFFER_SIZE; + + return new SourceReference (scanner.source_file, tokens[last_index].begin.line, tokens[last_index].begin.column, tokens[last_index].end.line, tokens[last_index].end.column); + } + + void rollback (SourceLocation location) { + while (tokens[index].begin.pos != location.pos) { + index = (index - 1 + BUFFER_SIZE) % BUFFER_SIZE; + size++; + if (size > BUFFER_SIZE) { + scanner.seek (location); + size = 0; + index = 0; + + next (); + } + } + } + + void skip_identifier () throws ParseError { + // also accept keywords as identifiers where there is no conflict + switch (current ()) { + case TokenType.ABSTRACT: + case TokenType.AS: + case TokenType.ASYNC: + case TokenType.BASE: + case TokenType.BREAK: + case TokenType.CASE: + case TokenType.CATCH: + case TokenType.CLASS: + case TokenType.CONST: + case TokenType.CONSTRUCT: + case TokenType.CONTINUE: + case TokenType.DEFAULT: + case TokenType.DELEGATE: + case TokenType.DELETE: + case TokenType.DO: + case TokenType.DYNAMIC: + case TokenType.ELSE: + case TokenType.ENUM: + case TokenType.ENSURES: + case TokenType.ERRORDOMAIN: + case TokenType.EXTERN: + case TokenType.FALSE: + case TokenType.FINALLY: + case TokenType.FOR: + case TokenType.FOREACH: + case TokenType.GET: + case TokenType.IDENTIFIER: + case TokenType.IF: + case TokenType.IN: + case TokenType.INLINE: + case TokenType.INTERFACE: + case TokenType.INTERNAL: + case TokenType.IS: + case TokenType.LOCK: + case TokenType.NAMESPACE: + case TokenType.NEW: + case TokenType.NULL: + case TokenType.OUT: + case TokenType.OVERRIDE: + case TokenType.OWNED: + case TokenType.PARAMS: + case TokenType.PRIVATE: + case TokenType.PROTECTED: + case TokenType.PUBLIC: + case TokenType.REF: + case TokenType.REQUIRES: + case TokenType.RETURN: + case TokenType.SEALED: + case TokenType.SET: + case TokenType.SIGNAL: + case TokenType.SIZEOF: + case TokenType.STATIC: + case TokenType.STRUCT: + case TokenType.SWITCH: + case TokenType.THIS: + case TokenType.THROW: + case TokenType.THROWS: + case TokenType.TRUE: + case TokenType.TRY: + case TokenType.TYPEOF: + case TokenType.UNOWNED: + case TokenType.USING: + case TokenType.VAR: + case TokenType.VIRTUAL: + case TokenType.VOID: + case TokenType.VOLATILE: + case TokenType.WEAK: + case TokenType.WHILE: + case TokenType.YIELD: + next (); + return; + case TokenType.INTEGER_LITERAL: + case TokenType.REAL_LITERAL: + // also accept integer and real literals + // as long as they contain at least one character + // and no decimal point + // for example, 2D and 3D + string id = get_current_string (); + if (id[id.length - 1].isalpha () && !("." in id)) { + next (); + return; + } + break; + default: + throw new ParseError.SYNTAX (get_error ("expected identifier")); + } + } + + string parse_identifier () throws ParseError { + skip_identifier (); + return get_last_string (); + } + + Expression parse_literal () throws ParseError { + var begin = get_location (); + + switch (current ()) { + case TokenType.TRUE: + next (); + return new BooleanLiteral (true, get_src (begin)); + case TokenType.FALSE: + next (); + return new BooleanLiteral (false, get_src (begin)); + case TokenType.INTEGER_LITERAL: + next (); + return new IntegerLiteral (get_last_string (), get_src (begin)); + case TokenType.REAL_LITERAL: + next (); + return new RealLiteral (get_last_string (), get_src (begin)); + case TokenType.CHARACTER_LITERAL: + next (); + // FIXME validate and unescape here and just pass unichar to CharacterLiteral + var lit = new CharacterLiteral (get_last_string (), get_src (begin)); + if (lit.error) { + Report.error (lit.source_reference, "invalid character literal"); + } + return lit; + case TokenType.REGEX_LITERAL: + next (); + string match_part = get_last_string (); + SourceReference src_begin = get_src (begin); + expect (TokenType.CLOSE_REGEX_LITERAL); + string close_token = get_last_string (); + return new RegexLiteral ("%s/%s".printf (close_token, match_part), src_begin); + case TokenType.STRING_LITERAL: + next (); + return new StringLiteral (get_last_string (), get_src (begin)); + case TokenType.TEMPLATE_STRING_LITERAL: + next (); + return new StringLiteral ("\"%s\"".printf (get_last_string ()), get_src (begin)); + case TokenType.VERBATIM_STRING_LITERAL: + next (); + string raw_string = get_last_string (); + string escaped_string = raw_string.substring (3, raw_string.length - 6).escape (""); + return new StringLiteral ("\"%s\"".printf (escaped_string), get_src (begin)); + case TokenType.NULL: + next (); + return new NullLiteral (get_src (begin)); + default: + throw new ParseError.SYNTAX (get_error ("expected literal")); + } + } + + public void parse_file (SourceFile source_file) { + scanner = new Scanner (source_file); + parse_file_comments (); + + index = -1; + size = 0; + + next (); + + + try { + parse_using_directives (context.root); + parse_declarations (context.root, true); + if (accept (TokenType.CLOSE_BRACE)) { + // only report error if it's not a secondary error + if (context.report.get_errors () == 0) { + Report.error (get_last_src (), "unexpected `}'"); + } + } + } catch (ParseError e) { + // already reported + } + + scanner = null; + } + + void parse_file_comments () { + scanner.parse_file_comments (); + } + + void skip_symbol_name () throws ParseError { + do { + skip_identifier (); + } while (accept (TokenType.DOT) || accept (TokenType.DOUBLE_COLON)); + } + + UnresolvedSymbol parse_symbol_name () throws ParseError { + var begin = get_location (); + UnresolvedSymbol sym = null; + do { + string name = parse_identifier (); + if (name == "global" && accept (TokenType.DOUBLE_COLON)) { + // global::Name + // qualified access to global symbol + name = parse_identifier (); + sym = new UnresolvedSymbol (sym, name, get_src (begin)); + sym.qualified = true; + continue; + } + sym = new UnresolvedSymbol (sym, name, get_src (begin)); + } while (accept (TokenType.DOT)); + return sym; + } + + void skip_type () throws ParseError { + accept (TokenType.DYNAMIC); + accept (TokenType.OWNED); + accept (TokenType.UNOWNED); + accept (TokenType.WEAK); + if (accept (TokenType.VOID)) { + } else { + skip_symbol_name (); + skip_type_argument_list (); + } + while (accept (TokenType.STAR)) { + } + accept (TokenType.INTERR); + while (accept (TokenType.OPEN_BRACKET)) { + do { + // required for decision between expression and declaration statement + if (current () != TokenType.COMMA && current () != TokenType.CLOSE_BRACKET) { + parse_expression (); + } + } while (accept (TokenType.COMMA)); + expect (TokenType.CLOSE_BRACKET); + accept (TokenType.INTERR); + } + accept (TokenType.OP_NEG); + accept (TokenType.HASH); + } + + DataType parse_type (bool owned_by_default, bool can_weak_ref) throws ParseError { + var begin = get_location (); + + bool is_dynamic = accept (TokenType.DYNAMIC); + + bool value_owned = owned_by_default; + + if (owned_by_default) { + if (context.profile == Profile.DOVA) { + if (can_weak_ref && accept (TokenType.WEAK)) { + value_owned = false; + } + } else if (accept (TokenType.UNOWNED)) { + value_owned = false; + } else if (accept (TokenType.WEAK)) { + if (!can_weak_ref && !context.deprecated) { + Report.warning (get_last_src (), "deprecated syntax, use `unowned` modifier"); + } + value_owned = false; + } + } else { + value_owned = (context.profile != Profile.DOVA && accept (TokenType.OWNED)); + } + + DataType type; + + if (!is_dynamic && value_owned == owned_by_default && accept (TokenType.VOID)) { + type = new VoidType (get_src (begin)); + } else { + var sym = parse_symbol_name (); + List type_arg_list = parse_type_argument_list (false); + + type = new UnresolvedType.from_symbol (sym, get_src (begin)); + if (type_arg_list != null) { + foreach (DataType type_arg in type_arg_list) { + type.add_type_argument (type_arg); + } + } + } + + while (accept (TokenType.STAR)) { + type = new PointerType (type, get_src (begin)); + } + + if (!(type is PointerType)) { + type.nullable = accept (TokenType.INTERR); + } + + // array brackets in types are read from right to left, + // this is more logical, especially when nullable arrays + // or pointers are involved + while (accept (TokenType.OPEN_BRACKET)) { + bool invalid_array = false; + int array_rank = 0; + do { + array_rank++; + // required for decision between expression and declaration statement + if (current () != TokenType.COMMA && current () != TokenType.CLOSE_BRACKET) { + parse_expression (); + // only used for parsing, reject use as real type + invalid_array = true; + } + } while (context.profile != Profile.DOVA && accept (TokenType.COMMA)); + expect (TokenType.CLOSE_BRACKET); + + // arrays contain strong references by default + type.value_owned = true; + + var array_type = new ArrayType (type, array_rank, get_src (begin)); + array_type.nullable = accept (TokenType.INTERR); + array_type.invalid_syntax = invalid_array; + + type = array_type; + } + + if (accept (TokenType.OP_NEG)) { + Report.warning (get_last_src (), "obsolete syntax, types are non-null by default"); + } + + if (!owned_by_default) { + if (context.profile != Profile.DOVA && accept (TokenType.HASH)) { + if (!context.deprecated) { + Report.warning (get_last_src (), "deprecated syntax, use `owned` modifier"); + } + value_owned = true; + } + } + + if (type is PointerType) { + value_owned = false; + } + + type.is_dynamic = is_dynamic; + type.value_owned = value_owned; + return type; + } + + DataType? parse_inline_array_type (DataType? type) throws ParseError { + var begin = get_location (); + + // inline-allocated array + if (type != null && accept (TokenType.OPEN_BRACKET)) { + int array_length = -1; + + if (current () != TokenType.CLOSE_BRACKET) { + if (current () != TokenType.INTEGER_LITERAL) { + throw new ParseError.SYNTAX (get_error ("expected `]' or integer literal")); + } + + var length_literal = (IntegerLiteral) parse_literal (); + array_length = int.parse (length_literal.value); + } + expect (TokenType.CLOSE_BRACKET); + + var array_type = new ArrayType (type, 1, get_src (begin)); + array_type.inline_allocated = true; + if (array_length > 0) { + array_type.fixed_length = true; + array_type.length = array_length; + } + array_type.value_owned = type.value_owned; + + return array_type; + } + return type; + } + + List parse_argument_list () throws ParseError { + var list = new ArrayList (); + if (current () != TokenType.CLOSE_PARENS) { + do { + list.add (parse_argument ()); + } while (accept (TokenType.COMMA)); + } + return list; + } + + Expression parse_argument () throws ParseError { + var begin = get_location (); + + if (accept (TokenType.REF)) { + var inner = parse_expression (); + return new UnaryExpression (UnaryOperator.REF, inner, get_src (begin)); + } else if (accept (TokenType.OUT)) { + var inner = parse_expression (); + return new UnaryExpression (UnaryOperator.OUT, inner, get_src (begin)); + } else { + var expr = parse_expression (); + var ma = expr as MemberAccess; + if (ma != null && ma.inner == null && accept (TokenType.COLON)) { + // named argument + expr = parse_expression (); + return new NamedArgument (ma.member_name, expr, get_src (begin)); + } else { + return expr; + } + } + } + + Expression parse_primary_expression () throws ParseError { + var begin = get_location (); + + Expression expr; + + switch (current ()) { + case TokenType.TRUE: + case TokenType.FALSE: + case TokenType.INTEGER_LITERAL: + case TokenType.REAL_LITERAL: + case TokenType.CHARACTER_LITERAL: + case TokenType.STRING_LITERAL: + case TokenType.REGEX_LITERAL: + case TokenType.TEMPLATE_STRING_LITERAL: + case TokenType.VERBATIM_STRING_LITERAL: + case TokenType.NULL: + expr = parse_literal (); + break; + case TokenType.OPEN_BRACE: + if (context.profile == Profile.DOVA) { + expr = parse_set_literal (); + } else { + expr = parse_initializer (); + } + break; + case TokenType.OPEN_BRACKET: + if (context.profile == Profile.DOVA) { + expr = parse_list_literal (); + } else { + expr = parse_simple_name (); + } + break; + case TokenType.OPEN_PARENS: + expr = parse_tuple (); + break; + case TokenType.OPEN_TEMPLATE: + expr = parse_template (); + break; + case TokenType.OPEN_REGEX_LITERAL: + expr = parse_regex_literal (); + break; + case TokenType.THIS: + expr = parse_this_access (); + break; + case TokenType.BASE: + expr = parse_base_access (); + break; + case TokenType.NEW: + expr = parse_object_or_array_creation_expression (); + break; + case TokenType.YIELD: + expr = parse_yield_expression (); + break; + case TokenType.SIZEOF: + expr = parse_sizeof_expression (); + break; + case TokenType.TYPEOF: + expr = parse_typeof_expression (); + break; + default: + expr = parse_simple_name (); + break; + } + + // process primary expressions that start with an inner primary expression + bool found = true; + while (found) { + switch (current ()) { + case TokenType.DOT: + expr = parse_member_access (begin, expr); + break; + case TokenType.OP_PTR: + if (context.profile == Profile.DOVA) { + found = false; + } else { + expr = parse_pointer_member_access (begin, expr); + } + break; + case TokenType.OPEN_PARENS: + expr = parse_method_call (begin, expr); + break; + case TokenType.OPEN_BRACKET: + expr = parse_element_access (begin, expr); + break; + case TokenType.OPEN_BRACE: + var ma = expr as MemberAccess; + if (context.profile == Profile.DOVA && ma != null) { + expr = parse_object_literal (begin, ma); + } else { + found = false; + } + break; + case TokenType.OP_INC: + expr = parse_post_increment_expression (begin, expr); + break; + case TokenType.OP_DEC: + expr = parse_post_decrement_expression (begin, expr); + break; + default: + found = false; + break; + } + } + + return expr; + } + + Expression parse_simple_name () throws ParseError { + var begin = get_location (); + string id = parse_identifier (); + bool qualified = false; + if (id == "global" && accept (TokenType.DOUBLE_COLON)) { + id = parse_identifier (); + qualified = true; + } + List type_arg_list = parse_type_argument_list (true); + var expr = new MemberAccess (null, id, get_src (begin)); + expr.qualified = qualified; + if (type_arg_list != null) { + foreach (DataType type_arg in type_arg_list) { + expr.add_type_argument (type_arg); + } + } + return expr; + } + + Expression parse_tuple () throws ParseError { + var begin = get_location (); + + expect (TokenType.OPEN_PARENS); + var expr_list = new ArrayList (); + if (current () != TokenType.CLOSE_PARENS) { + do { + expr_list.add (parse_expression ()); + } while (accept (TokenType.COMMA)); + } + expect (TokenType.CLOSE_PARENS); + if (expr_list.size != 1) { + var tuple = new Tuple (get_src (begin)); + foreach (Expression expr in expr_list) { + tuple.add_expression (expr); + } + return tuple; + } + return expr_list.get (0); + } + + Expression parse_template () throws ParseError { + var begin = get_location (); + var template = new Template (); + + expect (TokenType.OPEN_TEMPLATE); + while (current () != TokenType.CLOSE_TEMPLATE) { + template.add_expression (parse_expression ()); + expect (TokenType.COMMA); + } + expect (TokenType.CLOSE_TEMPLATE); + + template.source_reference = get_src (begin); + return template; + } + + Expression parse_regex_literal () throws ParseError { + expect (TokenType.OPEN_REGEX_LITERAL); + + var expr = parse_literal (); + + return expr; + } + + Expression parse_member_access (SourceLocation begin, Expression inner) throws ParseError { + expect (TokenType.DOT); + string id = parse_identifier (); + List type_arg_list = parse_type_argument_list (true); + var expr = new MemberAccess (inner, id, get_src (begin)); + if (type_arg_list != null) { + foreach (DataType type_arg in type_arg_list) { + expr.add_type_argument (type_arg); + } + } + return expr; + } + + Expression parse_pointer_member_access (SourceLocation begin, Expression inner) throws ParseError { + expect (TokenType.OP_PTR); + string id = parse_identifier (); + List type_arg_list = parse_type_argument_list (true); + var expr = new MemberAccess.pointer (inner, id, get_src (begin)); + if (type_arg_list != null) { + foreach (DataType type_arg in type_arg_list) { + expr.add_type_argument (type_arg); + } + } + return expr; + } + + Expression parse_method_call (SourceLocation begin, Expression inner) throws ParseError { + expect (TokenType.OPEN_PARENS); + var arg_list = parse_argument_list (); + expect (TokenType.CLOSE_PARENS); + var init_list = parse_object_initializer (); + + if (init_list.size > 0 && inner is MemberAccess) { + // struct creation expression + var member = (MemberAccess) inner; + member.creation_member = true; + + var expr = new ObjectCreationExpression (member, get_src (begin)); + expr.struct_creation = true; + foreach (Expression arg in arg_list) { + expr.add_argument (arg); + } + foreach (MemberInitializer initializer in init_list) { + expr.add_member_initializer (initializer); + } + return expr; + } else { + var expr = new MethodCall (inner, get_src (begin)); + foreach (Expression arg in arg_list) { + expr.add_argument (arg); + } + return expr; + } + } + + Expression parse_element_access (SourceLocation begin, Expression inner) throws ParseError { + expect (TokenType.OPEN_BRACKET); + var index_list = parse_expression_list (); + Expression? stop = null; + if (index_list.size == 1 && accept (TokenType.COLON)) { + // slice expression + stop = parse_expression (); + } + expect (TokenType.CLOSE_BRACKET); + + if (stop == null) { + var expr = new ElementAccess (inner, get_src (begin)); + foreach (Expression index in index_list) { + expr.append_index (index); + } + return expr; + } else { + return new SliceExpression (inner, index_list[0], stop, get_src (begin)); + } + } + + List parse_expression_list () throws ParseError { + var list = new ArrayList (); + do { + list.add (parse_expression ()); + } while (accept (TokenType.COMMA)); + return list; + } + + Expression parse_this_access () throws ParseError { + var begin = get_location (); + expect (TokenType.THIS); + return new MemberAccess (null, "this", get_src (begin)); + } + + Expression parse_base_access () throws ParseError { + var begin = get_location (); + expect (TokenType.BASE); + return new BaseAccess (get_src (begin)); + } + + Expression parse_post_increment_expression (SourceLocation begin, Expression inner) throws ParseError { + expect (TokenType.OP_INC); + return new PostfixExpression (inner, true, get_src (begin)); + } + + Expression parse_post_decrement_expression (SourceLocation begin, Expression inner) throws ParseError { + expect (TokenType.OP_DEC); + return new PostfixExpression (inner, false, get_src (begin)); + } + + Expression parse_object_or_array_creation_expression () throws ParseError { + var begin = get_location (); + expect (TokenType.NEW); + var member = parse_member_name (); + if (accept (TokenType.OPEN_PARENS)) { + var expr = parse_object_creation_expression (begin, member); + return expr; + } else { + bool is_pointer_type = false; + while (accept (TokenType.STAR)) { + is_pointer_type = true; + } + if (!is_pointer_type) { + accept (TokenType.INTERR); + } + if (accept (TokenType.OPEN_BRACKET)) { + rollback (begin); + var expr = parse_array_creation_expression (); + return expr; + } else { + throw new ParseError.SYNTAX (get_error ("expected ( or [")); + } + } + } + + Expression parse_object_creation_expression (SourceLocation begin, MemberAccess member) throws ParseError { + member.creation_member = true; + var arg_list = parse_argument_list (); + expect (TokenType.CLOSE_PARENS); + var init_list = parse_object_initializer (); + + var expr = new ObjectCreationExpression (member, get_src (begin)); + foreach (Expression arg in arg_list) { + expr.add_argument (arg); + } + foreach (MemberInitializer initializer in init_list) { + expr.add_member_initializer (initializer); + } + return expr; + } + + Expression parse_object_literal (SourceLocation begin, MemberAccess member) throws ParseError { + member.creation_member = true; + + var expr = new ObjectCreationExpression (member, get_src (begin)); + + expect (TokenType.OPEN_BRACE); + + do { + var member_begin = get_location (); + string id = parse_identifier (); + expect (TokenType.COLON); + var member_expr = parse_expression (); + + expr.add_member_initializer (new MemberInitializer (id, member_expr, get_src (member_begin))); + } while (accept (TokenType.COMMA)); + + expect (TokenType.CLOSE_BRACE); + + return expr; + } + + Expression parse_array_creation_expression () throws ParseError { + var begin = get_location (); + expect (TokenType.NEW); + var member = parse_member_name (); + DataType element_type = UnresolvedType.new_from_expression (member); + bool is_pointer_type = false; + while (accept (TokenType.STAR)) { + element_type = new PointerType (element_type, get_src (begin)); + is_pointer_type = true; + } + if (!is_pointer_type) { + if (accept (TokenType.INTERR)) { + element_type.nullable = true; + } + } + expect (TokenType.OPEN_BRACKET); + + bool size_specified = false; + List size_specifier_list = null; + bool first = true; + do { + if (!first) { + // array of arrays: new T[][42] + + if (size_specified) { + throw new ParseError.SYNTAX (get_error ("size of inner arrays must not be specified in array creation expression")); + } + + element_type = new ArrayType (element_type, size_specifier_list.size, element_type.source_reference); + } else { + first = false; + } + + size_specifier_list = new ArrayList (); + do { + Expression size = null; + if (current () != TokenType.CLOSE_BRACKET && current () != TokenType.COMMA) { + size = parse_expression (); + size_specified = true; + } + size_specifier_list.add (size); + } while (context.profile != Profile.DOVA && accept (TokenType.COMMA)); + expect (TokenType.CLOSE_BRACKET); + } while (accept (TokenType.OPEN_BRACKET)); + + InitializerList initializer = null; + if (context.profile != Profile.DOVA && current () == TokenType.OPEN_BRACE) { + initializer = parse_initializer (); + } + var expr = new ArrayCreationExpression (element_type, size_specifier_list.size, initializer, get_src (begin)); + if (size_specified) { + foreach (Expression size in size_specifier_list) { + expr.append_size (size); + } + } + return expr; + } + + List parse_object_initializer () throws ParseError { + var list = new ArrayList (); + if (context.profile != Profile.DOVA && accept (TokenType.OPEN_BRACE)) { + do { + list.add (parse_member_initializer ()); + } while (accept (TokenType.COMMA)); + expect (TokenType.CLOSE_BRACE); + } + return list; + } + + MemberInitializer parse_member_initializer () throws ParseError { + var begin = get_location (); + string id = parse_identifier (); + expect (TokenType.ASSIGN); + var expr = parse_expression (); + + return new MemberInitializer (id, expr, get_src (begin)); + } + + Expression parse_yield_expression () throws ParseError { + expect (TokenType.YIELD); + var expr = parse_expression (); + + var call = expr as MethodCall; + if (call == null) { + Report.error (expr.source_reference, "syntax error, expected method call"); + throw new ParseError.SYNTAX ("expected method call"); + } + + call.is_yield_expression = true; + return call; + } + + Expression parse_sizeof_expression () throws ParseError { + var begin = get_location (); + expect (TokenType.SIZEOF); + expect (TokenType.OPEN_PARENS); + var type = parse_type (true, false); + expect (TokenType.CLOSE_PARENS); + + return new SizeofExpression (type, get_src (begin)); + } + + Expression parse_typeof_expression () throws ParseError { + var begin = get_location (); + expect (TokenType.TYPEOF); + expect (TokenType.OPEN_PARENS); + var type = parse_type (true, false); + expect (TokenType.CLOSE_PARENS); + + return new TypeofExpression (type, get_src (begin)); + } + + UnaryOperator get_unary_operator (TokenType token_type) { + switch (token_type) { + case TokenType.PLUS: return UnaryOperator.PLUS; + case TokenType.MINUS: return UnaryOperator.MINUS; + case TokenType.OP_NEG: return UnaryOperator.LOGICAL_NEGATION; + case TokenType.TILDE: return UnaryOperator.BITWISE_COMPLEMENT; + case TokenType.OP_INC: return UnaryOperator.INCREMENT; + case TokenType.OP_DEC: return UnaryOperator.DECREMENT; + default: return UnaryOperator.NONE; + } + } + + Expression parse_unary_expression () throws ParseError { + var begin = get_location (); + var operator = get_unary_operator (current ()); + if (operator != UnaryOperator.NONE) { + next (); + var op = parse_unary_expression (); + return new UnaryExpression (operator, op, get_src (begin)); + } + switch (current ()) { + case TokenType.HASH: + if (!context.deprecated) { + Report.warning (get_last_src (), "deprecated syntax, use `(owned)` cast"); + } + next (); + var op = parse_unary_expression (); + return new ReferenceTransferExpression (op, get_src (begin)); + case TokenType.OPEN_PARENS: + next (); + switch (current ()) { + case TokenType.OWNED: + // (owned) foo + next (); + if (accept (TokenType.CLOSE_PARENS)) { + var op = parse_unary_expression (); + return new ReferenceTransferExpression (op, get_src (begin)); + } + break; + case TokenType.VOID: + case TokenType.DYNAMIC: + case TokenType.IDENTIFIER: + var type = parse_type (true, false); + if (accept (TokenType.CLOSE_PARENS)) { + // check follower to decide whether to create cast expression + switch (current ()) { + case TokenType.OP_NEG: + case TokenType.TILDE: + case TokenType.OPEN_PARENS: + case TokenType.TRUE: + case TokenType.FALSE: + case TokenType.INTEGER_LITERAL: + case TokenType.REAL_LITERAL: + case TokenType.CHARACTER_LITERAL: + case TokenType.STRING_LITERAL: + case TokenType.TEMPLATE_STRING_LITERAL: + case TokenType.VERBATIM_STRING_LITERAL: + case TokenType.REGEX_LITERAL: + case TokenType.NULL: + case TokenType.THIS: + case TokenType.BASE: + case TokenType.NEW: + case TokenType.YIELD: + case TokenType.SIZEOF: + case TokenType.TYPEOF: + case TokenType.IDENTIFIER: + case TokenType.PARAMS: + var inner = parse_unary_expression (); + return new CastExpression (inner, type, get_src (begin), false); + default: + break; + } + } + break; + case TokenType.OP_NEG: + next (); + if (accept (TokenType.CLOSE_PARENS)) { + // (!) non-null cast + var inner = parse_unary_expression (); + return new CastExpression.non_null (inner, get_src (begin)); + } + break; + default: + break; + } + // no cast expression + rollback (begin); + break; + case TokenType.STAR: + next (); + var op = parse_unary_expression (); + return new PointerIndirection (op, get_src (begin)); + case TokenType.BITWISE_AND: + next (); + var op = parse_unary_expression (); + return new AddressofExpression (op, get_src (begin)); + default: + break; + } + + var expr = parse_primary_expression (); + return expr; + } + + BinaryOperator get_binary_operator (TokenType token_type) { + switch (token_type) { + case TokenType.STAR: return BinaryOperator.MUL; + case TokenType.DIV: return BinaryOperator.DIV; + case TokenType.PERCENT: return BinaryOperator.MOD; + case TokenType.PLUS: return BinaryOperator.PLUS; + case TokenType.MINUS: return BinaryOperator.MINUS; + case TokenType.OP_LT: return BinaryOperator.LESS_THAN; + case TokenType.OP_GT: return BinaryOperator.GREATER_THAN; + case TokenType.OP_LE: return BinaryOperator.LESS_THAN_OR_EQUAL; + case TokenType.OP_GE: return BinaryOperator.GREATER_THAN_OR_EQUAL; + case TokenType.OP_EQ: return BinaryOperator.EQUALITY; + case TokenType.OP_NE: return BinaryOperator.INEQUALITY; + default: return BinaryOperator.NONE; + } + } + + Expression parse_multiplicative_expression () throws ParseError { + var begin = get_location (); + var left = parse_unary_expression (); + bool found = true; + while (found) { + var operator = get_binary_operator (current ()); + switch (operator) { + case BinaryOperator.MUL: + case BinaryOperator.DIV: + case BinaryOperator.MOD: + next (); + var right = parse_unary_expression (); + left = new BinaryExpression (operator, left, right, get_src (begin)); + break; + default: + found = false; + break; + } + } + return left; + } + + Expression parse_additive_expression () throws ParseError { + var begin = get_location (); + var left = parse_multiplicative_expression (); + bool found = true; + while (found) { + var operator = get_binary_operator (current ()); + switch (operator) { + case BinaryOperator.PLUS: + case BinaryOperator.MINUS: + next (); + var right = parse_multiplicative_expression (); + left = new BinaryExpression (operator, left, right, get_src (begin)); + break; + default: + found = false; + break; + } + } + return left; + } + + Expression parse_shift_expression () throws ParseError { + var begin = get_location (); + var left = parse_additive_expression (); + bool found = true; + while (found) { + switch (current ()) { + case TokenType.OP_SHIFT_LEFT: + next (); + var right = parse_additive_expression (); + left = new BinaryExpression (BinaryOperator.SHIFT_LEFT, left, right, get_src (begin)); + break; + // don't use OP_SHIFT_RIGHT to support >> for nested generics + case TokenType.OP_GT: + char* first_gt_pos = tokens[index].begin.pos; + next (); + // only accept >> when there is no space between the two > signs + if (current () == TokenType.OP_GT && tokens[index].begin.pos == first_gt_pos + 1) { + next (); + var right = parse_additive_expression (); + left = new BinaryExpression (BinaryOperator.SHIFT_RIGHT, left, right, get_src (begin)); + } else { + prev (); + found = false; + } + break; + default: + found = false; + break; + } + } + return left; + } + + Expression parse_relational_expression () throws ParseError { + var begin = get_location (); + var left = parse_shift_expression (); + + bool first = true; + bool found = true; + while (found) { + var operator = get_binary_operator (current ()); + switch (operator) { + case BinaryOperator.LESS_THAN: + case BinaryOperator.LESS_THAN_OR_EQUAL: + case BinaryOperator.GREATER_THAN_OR_EQUAL: + next (); + var right = parse_shift_expression (); + left = new BinaryExpression (operator, left, right, get_src (begin)); + if (!first) { + var be = (BinaryExpression) left; + be.chained = true; + if (!context.experimental) { + Report.warning (left.source_reference, "chained relational expressions are experimental"); + } + } + first = false; + break; + case BinaryOperator.GREATER_THAN: + next (); + // ignore >> and >>= (two tokens due to generics) + if (current () != TokenType.OP_GT && current () != TokenType.OP_GE) { + var right = parse_shift_expression (); + left = new BinaryExpression (operator, left, right, get_src (begin)); + if (!first) { + var be = (BinaryExpression) left; + be.chained = true; + if (!context.experimental) { + Report.warning (left.source_reference, "chained relational expressions are experimental"); + } + } + first = false; + } else { + prev (); + found = false; + } + break; + default: + switch (current ()) { + case TokenType.IS: + next (); + var type = parse_type (true, false); + left = new TypeCheck (left, type, get_src (begin)); + break; + case TokenType.AS: + next (); + var type = parse_type (true, false); + left = new CastExpression (left, type, get_src (begin), true); + break; + default: + found = false; + break; + } + break; + } + } + return left; + } + + Expression parse_equality_expression () throws ParseError { + var begin = get_location (); + var left = parse_relational_expression (); + bool found = true; + while (found) { + var operator = get_binary_operator (current ()); + switch (operator) { + case BinaryOperator.EQUALITY: + case BinaryOperator.INEQUALITY: + next (); + var right = parse_relational_expression (); + left = new BinaryExpression (operator, left, right, get_src (begin)); + break; + default: + found = false; + break; + } + } + return left; + } + + Expression parse_and_expression () throws ParseError { + var begin = get_location (); + var left = parse_equality_expression (); + while (accept (TokenType.BITWISE_AND)) { + var right = parse_equality_expression (); + left = new BinaryExpression (BinaryOperator.BITWISE_AND, left, right, get_src (begin)); + } + return left; + } + + Expression parse_exclusive_or_expression () throws ParseError { + var begin = get_location (); + var left = parse_and_expression (); + while (accept (TokenType.CARRET)) { + var right = parse_and_expression (); + left = new BinaryExpression (BinaryOperator.BITWISE_XOR, left, right, get_src (begin)); + } + return left; + } + + Expression parse_inclusive_or_expression () throws ParseError { + var begin = get_location (); + var left = parse_exclusive_or_expression (); + while (accept (TokenType.BITWISE_OR)) { + var right = parse_exclusive_or_expression (); + left = new BinaryExpression (BinaryOperator.BITWISE_OR, left, right, get_src (begin)); + } + return left; + } + + Expression parse_in_expression () throws ParseError { + var begin = get_location (); + var left = parse_inclusive_or_expression (); + while (accept (TokenType.IN)) { + var right = parse_inclusive_or_expression (); + left = new BinaryExpression (BinaryOperator.IN, left, right, get_src (begin)); + } + return left; + } + + Expression parse_conditional_and_expression () throws ParseError { + var begin = get_location (); + var left = parse_in_expression (); + while (accept (TokenType.OP_AND)) { + var right = parse_in_expression (); + left = new BinaryExpression (BinaryOperator.AND, left, right, get_src (begin)); + } + return left; + } + + Expression parse_conditional_or_expression () throws ParseError { + var begin = get_location (); + var left = parse_conditional_and_expression (); + while (accept (TokenType.OP_OR)) { + var right = parse_conditional_and_expression (); + left = new BinaryExpression (BinaryOperator.OR, left, right, get_src (begin)); + } + return left; + } + + Expression parse_coalescing_expression () throws ParseError { + var begin = get_location (); + var left = parse_conditional_or_expression (); + if (accept (TokenType.OP_COALESCING)) { + var right = parse_coalescing_expression (); + return new BinaryExpression (BinaryOperator.COALESCE, left, right, get_src (begin)); + } else { + return left; + } + } + + Expression parse_conditional_expression () throws ParseError { + var begin = get_location (); + var condition = parse_coalescing_expression (); + if (accept (TokenType.INTERR)) { + var true_expr = parse_expression (); + expect (TokenType.COLON); + var false_expr = parse_expression (); + return new ConditionalExpression (condition, true_expr, false_expr, get_src (begin)); + } else { + return condition; + } + } + + Parameter parse_lambda_parameter () throws ParseError { + var begin = get_location (); + var direction = ParameterDirection.IN; + if (accept (TokenType.OUT)) { + direction = ParameterDirection.OUT; + } else if (accept (TokenType.REF)) { + direction = ParameterDirection.REF; + } + + string id = parse_identifier (); + + var param = new Parameter (id, null, get_src (begin)); + param.direction = direction; + return param; + } + + Expression parse_lambda_expression () throws ParseError { + var begin = get_location (); + List params = new ArrayList (); + if (accept (TokenType.OPEN_PARENS)) { + if (current () != TokenType.CLOSE_PARENS) { + do { + params.add (parse_lambda_parameter ()); + } while (accept (TokenType.COMMA)); + } + expect (TokenType.CLOSE_PARENS); + } else { + params.add (parse_lambda_parameter ()); + } + expect (TokenType.LAMBDA); + + LambdaExpression lambda; + if (current () == TokenType.OPEN_BRACE) { + var block = parse_block (); + lambda = new LambdaExpression.with_statement_body (block, get_src (begin)); + } else { + var expr = parse_expression (); + lambda = new LambdaExpression (expr, get_src (begin)); + } + foreach (var param in params) { + lambda.add_parameter (param); + } + return lambda; + } + + AssignmentOperator get_assignment_operator (TokenType token_type) { + switch (token_type) { + case TokenType.ASSIGN: return AssignmentOperator.SIMPLE; + case TokenType.ASSIGN_ADD: return AssignmentOperator.ADD; + case TokenType.ASSIGN_SUB: return AssignmentOperator.SUB; + case TokenType.ASSIGN_BITWISE_OR: return AssignmentOperator.BITWISE_OR; + case TokenType.ASSIGN_BITWISE_AND: return AssignmentOperator.BITWISE_AND; + case TokenType.ASSIGN_BITWISE_XOR: return AssignmentOperator.BITWISE_XOR; + case TokenType.ASSIGN_DIV: return AssignmentOperator.DIV; + case TokenType.ASSIGN_MUL: return AssignmentOperator.MUL; + case TokenType.ASSIGN_PERCENT: return AssignmentOperator.PERCENT; + case TokenType.ASSIGN_SHIFT_LEFT: return AssignmentOperator.SHIFT_LEFT; + default: return AssignmentOperator.NONE; + } + } + + Expression parse_expression () throws ParseError { + if (is_lambda_expression ()) { + return parse_lambda_expression (); + } + + var begin = get_location (); + + Expression expr = parse_conditional_expression (); + + while (true) { + var operator = get_assignment_operator (current ()); + if (operator != AssignmentOperator.NONE) { + next (); + var rhs = parse_expression (); + expr = new Assignment (expr, rhs, operator, get_src (begin)); + } else if (current () == TokenType.OP_GT) { // >>= + char* first_gt_pos = tokens[index].begin.pos; + next (); + // only accept >>= when there is no space between the two > signs + if (current () == TokenType.OP_GE && tokens[index].begin.pos == first_gt_pos + 1) { + next (); + var rhs = parse_expression (); + expr = new Assignment (expr, rhs, AssignmentOperator.SHIFT_RIGHT, get_src (begin)); + } else { + prev (); + break; + } + } else { + break; + } + } + + return expr; + } + + void parse_statements (Block block) throws ParseError { + while (current () != TokenType.CLOSE_BRACE + && current () != TokenType.CASE + && current () != TokenType.DEFAULT + && current () != TokenType.EOF) { + try { + Statement stmt = null; + bool is_decl = false; + + comment = scanner.pop_comment (); + switch (current ()) { + case TokenType.OPEN_BRACE: + stmt = parse_block (); + break; + case TokenType.SEMICOLON: + stmt = parse_empty_statement (); + break; + case TokenType.IF: + stmt = parse_if_statement (); + break; + case TokenType.SWITCH: + stmt = parse_switch_statement (); + break; + case TokenType.WHILE: + stmt = parse_while_statement (); + break; + case TokenType.DO: + stmt = parse_do_statement (); + break; + case TokenType.FOR: + stmt = parse_for_statement (); + break; + case TokenType.FOREACH: + stmt = parse_foreach_statement (); + break; + case TokenType.BREAK: + stmt = parse_break_statement (); + break; + case TokenType.CONTINUE: + stmt = parse_continue_statement (); + break; + case TokenType.RETURN: + stmt = parse_return_statement (); + break; + case TokenType.YIELD: + stmt = parse_yield_statement (); + break; + case TokenType.THROW: + stmt = parse_throw_statement (); + break; + case TokenType.TRY: + stmt = parse_try_statement (); + break; + case TokenType.LOCK: + stmt = parse_lock_statement (); + break; + case TokenType.DELETE: + stmt = parse_delete_statement (); + break; + case TokenType.VAR: + is_decl = true; + parse_local_variable_declarations (block); + break; + case TokenType.CONST: + is_decl = true; + parse_local_constant_declarations (block); + break; + case TokenType.OP_INC: + case TokenType.OP_DEC: + case TokenType.BASE: + case TokenType.THIS: + case TokenType.OPEN_PARENS: + case TokenType.STAR: + case TokenType.NEW: + stmt = parse_expression_statement (); + break; + default: + bool is_expr = is_expression (); + if (is_expr) { + stmt = parse_expression_statement (); + } else { + is_decl = true; + parse_local_variable_declarations (block); + } + break; + } + + if (!is_decl) { + if (context.profile == Profile.DOVA && stmt is ReturnStatement) { + // split + // return foo; + // into + // result = foo; + // return; + var ret_stmt = (ReturnStatement) stmt; + if (ret_stmt.return_expression != null) { + var assignment = new Assignment (new MemberAccess.simple ("result", stmt.source_reference), ret_stmt.return_expression, AssignmentOperator.SIMPLE, stmt.source_reference); + ret_stmt.return_expression = null; + block.add_statement (new ExpressionStatement (assignment, stmt.source_reference)); + } + } + block.add_statement (stmt); + } + } catch (ParseError e) { + if (recover () != RecoveryState.STATEMENT_BEGIN) { + // beginning of next declaration or end of file reached + // return what we have so far + break; + } + } + } + } + + bool is_expression () throws ParseError { + var begin = get_location (); + + // decide between declaration and expression statement + skip_type (); + switch (current ()) { + // invocation expression + case TokenType.OPEN_PARENS: + // postfix increment + case TokenType.OP_INC: + // postfix decrement + case TokenType.OP_DEC: + // assignments + case TokenType.ASSIGN: + case TokenType.ASSIGN_ADD: + case TokenType.ASSIGN_BITWISE_AND: + case TokenType.ASSIGN_BITWISE_OR: + case TokenType.ASSIGN_BITWISE_XOR: + case TokenType.ASSIGN_DIV: + case TokenType.ASSIGN_MUL: + case TokenType.ASSIGN_PERCENT: + case TokenType.ASSIGN_SHIFT_LEFT: + case TokenType.ASSIGN_SUB: + case TokenType.OP_GT: // >>= + // member access + case TokenType.DOT: + // pointer member access + case TokenType.OP_PTR: + rollback (begin); + return true; + default: + rollback (begin); + return false; + } + } + + bool is_lambda_expression () { + var begin = get_location (); + + switch (current ()) { + case TokenType.OUT: + case TokenType.REF: + next (); + if (accept (TokenType.IDENTIFIER) && accept (TokenType.LAMBDA)) { + rollback (begin); + return true; + } + break; + case TokenType.IDENTIFIER: + next (); + if (accept (TokenType.LAMBDA)) { + rollback (begin); + return true; + } + break; + case TokenType.OPEN_PARENS: + next (); + if (current () != TokenType.CLOSE_PARENS) { + do { + if (current () == TokenType.OUT || current () == TokenType.REF) { + next (); + } + if (!accept (TokenType.IDENTIFIER)) { + rollback (begin); + return false; + } + } while (accept (TokenType.COMMA)); + } + if (accept (TokenType.CLOSE_PARENS) && accept (TokenType.LAMBDA)) { + rollback (begin); + return true; + } + break; + } + + rollback (begin); + return false; + } + + Block parse_embedded_statement () throws ParseError { + if (current () == TokenType.OPEN_BRACE) { + var block = parse_block (); + return block; + } + + comment = scanner.pop_comment (); + + var block = new Block (get_src (get_location ())); + + var stmt = parse_embedded_statement_without_block (); + if (context.profile == Profile.DOVA && stmt is ReturnStatement) { + // split + // return foo; + // into + // result = foo; + // return; + var ret_stmt = (ReturnStatement) stmt; + if (ret_stmt.return_expression != null) { + var assignment = new Assignment (new MemberAccess.simple ("result"), ret_stmt.return_expression); + ret_stmt.return_expression = null; + block.add_statement (new ExpressionStatement (assignment)); + } + } + block.add_statement (stmt); + + return block; + + } + + Statement parse_embedded_statement_without_block () throws ParseError { + switch (current ()) { + case TokenType.SEMICOLON: return parse_empty_statement (); + case TokenType.IF: return parse_if_statement (); + case TokenType.SWITCH: return parse_switch_statement (); + case TokenType.WHILE: return parse_while_statement (); + case TokenType.DO: return parse_do_statement (); + case TokenType.FOR: return parse_for_statement (); + case TokenType.FOREACH: return parse_foreach_statement (); + case TokenType.BREAK: return parse_break_statement (); + case TokenType.CONTINUE: return parse_continue_statement (); + case TokenType.RETURN: return parse_return_statement (); + case TokenType.YIELD: return parse_yield_statement (); + case TokenType.THROW: return parse_throw_statement (); + case TokenType.TRY: return parse_try_statement (); + case TokenType.LOCK: return parse_lock_statement (); + case TokenType.DELETE: return parse_delete_statement (); + case TokenType.VAR: + case TokenType.CONST: + throw new ParseError.SYNTAX (get_error ("embedded statement cannot be declaration ")); + case TokenType.OP_INC: + case TokenType.OP_DEC: + case TokenType.BASE: + case TokenType.THIS: + case TokenType.OPEN_PARENS: + case TokenType.STAR: + case TokenType.NEW: + return parse_expression_statement (); + default: + if (is_expression ()) { + return parse_expression_statement (); + } else { + throw new ParseError.SYNTAX (get_error ("embedded statement cannot be declaration")); + } + } + } + + Block parse_block () throws ParseError { + var begin = get_location (); + expect (TokenType.OPEN_BRACE); + var block = new Block (get_src (begin)); + parse_statements (block); + if (!accept (TokenType.CLOSE_BRACE)) { + // only report error if it's not a secondary error + if (context.report.get_errors () == 0) { + Report.error (get_current_src (), "expected `}'"); + } + } + + block.source_reference.last_line = get_current_src ().last_line; + block.source_reference.last_column = get_current_src ().last_column; + + return block; + } + + Statement parse_empty_statement () throws ParseError { + var begin = get_location (); + expect (TokenType.SEMICOLON); + return new EmptyStatement (get_src (begin)); + } + + void parse_local_variable_declarations (Block block) throws ParseError { + DataType variable_type; + if (accept (TokenType.VAR)) { + variable_type = null; + } else { + variable_type = parse_type (true, true); + } + do { + if (variable_type == null && accept (TokenType.OPEN_PARENS)) { + // tuple + var begin = get_location (); + + string[] identifiers = {}; + do { + identifiers += parse_identifier (); + } while (accept (TokenType.COMMA)); + expect (TokenType.CLOSE_PARENS); + + expect (TokenType.ASSIGN); + var tuple = parse_expression (); + var tuple_local = new LocalVariable (null, CodeNode.get_temp_name (), tuple, get_src (begin)); + block.add_statement (new DeclarationStatement (tuple_local, tuple_local.source_reference)); + + for (int i = 0; i < identifiers.length; i++) { + var temp_access = new MemberAccess.simple (tuple_local.name, tuple_local.source_reference); + var ea = new ElementAccess (temp_access, tuple_local.source_reference); + ea.append_index (new IntegerLiteral (i.to_string ())); + var local = new LocalVariable (null, identifiers[i], ea, tuple_local.source_reference); + block.add_statement (new DeclarationStatement (local, local.source_reference)); + } + + continue; + } + + DataType type_copy = null; + if (variable_type != null) { + type_copy = variable_type.copy (); + } + var local = parse_local_variable (type_copy); + block.add_statement (new DeclarationStatement (local, local.source_reference)); + } while (accept (TokenType.COMMA)); + expect (TokenType.SEMICOLON); + } + + LocalVariable parse_local_variable (DataType? variable_type) throws ParseError { + var begin = get_location (); + string id = parse_identifier (); + + var type = parse_inline_array_type (variable_type); + + Expression initializer = null; + if (accept (TokenType.ASSIGN)) { + initializer = parse_expression (); + } + return new LocalVariable (type, id, initializer, get_src (begin)); + } + + void parse_local_constant_declarations (Block block) throws ParseError { + expect (TokenType.CONST); + var constant_type = parse_type (false, false); + do { + DataType type_copy = constant_type.copy (); + var local = parse_local_constant (type_copy); + block.add_statement (new DeclarationStatement (local, local.source_reference)); + block.add_local_constant (local); + local.active = false; + } while (accept (TokenType.COMMA)); + expect (TokenType.SEMICOLON); + } + + Constant parse_local_constant (DataType constant_type) throws ParseError { + var begin = get_location (); + string id = parse_identifier (); + + var type = parse_inline_array_type (constant_type); + + expect (TokenType.ASSIGN); + var initializer = parse_expression (); + + return new Constant (id, type, initializer, get_src (begin)); + } + + Statement parse_expression_statement () throws ParseError { + var begin = get_location (); + var expr = parse_statement_expression (); + expect (TokenType.SEMICOLON); + return new ExpressionStatement (expr, get_src (begin)); + } + + Expression parse_statement_expression () throws ParseError { + // invocation expression, assignment, + // or pre/post increment/decrement expression + var expr = parse_expression (); + return expr; + } + + Statement parse_if_statement () throws ParseError { + var begin = get_location (); + expect (TokenType.IF); + expect (TokenType.OPEN_PARENS); + var condition = parse_expression (); + expect (TokenType.CLOSE_PARENS); + var src = get_src (begin); + var true_stmt = parse_embedded_statement (); + Block false_stmt = null; + if (accept (TokenType.ELSE)) { + false_stmt = parse_embedded_statement (); + } + return new IfStatement (condition, true_stmt, false_stmt, src); + } + + Statement parse_switch_statement () throws ParseError { + var begin = get_location (); + expect (TokenType.SWITCH); + expect (TokenType.OPEN_PARENS); + var condition = parse_expression (); + expect (TokenType.CLOSE_PARENS); + var stmt = new SwitchStatement (condition, get_src (begin)); + expect (TokenType.OPEN_BRACE); + while (current () != TokenType.CLOSE_BRACE) { + var section = new SwitchSection (get_src (begin)); + do { + if (accept (TokenType.CASE)) { + section.add_label (new SwitchLabel (parse_expression (), get_src (begin))); + } else { + expect (TokenType.DEFAULT); + section.add_label (new SwitchLabel.with_default (get_src (begin))); + } + expect (TokenType.COLON); + } while (current () == TokenType.CASE || current () == TokenType.DEFAULT); + parse_statements (section); + stmt.add_section (section); + } + expect (TokenType.CLOSE_BRACE); + return stmt; + } + + Statement parse_while_statement () throws ParseError { + var begin = get_location (); + expect (TokenType.WHILE); + expect (TokenType.OPEN_PARENS); + var condition = parse_expression (); + expect (TokenType.CLOSE_PARENS); + var body = parse_embedded_statement (); + return new WhileStatement (condition, body, get_src (begin)); + } + + Statement parse_do_statement () throws ParseError { + var begin = get_location (); + expect (TokenType.DO); + var body = parse_embedded_statement (); + expect (TokenType.WHILE); + expect (TokenType.OPEN_PARENS); + var condition = parse_expression (); + expect (TokenType.CLOSE_PARENS); + expect (TokenType.SEMICOLON); + return new DoStatement (body, condition, get_src (begin)); + } + + Statement parse_for_statement () throws ParseError { + var begin = get_location (); + Block block = null; + expect (TokenType.FOR); + expect (TokenType.OPEN_PARENS); + var initializer_list = new ArrayList (); + if (!accept (TokenType.SEMICOLON)) { + bool is_expr; + switch (current ()) { + case TokenType.VAR: + is_expr = false; + break; + case TokenType.OP_INC: + case TokenType.OP_DEC: + is_expr = true; + break; + default: + is_expr = is_expression (); + break; + } + + if (is_expr) { + do { + initializer_list.add (parse_statement_expression ()); + } while (accept (TokenType.COMMA)); + expect (TokenType.SEMICOLON); + } else { + // variable declaration in initializer + block = new Block (get_src (begin)); + parse_local_variable_declarations (block); + } + } + Expression condition = null; + if (current () != TokenType.SEMICOLON) { + condition = parse_expression (); + } + expect (TokenType.SEMICOLON); + var iterator_list = new ArrayList (); + if (current () != TokenType.CLOSE_PARENS) { + do { + iterator_list.add (parse_statement_expression ()); + } while (accept (TokenType.COMMA)); + } + expect (TokenType.CLOSE_PARENS); + var src = get_src (begin); + var body = parse_embedded_statement (); + var stmt = new ForStatement (condition, body, src); + foreach (Expression init in initializer_list) { + stmt.add_initializer (init); + } + foreach (Expression iter in iterator_list) { + stmt.add_iterator (iter); + } + if (block != null) { + block.add_statement (stmt); + return block; + } else { + return stmt; + } + } + + Statement parse_foreach_statement () throws ParseError { + var begin = get_location (); + expect (TokenType.FOREACH); + expect (TokenType.OPEN_PARENS); + DataType type = null; + if (!accept (TokenType.VAR)) { + type = parse_type (true, true); + if (accept (TokenType.IN)) { + Report.error (type.source_reference, "syntax error, expected var or type"); + throw new ParseError.SYNTAX ("expected var or type"); + } + } + string id = parse_identifier (); + expect (TokenType.IN); + var collection = parse_expression (); + expect (TokenType.CLOSE_PARENS); + var src = get_src (begin); + var body = parse_embedded_statement (); + return new ForeachStatement (type, id, collection, body, src); + } + + Statement parse_break_statement () throws ParseError { + var begin = get_location (); + expect (TokenType.BREAK); + expect (TokenType.SEMICOLON); + return new BreakStatement (get_src (begin)); + } + + Statement parse_continue_statement () throws ParseError { + var begin = get_location (); + expect (TokenType.CONTINUE); + expect (TokenType.SEMICOLON); + return new ContinueStatement (get_src (begin)); + } + + Statement parse_return_statement () throws ParseError { + var begin = get_location (); + expect (TokenType.RETURN); + Expression expr = null; + if (current () != TokenType.SEMICOLON) { + expr = parse_expression (); + } + expect (TokenType.SEMICOLON); + return new ReturnStatement (expr, get_src (begin)); + } + + Statement parse_yield_statement () throws ParseError { + var begin = get_location (); + expect (TokenType.YIELD); + if (current () != TokenType.SEMICOLON && current () != TokenType.RETURN) { + // yield expression + prev (); + return parse_expression_statement (); + } + Expression expr = null; + if (accept (TokenType.RETURN)) { + expr = parse_expression (); + } + expect (TokenType.SEMICOLON); + return new YieldStatement (expr, get_src (begin)); + } + + Statement parse_throw_statement () throws ParseError { + var begin = get_location (); + expect (TokenType.THROW); + var expr = parse_expression (); + expect (TokenType.SEMICOLON); + return new ThrowStatement (expr, get_src (begin)); + } + + Statement parse_try_statement () throws ParseError { + var begin = get_location (); + expect (TokenType.TRY); + var try_block = parse_block (); + Block finally_clause = null; + var catch_clauses = new ArrayList (); + if (current () == TokenType.CATCH) { + parse_catch_clauses (catch_clauses); + if (current () == TokenType.FINALLY) { + finally_clause = parse_finally_clause (); + } + } else { + finally_clause = parse_finally_clause (); + } + var stmt = new TryStatement (try_block, finally_clause, get_src (begin)); + foreach (CatchClause clause in catch_clauses) { + stmt.add_catch_clause (clause); + } + return stmt; + } + + void parse_catch_clauses (List catch_clauses) throws ParseError { + while (accept (TokenType.CATCH)) { + var begin = get_location (); + DataType type = null; + string id = null; + if (accept (TokenType.OPEN_PARENS)) { + type = parse_type (true, true); + id = parse_identifier (); + expect (TokenType.CLOSE_PARENS); + } + var block = parse_block (); + catch_clauses.add (new CatchClause (type, id, block, get_src (begin))); + } + } + + Block parse_finally_clause () throws ParseError { + expect (TokenType.FINALLY); + var block = parse_block (); + return block; + } + + Statement parse_lock_statement () throws ParseError { + var begin = get_location (); + expect (TokenType.LOCK); + expect (TokenType.OPEN_PARENS); + var expr = parse_expression (); + expect (TokenType.CLOSE_PARENS); + var stmt = parse_embedded_statement (); + return new LockStatement (expr, stmt, get_src (begin)); + } + + Statement parse_delete_statement () throws ParseError { + var begin = get_location (); + expect (TokenType.DELETE); + var expr = parse_expression (); + expect (TokenType.SEMICOLON); + return new DeleteStatement (expr, get_src (begin)); + } + + string parse_attribute_value () throws ParseError { + switch (current ()) { + case TokenType.NULL: + case TokenType.TRUE: + case TokenType.FALSE: + case TokenType.INTEGER_LITERAL: + case TokenType.REAL_LITERAL: + case TokenType.STRING_LITERAL: + next (); + return get_last_string (); + case TokenType.MINUS: + next (); + switch (current ()) { + case TokenType.INTEGER_LITERAL: + case TokenType.REAL_LITERAL: + next (); + return "-" + get_last_string (); + default: + throw new ParseError.SYNTAX (get_error ("expected number")); + } + default: + throw new ParseError.SYNTAX (get_error ("expected literal")); + } + } + + List? parse_attributes () throws ParseError { + if (current () != TokenType.OPEN_BRACKET) { + return null; + } + var attrs = new ArrayList (); + while (accept (TokenType.OPEN_BRACKET)) { + do { + var begin = get_location (); + string id = parse_identifier (); + var attr = new Attribute (id, get_src (begin)); + if (accept (TokenType.OPEN_PARENS)) { + if (current () != TokenType.CLOSE_PARENS) { + do { + id = parse_identifier (); + expect (TokenType.ASSIGN); + attr.add_argument (id, parse_attribute_value ()); + } while (accept (TokenType.COMMA)); + } + expect (TokenType.CLOSE_PARENS); + } + attrs.add (attr); + } while (accept (TokenType.COMMA)); + expect (TokenType.CLOSE_BRACKET); + } + return attrs; + } + + void set_attributes (CodeNode node, List? attributes) { + if (attributes != null) { + foreach (Attribute attr in (List) attributes) { + if (node.get_attribute (attr.name) != null) { + Report.error (attr.source_reference, "duplicate attribute `%s`".printf (attr.name)); + } + node.attributes.append (attr); + } + } + } + + void parse_main_block (Symbol parent) throws ParseError { + var begin = get_location (); + + var method = new Method ("main", new VoidType (), get_src (begin)); + method.body = new Block (get_src (begin)); + parse_statements (method.body); + if (current () != TokenType.EOF) { + Report.error (get_current_src (), "expected end of file"); + } + + method.body.source_reference.last_line = get_current_src ().last_line; + method.body.source_reference.last_column = get_current_src ().last_column; + + if (!context.experimental && context.profile != Profile.DOVA) { + Report.warning (method.source_reference, "main blocks are experimental"); + } + + parent.add_method (method); + } + + void parse_declaration (Symbol parent, bool root = false) throws ParseError { + comment = scanner.pop_comment (); + var attrs = parse_attributes (); + + var begin = get_location (); + + TokenType last_keyword = current (); + + while (is_declaration_keyword (current ())) { + last_keyword = current (); + next (); + } + + switch (current ()) { + case TokenType.CONSTRUCT: + if (context.profile == Profile.GOBJECT) { + rollback (begin); + parse_constructor_declaration (parent, attrs); + return; + } + break; + case TokenType.TILDE: + rollback (begin); + parse_destructor_declaration (parent, attrs); + return; + case TokenType.OPEN_BRACE: + case TokenType.SEMICOLON: + case TokenType.IF: + case TokenType.SWITCH: + case TokenType.WHILE: + case TokenType.DO: + case TokenType.FOR: + case TokenType.FOREACH: + case TokenType.BREAK: + case TokenType.CONTINUE: + case TokenType.RETURN: + case TokenType.YIELD: + case TokenType.THROW: + case TokenType.TRY: + case TokenType.LOCK: + case TokenType.DELETE: + case TokenType.VAR: + case TokenType.OP_INC: + case TokenType.OP_DEC: + case TokenType.BASE: + case TokenType.THIS: + case TokenType.OPEN_PARENS: + case TokenType.STAR: + case TokenType.NEW: + // statement + if (attrs != null) { + // no attributes allowed before statements + throw new ParseError.SYNTAX (get_error ("expected statement")); + } + if (!root) { + throw new ParseError.SYNTAX (get_error ("statements outside blocks allowed only in root namespace")); + } + rollback (begin); + parse_main_block (parent); + return; + default: + if (root) { + bool is_expr = is_expression (); + if (is_expr) { + rollback (begin); + parse_main_block (parent); + return; + } + } + + skip_type (); + switch (current ()) { + case TokenType.OPEN_BRACE: + case TokenType.SEMICOLON: + case TokenType.COLON: + rollback (begin); + switch (last_keyword) { + case TokenType.CLASS: + parse_class_declaration (parent, attrs); + return; + case TokenType.ENUM: + parse_enum_declaration (parent, attrs); + return; + case TokenType.ERRORDOMAIN: + parse_errordomain_declaration (parent, attrs); + return; + case TokenType.INTERFACE: + parse_interface_declaration (parent, attrs); + return; + case TokenType.NAMESPACE: + parse_namespace_declaration (parent, attrs); + return; + case TokenType.STRUCT: + parse_struct_declaration (parent, attrs); + return; + default: + break; + } + break; + case TokenType.OPEN_PARENS: + rollback (begin); + parse_creation_method_declaration (parent, attrs); + return; + default: + skip_type (); // might contain type parameter list + switch (current ()) { + case TokenType.OPEN_PARENS: + rollback (begin); + switch (last_keyword) { + case TokenType.DELEGATE: + parse_delegate_declaration (parent, attrs); + return; + case TokenType.SIGNAL: + parse_signal_declaration (parent, attrs); + return; + default: + parse_method_declaration (parent, attrs); + return; + } + case TokenType.ASSIGN: + case TokenType.SEMICOLON: + rollback (begin); + switch (last_keyword) { + case TokenType.CONST: + parse_constant_declaration (parent, attrs); + return; + default: + parse_field_declaration (parent, attrs); + return; + } + case TokenType.OPEN_BRACE: + case TokenType.THROWS: + rollback (begin); + parse_property_declaration (parent, attrs); + return; + default: + break; + } + break; + } + break; + } + + rollback (begin); + + throw new ParseError.SYNTAX (get_error ("expected declaration")); + } + + void parse_declarations (Symbol parent, bool root = false) throws ParseError { + if (!root) { + expect (TokenType.OPEN_BRACE); + } + while (current () != TokenType.CLOSE_BRACE && current () != TokenType.EOF) { + try { + parse_declaration (parent, (parent == context.root)); + } catch (ParseError e) { + int r; + do { + r = recover (); + if (r == RecoveryState.STATEMENT_BEGIN) { + next (); + } else { + break; + } + } while (true); + if (r == RecoveryState.EOF) { + return; + } + } + } + if (!root) { + if (!accept (TokenType.CLOSE_BRACE)) { + // only report error if it's not a secondary error + if (context.report.get_errors () == 0) { + Report.error (get_current_src (), "expected `}'"); + } + } + } + } + + enum RecoveryState { + EOF, + DECLARATION_BEGIN, + STATEMENT_BEGIN + } + + RecoveryState recover () { + while (current () != TokenType.EOF) { + switch (current ()) { + case TokenType.ABSTRACT: + case TokenType.CLASS: + case TokenType.CONST: + case TokenType.CONSTRUCT: + case TokenType.DELEGATE: + case TokenType.ENUM: + case TokenType.ERRORDOMAIN: + case TokenType.EXTERN: + case TokenType.INLINE: + case TokenType.INTERFACE: + case TokenType.INTERNAL: + case TokenType.NAMESPACE: + case TokenType.NEW: + case TokenType.OVERRIDE: + case TokenType.PRIVATE: + case TokenType.PROTECTED: + case TokenType.PUBLIC: + case TokenType.SEALED: + case TokenType.SIGNAL: + case TokenType.STATIC: + case TokenType.STRUCT: + case TokenType.VIRTUAL: + case TokenType.VOLATILE: + return RecoveryState.DECLARATION_BEGIN; + case TokenType.BREAK: + case TokenType.CONTINUE: + case TokenType.DELETE: + case TokenType.DO: + case TokenType.FOR: + case TokenType.FOREACH: + case TokenType.IF: + case TokenType.LOCK: + case TokenType.RETURN: + case TokenType.SWITCH: + case TokenType.THROW: + case TokenType.TRY: + case TokenType.VAR: + case TokenType.WHILE: + case TokenType.YIELD: + return RecoveryState.STATEMENT_BEGIN; + default: + next (); + break; + } + } + return RecoveryState.EOF; + } + + void parse_namespace_declaration (Symbol parent, List? attrs) throws ParseError { + var begin = get_location (); + expect (TokenType.NAMESPACE); + var sym = parse_symbol_name (); + var ns = new Namespace (sym.name, get_src (begin)); + if (comment != null) { + ns.add_comment (comment); + comment = null; + } + + set_attributes (ns, attrs); + + expect (TokenType.OPEN_BRACE); + + var old_using_directives = scanner.source_file.current_using_directives; + parse_using_directives (ns); + + parse_declarations (ns, true); + + scanner.source_file.current_using_directives = old_using_directives; + + if (!accept (TokenType.CLOSE_BRACE)) { + // only report error if it's not a secondary error + if (context.report.get_errors () == 0) { + Report.error (get_current_src (), "expected `}'"); + } + } + + Symbol result = ns; + while (sym != null) { + sym = sym.inner; + + Symbol next = (sym != null ? new Namespace (sym.name, ns.source_reference) : parent); + next.add_namespace ((Namespace) result); + result = next; + } + } + + void parse_using_directives (Namespace ns) throws ParseError { + while (accept (TokenType.USING)) { + do { + var begin = get_location (); + var sym = parse_symbol_name (); + var ns_ref = new UsingDirective (sym, get_src (begin)); + scanner.source_file.add_using_directive (ns_ref); + ns.add_using_directive (ns_ref); + } while (accept (TokenType.COMMA)); + expect (TokenType.SEMICOLON); + } + } + + void parse_class_declaration (Symbol parent, List? attrs) throws ParseError { + var begin = get_location (); + var access = parse_access_modifier (); + var flags = parse_type_declaration_modifiers (); + expect (TokenType.CLASS); + var sym = parse_symbol_name (); + var type_param_list = parse_type_parameter_list (); + var base_types = new ArrayList (); + if (accept (TokenType.COLON)) { + do { + base_types.add (parse_type (true, false)); + } while (accept (TokenType.COMMA)); + } + + var cl = new Class (sym.name, get_src (begin), comment); + cl.access = access; + if (ModifierFlags.ABSTRACT in flags) { + cl.is_abstract = true; + } + if (ModifierFlags.EXTERN in flags || scanner.source_file.file_type == SourceFileType.PACKAGE) { + cl.external = true; + } + set_attributes (cl, attrs); + foreach (TypeParameter type_param in type_param_list) { + cl.add_type_parameter (type_param); + } + foreach (DataType base_type in base_types) { + cl.add_base_type (base_type); + } + + parse_declarations (cl); + + // ensure there is always a default construction method + if (scanner.source_file.file_type == SourceFileType.SOURCE + && cl.default_construction_method == null) { + var m = new CreationMethod (cl.name, null, cl.source_reference); + m.access = SymbolAccessibility.PUBLIC; + m.body = new Block (cl.source_reference); + cl.add_method (m); + } + + Symbol result = cl; + while (sym != null) { + sym = sym.inner; + + Symbol next = (sym != null ? new Namespace (sym.name, cl.source_reference) : parent); + if (result is Namespace) { + next.add_namespace ((Namespace) result); + } else { + next.add_class ((Class) result); + } + result = next; + } + } + + void parse_constant_declaration (Symbol parent, List? attrs) throws ParseError { + var begin = get_location (); + var access = parse_access_modifier (); + var flags = parse_member_declaration_modifiers (); + expect (TokenType.CONST); + var type = parse_type (false, false); + string id = parse_identifier (); + + type = parse_inline_array_type (type); + + Expression initializer = null; + if (accept (TokenType.ASSIGN)) { + initializer = parse_expression (); + } + expect (TokenType.SEMICOLON); + + // constant arrays don't own their element + var array_type = type as ArrayType; + if (array_type != null) { + array_type.element_type.value_owned = false; + } + + var c = new Constant (id, type, initializer, get_src (begin), comment); + c.access = access; + if (ModifierFlags.EXTERN in flags || scanner.source_file.file_type == SourceFileType.PACKAGE) { + c.external = true; + } + if (ModifierFlags.NEW in flags) { + c.hides = true; + } + set_attributes (c, attrs); + + parent.add_constant (c); + } + + void parse_field_declaration (Symbol parent, List? attrs) throws ParseError { + var begin = get_location (); + var access = parse_access_modifier (); + var flags = parse_member_declaration_modifiers (); + if (context.profile == Profile.DOVA) { + accept (TokenType.VOLATILE); + } + var type = parse_type (true, true); + string id = parse_identifier (); + + type = parse_inline_array_type (type); + + var f = new Field (id, type, null, get_src (begin), comment); + f.access = access; + set_attributes (f, attrs); + if (ModifierFlags.STATIC in flags) { + f.binding = MemberBinding.STATIC; + } else if (ModifierFlags.CLASS in flags) { + f.binding = MemberBinding.CLASS; + } + if (ModifierFlags.ABSTRACT in flags + || ModifierFlags.VIRTUAL in flags + || ModifierFlags.OVERRIDE in flags) { + Report.error (f.source_reference, "abstract, virtual, and override modifiers are not applicable to fields"); + } + if (ModifierFlags.EXTERN in flags || scanner.source_file.file_type == SourceFileType.PACKAGE) { + f.external = true; + } + if (ModifierFlags.NEW in flags) { + f.hides = true; + } + if (accept (TokenType.ASSIGN)) { + f.initializer = parse_expression (); + } + expect (TokenType.SEMICOLON); + + parent.add_field (f); + } + + InitializerList parse_initializer () throws ParseError { + var begin = get_location (); + expect (TokenType.OPEN_BRACE); + var initializer = new InitializerList (get_src (begin)); + if (current () != TokenType.CLOSE_BRACE) { + do { + var init = parse_argument (); + initializer.append (init); + } while (accept (TokenType.COMMA)); + } + expect (TokenType.CLOSE_BRACE); + return initializer; + } + + ListLiteral parse_list_literal () throws ParseError { + var begin = get_location (); + expect (TokenType.OPEN_BRACKET); + var initializer = new ListLiteral (get_src (begin)); + if (current () != TokenType.CLOSE_BRACKET) { + do { + var init = parse_expression (); + initializer.add_expression (init); + } while (accept (TokenType.COMMA)); + } + expect (TokenType.CLOSE_BRACKET); + return initializer; + } + + Expression parse_set_literal () throws ParseError { + var begin = get_location (); + expect (TokenType.OPEN_BRACE); + var set = new SetLiteral (get_src (begin)); + bool first = true; + if (current () != TokenType.CLOSE_BRACE) { + do { + var expr = parse_expression (); + if (first && accept (TokenType.COLON)) { + // found colon after expression, it's a map + rollback (begin); + return parse_map_literal (); + } + first = false; + set.add_expression (expr); + } while (accept (TokenType.COMMA)); + } + expect (TokenType.CLOSE_BRACE); + return set; + } + + Expression parse_map_literal () throws ParseError { + var begin = get_location (); + expect (TokenType.OPEN_BRACE); + var map = new MapLiteral (get_src (begin)); + if (current () != TokenType.CLOSE_BRACE) { + do { + var key = parse_expression (); + map.add_key (key); + expect (TokenType.COLON); + var value = parse_expression (); + map.add_value (value); + } while (accept (TokenType.COMMA)); + } + expect (TokenType.CLOSE_BRACE); + return map; + } + + void parse_method_declaration (Symbol parent, List? attrs) throws ParseError { + var begin = get_location (); + var access = parse_access_modifier (); + var flags = parse_member_declaration_modifiers (); + var type = parse_type (true, false); + string id = parse_identifier (); + var type_param_list = parse_type_parameter_list (); + var method = new Method (id, type, get_src (begin), comment); + method.access = access; + set_attributes (method, attrs); + foreach (TypeParameter type_param in type_param_list) { + method.add_type_parameter (type_param); + } + if (ModifierFlags.STATIC in flags) { + method.binding = MemberBinding.STATIC; + } else if (ModifierFlags.CLASS in flags) { + method.binding = MemberBinding.CLASS; + } + if (ModifierFlags.ASYNC in flags) { + method.coroutine = true; + } + if (ModifierFlags.NEW in flags) { + method.hides = true; + } + + if (method.binding == MemberBinding.INSTANCE) { + if (ModifierFlags.ABSTRACT in flags) { + method.is_abstract = true; + } + if (ModifierFlags.VIRTUAL in flags) { + method.is_virtual = true; + } + if (ModifierFlags.OVERRIDE in flags) { + method.overrides = true; + } + if ((method.is_abstract && method.is_virtual) + || (method.is_abstract && method.overrides) + || (method.is_virtual && method.overrides)) { + throw new ParseError.SYNTAX (get_error ("only one of `abstract', `virtual', or `override' may be specified")); + } + } else { + if (ModifierFlags.ABSTRACT in flags + || ModifierFlags.VIRTUAL in flags + || ModifierFlags.OVERRIDE in flags) { + throw new ParseError.SYNTAX (get_error ("the modifiers `abstract', `virtual', and `override' are not valid for static methods")); + } + } + + if (ModifierFlags.INLINE in flags) { + method.is_inline = true; + } + if (ModifierFlags.EXTERN in flags) { + method.external = true; + } + expect (TokenType.OPEN_PARENS); + if (current () != TokenType.CLOSE_PARENS) { + do { + var param = parse_parameter (); + method.add_parameter (param); + } while (accept (TokenType.COMMA)); + } + expect (TokenType.CLOSE_PARENS); + if (context.profile == Profile.DOVA) { + var error_type = new UnresolvedType.from_symbol (new UnresolvedSymbol (new UnresolvedSymbol (null, "Dova"), "Error"), method.source_reference); + method.add_error_type (error_type); + if (accept (TokenType.THROWS)) { + do { + parse_type (true, false); + } while (accept (TokenType.COMMA)); + Report.warning (method.source_reference, "`throws' is ignored in the Dova profile"); + } + } else { + if (accept (TokenType.THROWS)) { + do { + method.add_error_type (parse_type (true, false)); + } while (accept (TokenType.COMMA)); + } + } + while (accept (TokenType.REQUIRES)) { + expect (TokenType.OPEN_PARENS); + method.add_precondition (parse_expression ()); + expect (TokenType.CLOSE_PARENS); + } + while (accept (TokenType.ENSURES)) { + expect (TokenType.OPEN_PARENS); + method.add_postcondition (parse_expression ()); + expect (TokenType.CLOSE_PARENS); + } + if (!accept (TokenType.SEMICOLON)) { + method.body = parse_block (); + } else if (scanner.source_file.file_type == SourceFileType.PACKAGE) { + method.external = true; + } + + parent.add_method (method); + } + + void parse_property_declaration (Symbol parent, List? attrs) throws ParseError { + var begin = get_location (); + var access = parse_access_modifier (); + var flags = parse_member_declaration_modifiers (); + var type = parse_type (true, true); + + bool getter_owned = false; + if (context.profile == Profile.DOVA) { + getter_owned = true; + } else if (accept (TokenType.HASH)) { + if (!context.deprecated) { + Report.warning (get_last_src (), "deprecated syntax, use `owned` modifier before `get'"); + } + getter_owned = true; + } + + string id = parse_identifier (); + var prop = new Property (id, type, null, null, get_src (begin), comment); + prop.access = access; + set_attributes (prop, attrs); + if (ModifierFlags.STATIC in flags) { + prop.binding = MemberBinding.STATIC; + } else if (ModifierFlags.CLASS in flags) { + prop.binding = MemberBinding.CLASS; + } + if (ModifierFlags.ABSTRACT in flags) { + prop.is_abstract = true; + } + if (ModifierFlags.VIRTUAL in flags) { + prop.is_virtual = true; + } + if (ModifierFlags.OVERRIDE in flags) { + prop.overrides = true; + } + if (ModifierFlags.NEW in flags) { + prop.hides = true; + } + if (ModifierFlags.ASYNC in flags) { + Report.error (prop.source_reference, "async properties are not supported yet"); + } + if (ModifierFlags.EXTERN in flags || scanner.source_file.file_type == SourceFileType.PACKAGE) { + prop.external = true; + } + if (context.profile == Profile.DOVA) { + } else { + if (accept (TokenType.THROWS)) { + do { + prop.add_error_type (parse_type (true, false)); + } while (accept (TokenType.COMMA)); + Report.error (prop.source_reference, "properties throwing errors are not supported yet"); + } + } + expect (TokenType.OPEN_BRACE); + while (current () != TokenType.CLOSE_BRACE) { + if (accept (TokenType.DEFAULT)) { + if (prop.initializer != null) { + throw new ParseError.SYNTAX (get_error ("property default value already defined")); + } + expect (TokenType.ASSIGN); + prop.initializer = parse_expression (); + expect (TokenType.SEMICOLON); + } else { + comment = scanner.pop_comment (); + + var accessor_begin = get_location (); + var accessor_attrs = parse_attributes (); + var accessor_access = parse_access_modifier (SymbolAccessibility.PUBLIC); + + var value_type = type.copy (); + value_type.value_owned = (context.profile != Profile.DOVA && accept (TokenType.OWNED)); + + if (accept (TokenType.GET)) { + if (prop.get_accessor != null) { + throw new ParseError.SYNTAX (get_error ("property get accessor already defined")); + } + + if (getter_owned) { + value_type.value_owned = true; + } + + Block block = null; + if (!accept (TokenType.SEMICOLON)) { + block = parse_block (); + prop.external = false; + } + prop.get_accessor = new PropertyAccessor (true, false, false, value_type, block, get_src (accessor_begin), comment); + set_attributes (prop.get_accessor, accessor_attrs); + prop.get_accessor.access = accessor_access; + } else { + bool writable, _construct; + if (accept (TokenType.SET)) { + writable = true; + _construct = (context.profile == Profile.GOBJECT) && accept (TokenType.CONSTRUCT); + } else if (context.profile == Profile.GOBJECT && accept (TokenType.CONSTRUCT)) { + _construct = true; + writable = accept (TokenType.SET); + } else { + throw new ParseError.SYNTAX (get_error ("expected get, set, or construct")); + } + if (prop.set_accessor != null) { + throw new ParseError.SYNTAX (get_error ("property set accessor already defined")); + } + Block block = null; + if (!accept (TokenType.SEMICOLON)) { + block = parse_block (); + prop.external = false; + } + prop.set_accessor = new PropertyAccessor (false, writable, _construct, value_type, block, get_src (accessor_begin), comment); + set_attributes (prop.set_accessor, accessor_attrs); + prop.set_accessor.access = accessor_access; + } + } + } + expect (TokenType.CLOSE_BRACE); + + if (!prop.is_abstract && prop.source_type == SourceFileType.SOURCE) { + bool empty_get = (prop.get_accessor != null && prop.get_accessor.body == null); + bool empty_set = (prop.set_accessor != null && prop.set_accessor.body == null); + + if (empty_get != empty_set) { + if (empty_get) { + Report.error (prop.source_reference, "property getter must have a body"); + } else if (empty_set) { + Report.error (prop.source_reference, "property setter must have a body"); + } + prop.error = true; + } + + if (empty_get && empty_set) { + /* automatic property accessor body generation */ + var variable_type = prop.property_type.copy (); + prop.field = new Field ("_%s".printf (prop.name), variable_type, prop.initializer, prop.source_reference); + prop.field.access = SymbolAccessibility.PRIVATE; + prop.field.binding = prop.binding; + } else if (prop.initializer != null) { + Report.error (prop.initializer.source_reference, "only automatic properties can have default values"); + } + } + + parent.add_property (prop); + } + + void parse_signal_declaration (Symbol parent, List? attrs) throws ParseError { + var begin = get_location (); + var access = parse_access_modifier (); + var flags = parse_member_declaration_modifiers (); + expect (TokenType.SIGNAL); + var type = parse_type (true, false); + string id = parse_identifier (); + var sig = new Signal (id, type, get_src (begin), comment); + sig.access = access; + set_attributes (sig, attrs); + if (ModifierFlags.STATIC in flags) { + throw new ParseError.SYNTAX (get_error ("`static' modifier not allowed on signals")); + } else if (ModifierFlags.CLASS in flags) { + throw new ParseError.SYNTAX (get_error ("`class' modifier not allowed on signals")); + } + if (ModifierFlags.VIRTUAL in flags) { + sig.is_virtual = true; + } + if (ModifierFlags.NEW in flags) { + sig.hides = true; + } + expect (TokenType.OPEN_PARENS); + if (current () != TokenType.CLOSE_PARENS) { + do { + var param = parse_parameter (); + sig.add_parameter (param); + } while (accept (TokenType.COMMA)); + } + expect (TokenType.CLOSE_PARENS); + if (!accept (TokenType.SEMICOLON)) { + sig.body = parse_block (); + } + + parent.add_signal (sig); + } + + void parse_constructor_declaration (Symbol parent, List? attrs) throws ParseError { + var begin = get_location (); + var flags = parse_member_declaration_modifiers (); + expect (TokenType.CONSTRUCT); + if (ModifierFlags.NEW in flags) { + throw new ParseError.SYNTAX (get_error ("`new' modifier not allowed on constructor")); + } + var c = new Constructor (get_src (begin)); + if (ModifierFlags.STATIC in flags) { + c.binding = MemberBinding.STATIC; + } else if (ModifierFlags.CLASS in flags) { + c.binding = MemberBinding.CLASS; + } + c.body = parse_block (); + + parent.add_constructor (c); + } + + void parse_destructor_declaration (Symbol parent, List? attrs) throws ParseError { + var begin = get_location (); + var flags = parse_member_declaration_modifiers (); + expect (TokenType.TILDE); + parse_identifier (); + expect (TokenType.OPEN_PARENS); + expect (TokenType.CLOSE_PARENS); + if (ModifierFlags.NEW in flags) { + throw new ParseError.SYNTAX (get_error ("`new' modifier not allowed on destructor")); + } + var d = new Destructor (get_src (begin)); + if (ModifierFlags.STATIC in flags) { + d.binding = MemberBinding.STATIC; + } else if (ModifierFlags.CLASS in flags) { + d.binding = MemberBinding.CLASS; + } + d.body = parse_block (); + + parent.add_destructor (d); + } + + void parse_struct_declaration (Symbol parent, List? attrs) throws ParseError { + var begin = get_location (); + var access = parse_access_modifier (); + var flags = parse_type_declaration_modifiers (); + expect (TokenType.STRUCT); + var sym = parse_symbol_name (); + var type_param_list = parse_type_parameter_list (); + DataType base_type = null; + if (accept (TokenType.COLON)) { + base_type = parse_type (true, false); + } + var st = new Struct (sym.name, get_src (begin), comment); + st.access = access; + if (ModifierFlags.EXTERN in flags || scanner.source_file.file_type == SourceFileType.PACKAGE) { + st.external = true; + } + set_attributes (st, attrs); + foreach (TypeParameter type_param in type_param_list) { + st.add_type_parameter (type_param); + } + if (base_type != null) { + st.base_type = base_type; + } + + parse_declarations (st); + + Symbol result = st; + while (sym != null) { + sym = sym.inner; + + Symbol next = (sym != null ? new Namespace (sym.name, st.source_reference) : parent); + if (result is Namespace) { + next.add_namespace ((Namespace) result); + } else { + next.add_struct ((Struct) result); + } + result = next; + } + } + + void parse_interface_declaration (Symbol parent, List? attrs) throws ParseError { + var begin = get_location (); + var access = parse_access_modifier (); + var flags = parse_type_declaration_modifiers (); + expect (TokenType.INTERFACE); + var sym = parse_symbol_name (); + var type_param_list = parse_type_parameter_list (); + var base_types = new ArrayList (); + if (accept (TokenType.COLON)) { + do { + var type = parse_type (true, false); + base_types.add (type); + } while (accept (TokenType.COMMA)); + } + var iface = new Interface (sym.name, get_src (begin), comment); + iface.access = access; + if (ModifierFlags.EXTERN in flags || scanner.source_file.file_type == SourceFileType.PACKAGE) { + iface.external = true; + } + set_attributes (iface, attrs); + foreach (TypeParameter type_param in type_param_list) { + iface.add_type_parameter (type_param); + } + foreach (DataType base_type in base_types) { + iface.add_prerequisite (base_type); + } + + parse_declarations (iface); + + Symbol result = iface; + while (sym != null) { + sym = sym.inner; + + Symbol next = (sym != null ? new Namespace (sym.name, iface.source_reference) : parent); + if (result is Namespace) { + next.add_namespace ((Namespace) result); + } else { + next.add_interface ((Interface) result); + } + result = next; + } + } + + void parse_enum_declaration (Symbol parent, List? attrs) throws ParseError { + var begin = get_location (); + var access = parse_access_modifier (); + var flags = parse_type_declaration_modifiers (); + expect (TokenType.ENUM); + var sym = parse_symbol_name (); + var en = new Enum (sym.name, get_src (begin), comment); + en.access = access; + if (ModifierFlags.EXTERN in flags || scanner.source_file.file_type == SourceFileType.PACKAGE) { + en.external = true; + } + set_attributes (en, attrs); + + expect (TokenType.OPEN_BRACE); + do { + if (current () == TokenType.CLOSE_BRACE + && en.get_values ().size > 0) { + // allow trailing comma + break; + } + var value_attrs = parse_attributes (); + var value_begin = get_location (); + string id = parse_identifier (); + comment = scanner.pop_comment (); + + Expression value = null; + if (accept (TokenType.ASSIGN)) { + value = parse_expression (); + } + + var ev = new EnumValue (id, value, get_src (value_begin), comment); + ev.access = SymbolAccessibility.PUBLIC; + set_attributes (ev, value_attrs); + en.add_value (ev); + } while (accept (TokenType.COMMA)); + if (accept (TokenType.SEMICOLON)) { + // enum methods + while (current () != TokenType.CLOSE_BRACE) { + parse_declaration (en); + } + } + expect (TokenType.CLOSE_BRACE); + + Symbol result = en; + while (sym != null) { + sym = sym.inner; + + Symbol next = (sym != null ? new Namespace (sym.name, en.source_reference) : parent); + if (result is Namespace) { + next.add_namespace ((Namespace) result); + } else { + next.add_enum ((Enum) result); + } + result = next; + } + } + + void parse_errordomain_declaration (Symbol parent, List? attrs) throws ParseError { + var begin = get_location (); + var access = parse_access_modifier (); + var flags = parse_type_declaration_modifiers (); + expect (TokenType.ERRORDOMAIN); + var sym = parse_symbol_name (); + var ed = new ErrorDomain (sym.name, get_src (begin), comment); + ed.access = access; + if (ModifierFlags.EXTERN in flags || scanner.source_file.file_type == SourceFileType.PACKAGE) { + ed.external = true; + } + set_attributes (ed, attrs); + + expect (TokenType.OPEN_BRACE); + do { + if (current () == TokenType.CLOSE_BRACE + && ed.get_codes ().size > 0) { + // allow trailing comma + break; + } + var code_attrs = parse_attributes (); + var code_begin = get_location (); + string id = parse_identifier (); + comment = scanner.pop_comment (); + var ec = new ErrorCode (id, get_src (code_begin), comment); + set_attributes (ec, code_attrs); + if (accept (TokenType.ASSIGN)) { + ec.value = parse_expression (); + } + ed.add_code (ec); + } while (accept (TokenType.COMMA)); + if (accept (TokenType.SEMICOLON)) { + // errordomain methods + while (current () != TokenType.CLOSE_BRACE) { + parse_declaration (ed); + } + } + expect (TokenType.CLOSE_BRACE); + + Symbol result = ed; + while (sym != null) { + sym = sym.inner; + + Symbol next = (sym != null ? new Namespace (sym.name, ed.source_reference) : parent); + if (result is Namespace) { + next.add_namespace ((Namespace) result); + } else { + next.add_error_domain ((ErrorDomain) result); + } + result = next; + } + } + + SymbolAccessibility parse_access_modifier (SymbolAccessibility default_access = SymbolAccessibility.PRIVATE) { + switch (current ()) { + case TokenType.PRIVATE: + next (); + return SymbolAccessibility.PRIVATE; + case TokenType.PROTECTED: + next (); + return SymbolAccessibility.PROTECTED; + case TokenType.INTERNAL: + next (); + return SymbolAccessibility.INTERNAL; + case TokenType.PUBLIC: + next (); + return SymbolAccessibility.PUBLIC; + default: + return default_access; + } + } + + ModifierFlags parse_type_declaration_modifiers () { + ModifierFlags flags = 0; + while (true) { + switch (current ()) { + case TokenType.ABSTRACT: + next (); + flags |= ModifierFlags.ABSTRACT; + break; + case TokenType.EXTERN: + next (); + flags |= ModifierFlags.EXTERN; + break; + case TokenType.SEALED: + next (); + flags |= ModifierFlags.SEALED; + break; + default: + return flags; + } + } + } + + ModifierFlags parse_member_declaration_modifiers () { + ModifierFlags flags = 0; + while (true) { + switch (current ()) { + case TokenType.ABSTRACT: + next (); + flags |= ModifierFlags.ABSTRACT; + break; + case TokenType.ASYNC: + next (); + flags |= ModifierFlags.ASYNC; + break; + case TokenType.CLASS: + next (); + flags |= ModifierFlags.CLASS; + break; + case TokenType.EXTERN: + next (); + flags |= ModifierFlags.EXTERN; + break; + case TokenType.INLINE: + next (); + flags |= ModifierFlags.INLINE; + break; + case TokenType.NEW: + next (); + flags |= ModifierFlags.NEW; + break; + case TokenType.OVERRIDE: + next (); + flags |= ModifierFlags.OVERRIDE; + break; + case TokenType.SEALED: + next (); + flags |= ModifierFlags.SEALED; + break; + case TokenType.STATIC: + next (); + flags |= ModifierFlags.STATIC; + break; + case TokenType.VIRTUAL: + next (); + flags |= ModifierFlags.VIRTUAL; + break; + default: + return flags; + } + } + } + + Parameter parse_parameter () throws ParseError { + var attrs = parse_attributes (); + var begin = get_location (); + if (accept (TokenType.ELLIPSIS)) { + // varargs + return new Parameter.with_ellipsis (get_src (begin)); + } + bool params_array = accept (TokenType.PARAMS); + var direction = ParameterDirection.IN; + if (accept (TokenType.OUT)) { + direction = ParameterDirection.OUT; + } else if (accept (TokenType.REF)) { + direction = ParameterDirection.REF; + } + + if (context.profile == Profile.DOVA) { + accept (TokenType.VOLATILE); + } + DataType type; + if (direction == ParameterDirection.IN) { + // in parameters are unowned by default + type = parse_type (false, false); + } else if (direction == ParameterDirection.REF) { + // ref parameters own the value by default + type = parse_type (true, true); + } else { + // out parameters own the value by default + type = parse_type (true, false); + } + string id = parse_identifier (); + + type = parse_inline_array_type (type); + + var param = new Parameter (id, type, get_src (begin)); + set_attributes (param, attrs); + param.direction = direction; + param.params_array = params_array; + if (accept (TokenType.ASSIGN)) { + param.initializer = parse_expression (); + } + return param; + } + + void parse_creation_method_declaration (Symbol parent, List? attrs) throws ParseError { + var begin = get_location (); + var access = parse_access_modifier (); + var flags = parse_member_declaration_modifiers (); + var sym = parse_symbol_name (); + if (ModifierFlags.NEW in flags) { + throw new ParseError.SYNTAX (get_error ("`new' modifier not allowed on creation method")); + } + CreationMethod method; + if (sym.inner == null) { + method = new CreationMethod (sym.name, null, get_src (begin), comment); + } else { + method = new CreationMethod (sym.inner.name, sym.name, get_src (begin), comment); + } + if (ModifierFlags.EXTERN in flags) { + method.external = true; + } + if (ModifierFlags.ABSTRACT in flags + || ModifierFlags.VIRTUAL in flags + || ModifierFlags.OVERRIDE in flags) { + Report.error (method.source_reference, "abstract, virtual, and override modifiers are not applicable to creation methods"); + } + if (ModifierFlags.ASYNC in flags) { + method.coroutine = true; + } + expect (TokenType.OPEN_PARENS); + if (current () != TokenType.CLOSE_PARENS) { + do { + var param = parse_parameter (); + method.add_parameter (param); + } while (accept (TokenType.COMMA)); + } + expect (TokenType.CLOSE_PARENS); + if (context.profile == Profile.DOVA) { + var error_type = new UnresolvedType.from_symbol (new UnresolvedSymbol (new UnresolvedSymbol (null, "Dova"), "Error"), method.source_reference); + method.add_error_type (error_type); + if (accept (TokenType.THROWS)) { + do { + parse_type (true, false); + } while (accept (TokenType.COMMA)); + Report.warning (method.source_reference, "`throws' is ignored in the Dova profile"); + } + } else { + if (accept (TokenType.THROWS)) { + do { + method.add_error_type (parse_type (true, false)); + } while (accept (TokenType.COMMA)); + } + } + while (accept (TokenType.REQUIRES)) { + expect (TokenType.OPEN_PARENS); + method.add_precondition (parse_expression ()); + expect (TokenType.CLOSE_PARENS); + } + while (accept (TokenType.ENSURES)) { + expect (TokenType.OPEN_PARENS); + method.add_postcondition (parse_expression ()); + expect (TokenType.CLOSE_PARENS); + } + method.access = access; + set_attributes (method, attrs); + if (!accept (TokenType.SEMICOLON)) { + method.body = parse_block (); + } else if (scanner.source_file.file_type == SourceFileType.PACKAGE) { + method.external = true; + } + + parent.add_method (method); + } + + void parse_delegate_declaration (Symbol parent, List? attrs) throws ParseError { + var begin = get_location (); + var access = parse_access_modifier (); + var flags = parse_member_declaration_modifiers (); + expect (TokenType.DELEGATE); + if (ModifierFlags.NEW in flags) { + throw new ParseError.SYNTAX (get_error ("`new' modifier not allowed on delegates")); + } + var type = parse_type (true, false); + var sym = parse_symbol_name (); + var type_param_list = parse_type_parameter_list (); + var d = new Delegate (sym.name, type, get_src (begin), comment); + d.access = access; + set_attributes (d, attrs); + if (ModifierFlags.STATIC in flags) { + if (!context.deprecated) { + // TODO enable warning in future releases + Report.warning (get_last_src (), "deprecated syntax, use [CCode (has_target = false)]"); + } + d.has_target = false; + } + if (ModifierFlags.EXTERN in flags || scanner.source_file.file_type == SourceFileType.PACKAGE) { + d.external = true; + } + foreach (TypeParameter type_param in type_param_list) { + d.add_type_parameter (type_param); + } + expect (TokenType.OPEN_PARENS); + if (current () != TokenType.CLOSE_PARENS) { + do { + var param = parse_parameter (); + d.add_parameter (param); + } while (accept (TokenType.COMMA)); + } + expect (TokenType.CLOSE_PARENS); + if (context.profile == Profile.DOVA) { + var error_type = new UnresolvedType.from_symbol (new UnresolvedSymbol (new UnresolvedSymbol (null, "Dova"), "Error"), d.source_reference); + d.add_error_type (error_type); + if (accept (TokenType.THROWS)) { + do { + parse_type (true, false); + } while (accept (TokenType.COMMA)); + Report.warning (d.source_reference, "`throws' is ignored in the Dova profile"); + } + } else { + if (accept (TokenType.THROWS)) { + do { + d.add_error_type (parse_type (true, false)); + } while (accept (TokenType.COMMA)); + } + } + expect (TokenType.SEMICOLON); + + Symbol result = d; + while (sym != null) { + sym = sym.inner; + + Symbol next = (sym != null ? new Namespace (sym.name, d.source_reference) : parent); + if (result is Namespace) { + next.add_namespace ((Namespace) result); + } else { + next.add_delegate ((Delegate) result); + } + result = next; + } + } + + List parse_type_parameter_list () throws ParseError { + if (accept (TokenType.OP_LT)) { + var list = new ArrayList (); + do { + var begin = get_location (); + string id = parse_identifier (); + list.add (new TypeParameter (id, get_src (begin))); + } while (accept (TokenType.COMMA)); + expect (TokenType.OP_GT); + return list; + } else { + if (_empty_type_parameter_list == null) { + _empty_type_parameter_list = new ArrayList (); + } + return _empty_type_parameter_list; + } + } + + void skip_type_argument_list () throws ParseError { + if (accept (TokenType.OP_LT)) { + do { + skip_type (); + } while (accept (TokenType.COMMA)); + expect (TokenType.OP_GT); + } + } + + // try to parse type argument list + List? parse_type_argument_list (bool maybe_expression) throws ParseError { + var begin = get_location (); + if (accept (TokenType.OP_LT)) { + var list = new ArrayList (); + do { + switch (current ()) { + case TokenType.VOID: + case TokenType.DYNAMIC: + case TokenType.UNOWNED: + case TokenType.WEAK: + case TokenType.IDENTIFIER: + var type = parse_type (true, true); + list.add (type); + break; + default: + rollback (begin); + return null; + } + } while (accept (TokenType.COMMA)); + if (!accept (TokenType.OP_GT)) { + rollback (begin); + return null; + } + if (maybe_expression) { + // check follower to decide whether to keep type argument list + switch (current ()) { + case TokenType.OPEN_PARENS: + case TokenType.CLOSE_PARENS: + case TokenType.CLOSE_BRACKET: + case TokenType.OPEN_BRACE: + case TokenType.COLON: + case TokenType.SEMICOLON: + case TokenType.COMMA: + case TokenType.DOT: + case TokenType.INTERR: + case TokenType.OP_EQ: + case TokenType.OP_NE: + // keep type argument list + break; + default: + // interpret tokens as expression + rollback (begin); + return null; + } + } + return list; + } + return null; + } + + MemberAccess parse_member_name (Expression? base_expr = null) throws ParseError { + var begin = get_location (); + MemberAccess expr = null; + bool first = true; + do { + string id = parse_identifier (); + + // The first member access can be global:: qualified + bool qualified = false; + if (first && id == "global" && accept (TokenType.DOUBLE_COLON)) { + id = parse_identifier (); + qualified = true; + } + + List type_arg_list = parse_type_argument_list (false); + expr = new MemberAccess (expr != null ? expr : base_expr, id, get_src (begin)); + expr.qualified = qualified; + if (type_arg_list != null) { + foreach (DataType type_arg in type_arg_list) { + expr.add_type_argument (type_arg); + } + } + + first = false; + } while (accept (TokenType.DOT)); + return expr; + } + + bool is_declaration_keyword (TokenType type) { + switch (type) { + case TokenType.ABSTRACT: + case TokenType.ASYNC: + case TokenType.CLASS: + case TokenType.CONST: + case TokenType.DELEGATE: + case TokenType.ENUM: + case TokenType.ERRORDOMAIN: + case TokenType.EXTERN: + case TokenType.INLINE: + case TokenType.INTERFACE: + case TokenType.INTERNAL: + case TokenType.NAMESPACE: + case TokenType.NEW: + case TokenType.OVERRIDE: + case TokenType.PRIVATE: + case TokenType.PROTECTED: + case TokenType.PUBLIC: + case TokenType.SEALED: + case TokenType.SIGNAL: + case TokenType.STATIC: + case TokenType.STRUCT: + case TokenType.VIRTUAL: + case TokenType.VOLATILE: + return true; + default: + return false; + } + } +} + +public errordomain Vala.ParseError { + FAILED, + SYNTAX +} + diff --git a/tests/vera/arb.if.vri b/tests/vera/arb.if.vri new file mode 100644 index 0000000..21b8168 --- /dev/null +++ b/tests/vera/arb.if.vri @@ -0,0 +1,6 @@ +interface arb { + input clk CLOCK ; + output reset PHOLD #1 ; + output [1:0] request PHOLD #1 ; + input [1:0] grant PSAMPLE #-1 ; +} // end of interface arb diff --git a/tests/vera/class_a.vr b/tests/vera/class_a.vr new file mode 100644 index 0000000..5bd0a43 --- /dev/null +++ b/tests/vera/class_a.vr @@ -0,0 +1,6 @@ +// Below code can be in another file +class A { + task print () { + printf("I am in seprate file\n"); + } +} diff --git a/tests/vera/class_extension.vr b/tests/vera/class_extension.vr new file mode 100644 index 0000000..8f99483 --- /dev/null +++ b/tests/vera/class_extension.vr @@ -0,0 +1,99 @@ +// Virtual class for body of any driver +virtual class verif { + // This starts all the threads + virtual task startSim(); + // This stops all the threads + virtual task stopSim(); + // This prints all the stats + virtual task printStats(); + // This check if driver is done or not + virtual function bit isDone () { + isDone = 0; + } + // set the driver config + virtual task setConfig(integer item, integer value); + virtual function integer getConfig(integer item) { + getConfig = 32'hDEAD_BEAF; + } +} +// ethernet inherits verif +class ethernet extends verif { + integer min_frame_size; + integer max_frame_size; + task new() { + min_frame_size = 32'h40; + max_frame_size = 32'h200; + } + task startSim() { + printf("Starting Simulation\n"); + } + task stopSim() { + printf("Stopping Simulation\n"); + } + task printStats() { + printf("Sent normal frames %d\n",100); + printf("Sent runt frames %d\n",1); + printf("Sent oversize frames %d\n",1); + } + function bit isDone() { + isDone = 1; + } + task setConfig(integer item, integer value) { + case(item) { + 0 : min_frame_size = value; + 1 : max_frame_size = value; + } + } + function integer getConfig(integer item) { + case(item) { + 0 : getConfig = min_frame_size; + 1 : getConfig = max_frame_size; + default : { + printf("Calling super.setConfig\n"); + getConfig = super.getConfig(item); + } + } + } +} + +class ethernet2 extends ethernet { + integer min_ipg; + task new() { + min_ipg = 32'hc; + } + task setConfig(integer item, integer value) { + case(item) { + 2 : min_ipg = value; + default : { + printf("Calling super.setConfig\n"); + super.setConfig(item,value); + } + } + } + function integer getConfig(integer item) { + case(item) { + 2 : getConfig = min_ipg; + default : { + printf("Calling super.setConfig\n"); + getConfig = super.getConfig(item); + } + } + } +} + +program class_extension { + ethernet2 eth = new(); + eth.setConfig(0,32'h100); + eth.setConfig(2,32'h24); + printf ("Value of min_frame is %0x\n", eth.getConfig(0)); + printf ("Value of max_frame is %0x\n", eth.getConfig(1)); + printf ("Value of min_ipg is %0x\n", eth.getConfig(2)); + printf ("Value of unknown is %0x\n", eth.getConfig(3)); + + eth.startSim(); + while (eth.isDone() == 0) { + delay(1); + } + eth.stopSim(); + eth.printStats(); +} diff --git a/tests/vera/copy_object.vr b/tests/vera/copy_object.vr new file mode 100644 index 0000000..65639df --- /dev/null +++ b/tests/vera/copy_object.vr @@ -0,0 +1,29 @@ +class A{ + integer j; + task new(){ + j = 100; + } +} + +class B { + integer i; + A a; + task new() { + i = 200; + } +} + +program copy_object { + B b1 = new(); // Create an object of class B + B b2; //Create a null variable of class B + b1.a = new; //Create an object of class A + b2 = new b1; // Create an object that is a copy of b1, + //but only copies the handle a, not the object referenced by a. + b2.i = 300; // i is changed in b2, but not b1 + printf("i in b2 = %0d\n", b2.i);// i equals 10 + printf("i in b1 = %0d\n", b1.i);// i equals 1 + //where as: + b2.a.j = 400; // Change j in the object referenced + // by a. j is shared by both b1 and b2 + printf("j is %0d in b1 and %0d in b2\n", b1.a.j, b2.a.j); +} diff --git a/tests/vera/enum_t.vr b/tests/vera/enum_t.vr new file mode 100644 index 0000000..93c713c --- /dev/null +++ b/tests/vera/enum_t.vr @@ -0,0 +1,13 @@ +enum pkt_size {NORMAL, RUNT, OVERSIZE}; +enum pkt_type {UNICAST=11,MULTICAST,BROADCAST}; + +program enum_t { + pkt_size size = NORMAL; + pkt_type type = MULTICAST; + // Print the enum value + printf("Packet size is %d\n", size); + printf("Packet type is %d\n", type); + // Print the enum name + printf("Packet size is %s\n", size); + printf("Packet type is %s\n", type); +} diff --git a/tests/vera/properties.vr b/tests/vera/properties.vr new file mode 100644 index 0000000..72a8100 --- /dev/null +++ b/tests/vera/properties.vr @@ -0,0 +1,60 @@ +class A { + public integer data; + local integer addr; + protected integer cmd; + static integer credits; + task new() { + data = 100; + addr = 200; + cmd = 1; + credits = 10; + } + task printA() { + printf ("value of data %0d in A\n", data); + printf ("value of addr %0d in A\n", addr); + printf ("value of cmd %0d in A\n", cmd); + } +} + +class B extends A { + task printB() { + printf ("value of data %0d in B\n", data); + // Below line will give compile error + //printf ("value of addr %0d in B\n", addr); + printf ("value of cmd %0d in B\n", cmd); + } +} + +class C { + A a; + B b; + task new() { + a = new(); + b = new(); + b.data = 2; + } + task printC() { + printf ("value of data %0d in C\n", a.data); + printf ("value of data %0d in C\n", b.data); + // Below line will give compile error + //printf ("value of addr %0d in C\n", a.addr); + //printf ("value of cmd %0d in C\n", a.cmd); + //printf ("value of addr %0d in C\n", b.addr); + //printf ("value of cmd %0d in C\n", b.cmd); + } +} + +program properties { + C c = new(); + c.a.printA(); + c.b.printB(); + c.printC(); + printf("value of credits is %0d\n",c.a.credits); + printf("value of credits is %0d\n",c.b.credits); + c.a.credits ++; + printf("value of credits is %0d\n",c.a.credits); + printf("value of credits is %0d\n",c.b.credits); + c.b.credits ++; + printf("value of credits is %0d\n",c.a.credits); + printf("value of credits is %0d\n",c.b.credits); +} diff --git a/tests/vera/this_ex.vr b/tests/vera/this_ex.vr new file mode 100644 index 0000000..30b4ec2 --- /dev/null +++ b/tests/vera/this_ex.vr @@ -0,0 +1,14 @@ +class A { + integer i; + + task set_i(integer value) { + this.i = value; + } +} + +program this_ex { + A a = new(); + printf("value of i is %0d\n",a.i); + a.set_i(100); + printf("value of i is %0d\n",a.i); + } diff --git a/tests/vera/virtual_port.vr b/tests/vera/virtual_port.vr new file mode 100644 index 0000000..8c3a5af --- /dev/null +++ b/tests/vera/virtual_port.vr @@ -0,0 +1,45 @@ +#include "vera_defines.vrh" + +// Port Declaration +port count_p { + clock; + reset; + enable; + cout; +} +// This is what connects with HDL +interface count_if0 { + input clock CLOCK; + output reset PHOLD#1; + output enable PHOLD#1; + input [3:0] cout PSAMPLE #-1; +} +// Now bind interface with Port +bind count_p count_bind { + clock count_if0.clock; + reset count_if0.reset; + enable count_if0.enable; + cout count_if0.cout; +} +// Top level program +program virtual_port { + count_p count = count_bind; + // Start the actual test here + @ (posedge count.$clock); + printf("Asserting Reset\n"); + count.$reset = 1; + count.$enable = 0; + @ (posedge count.$clock); + printf("Deasserting Reset\n"); + count.$reset = 0; + @ (posedge count.$clock); + printf("Asserting Enable\n"); + count.$enable = 1; + repeat(10) { + @ (posedge count.$clock); + printf("Counter value %x\n",count.$cout); + } + @ (posedge count.$clock); + printf("Deasserting Enable\n"); + count.$enable = 0; +} diff --git a/tests/vhdl/gcd2.vhd b/tests/vhdl/gcd2.vhd new file mode 100644 index 0000000..6f7dba8 --- /dev/null +++ b/tests/vhdl/gcd2.vhd @@ -0,0 +1,306 @@ +---------------------------------------------------------------------- +-- GCD CALCULATOR (ESD book figure 2.11) +-- Weijun Zhang, 04/2001 +-- +-- we can put all the components in one document(gcd2.vhd) +-- or put them in separate files +-- this is the example of RT level modeling (FSM + DataPath) +-- the code is synthesized by Synopsys design compiler +---------------------------------------------------------------------- + +-- Component: MULTIPLEXOR -------------------------------------------- + +library IEEE; +use IEEE.std_logic_1164.all; +use IEEE.std_logic_arith.all; +use IEEE.std_logic_unsigned.all; + +entity mux is + port( rst, sLine: in std_logic; + load, result: in std_logic_vector( 3 downto 0 ); + output: out std_logic_vector( 3 downto 0 ) + ); +end mux; + +architecture mux_arc of mux is +begin + process( rst, sLine, load, result ) + begin + if( rst = '1' ) then + output <= "0000"; -- do nothing + elsif sLine = '0' then + output <= load; -- load inputs + else + output <= result; -- load results + end if; + end process; +end mux_arc; + +-- Component: COMPARATOR --------------------------------------------- + +library IEEE; +use IEEE.std_logic_1164.all; +use IEEE.std_logic_arith.all; +use IEEE.std_logic_unsigned.all; + +entity comparator is + port( rst: in std_logic; + x, y: in std_logic_vector( 3 downto 0 ); + output: out std_logic_vector( 1 downto 0 ) + ); +end comparator; + +architecture comparator_arc of comparator is +begin + process( x, y, rst ) + begin + if( rst = '1' ) then + output <= "00"; -- do nothing + elsif( x > y ) then + output <= "10"; -- if x greater + elsif( x < y ) then + output <= "01"; -- if y greater + else + output <= "11"; -- if equivalance. + end if; + end process; +end comparator_arc; + +-- Component: SUBTRACTOR ---------------------------------------------- + +library IEEE; +use IEEE.std_logic_1164.all; +use IEEE.std_logic_arith.all; +use IEEE.std_logic_unsigned.all; + +entity subtractor is + port( rst: in std_logic; + cmd: in std_logic_vector( 1 downto 0 ); + x, y: in std_logic_vector( 3 downto 0 ); + xout, yout: out std_logic_vector( 3 downto 0 ) + ); +end subtractor; + +architecture subtractor_arc of subtractor is +begin + process( rst, cmd, x, y ) + begin + if( rst = '1' or cmd = "00" ) then -- not active. + xout <= "0000"; + yout <= "0000"; + elsif( cmd = "10" ) then -- x is greater + xout <= ( x - y ); + yout <= y; + elsif( cmd = "01" ) then -- y is greater + xout <= x; + yout <= ( y - x ); + else + xout <= x; -- x and y are equal + yout <= y; + end if; + end process; +end subtractor_arc; + +-- Component: REGISTER --------------------------------------------------- + +library IEEE; +use IEEE.std_logic_1164.all; +use IEEE.std_logic_arith.all; +use IEEE.std_logic_unsigned.all; + +entity regis is + port( rst, clk, load: in std_logic; + input: in std_logic_vector( 3 downto 0 ); + output: out std_logic_vector( 3 downto 0 ) + ); +end regis; + +architecture regis_arc of regis is +begin + process( rst, clk, load, input ) + begin + if( rst = '1' ) then + output <= "0000"; + elsif( clk'event and clk = '1') then + if( load = '1' ) then + output <= input; + end if; + end if; + end process; +end regis_arc; + +-- component: FSM controller -------------------------------------------- + +library IEEE; +use IEEE.std_logic_1164.all; +use IEEE.std_logic_arith.all; +use IEEE.std_logic_unsigned.all; + +entity fsm is + port( rst, clk, proceed: in std_logic; + comparison: in std_logic_vector( 1 downto 0 ); + enable, xsel, ysel, xld, yld: out std_logic + ); +end fsm; + +architecture fsm_arc of fsm is + + type states is ( init, s0, s1, s2, s3, s4, s5 ); + signal nState, cState: states; + +begin + process( rst, clk ) + begin + if( rst = '1' ) then + cState <= init; + elsif( clk'event and clk = '1' ) then + cState <= nState; + end if; + end process; + + process( proceed, comparison, cState ) + begin + case cState is + + when init => if( proceed = '0' ) then + nState <= init; + else + nState <= s0; + end if; + + when s0 => enable <= '0'; + xsel <= '0'; + ysel <= '0'; + xld <= '0'; + yld <= '0'; + nState <= s1; + + when s1 => enable <= '0'; + xsel <= '0'; + ysel <= '0'; + xld <= '1'; + yld <= '1'; + nState <= s2; + + when s2 => xld <= '0'; + yld <= '0'; + if( comparison = "10" ) then + nState <= s3; + elsif( comparison = "01" ) then + nState <= s4; + elsif( comparison = "11" ) then + nState <= s5; + end if; + + when s3 => enable <= '0'; + xsel <= '1'; + ysel <= '0'; + xld <= '1'; + yld <= '0'; + nState <= s2; + + when s4 => enable <= '0'; + xsel <= '0'; + ysel <= '1'; + xld <= '0'; + yld <= '1'; + nState <= s2; + + when s5 => enable <= '1'; + xsel <= '1'; + ysel <= '1'; + xld <= '1'; + yld <= '1'; + nState <= s0; + + when others => nState <= s0; + + end case; + + end process; + +end fsm_arc; + +---------------------------------------------------------------------- +-- GCD Calculator: top level design using structural modeling +-- FSM + Datapath (mux, registers, subtracter and comparator) +---------------------------------------------------------------------- + +library IEEE; +use IEEE.std_logic_1164.all; +use IEEE.std_logic_arith.all; +use IEEE.std_logic_unsigned.all; +use work.all; + +entity gcd is + port( rst, clk, go_i: in std_logic; + x_i, y_i: in std_logic_vector( 3 downto 0 ); + d_o: out std_logic_vector( 3 downto 0 ) + ); +end gcd; + +architecture gcd_arc of gcd is + +component fsm is + port( rst, clk, proceed: in std_logic; + comparison: in std_logic_vector( 1 downto 0 ); + enable, xsel, ysel, xld, yld: out std_logic + ); +end component; + +component mux is + port( rst, sLine: in std_logic; + load, result: in std_logic_vector( 3 downto 0 ); + output: out std_logic_vector( 3 downto 0 ) + ); +end component; + +component comparator is + port( rst: in std_logic; + x, y: in std_logic_vector( 3 downto 0 ); + output: out std_logic_vector( 1 downto 0 ) + ); +end component; + +component subtractor is + port( rst: in std_logic; + cmd: in std_logic_vector( 1 downto 0 ); + x, y: in std_logic_vector( 3 downto 0 ); + xout, yout: out std_logic_vector( 3 downto 0 ) + ); +end component; + +component regis is + port( rst, clk, load: in std_logic; + input: in std_logic_vector( 3 downto 0 ); + output: out std_logic_vector( 3 downto 0 ) + ); +end component; + +signal xld, yld, xsel, ysel, enable: std_logic; +signal comparison: std_logic_vector( 1 downto 0 ); +signal result: std_logic_vector( 3 downto 0 ); + +signal xsub, ysub, xmux, ymux, xreg, yreg: std_logic_vector( 3 downto 0 ); + +begin + + -- doing structure modeling here + + -- FSM controller + TOFSM: fsm port map( rst, clk, go_i, comparison, + enable, xsel, ysel, xld, yld ); + -- Datapath + X_MUX: mux port map( rst, xsel, x_i, xsub, xmux ); + Y_MUX: mux port map( rst, ysel, y_i, ysub, ymux ); + X_REG: regis port map( rst, clk, xld, xmux, xreg ); + Y_REG: regis port map( rst, clk, yld, ymux, yreg ); + U_COMP: comparator port map( rst, xreg, yreg, comparison ); + X_SUB: subtractor port map( rst, comparison, xreg, yreg, xsub, ysub ); + OUT_REG: regis port map( rst, clk, enable, xsub, result ); + + d_o <= result; + +end gcd_arc; + +--------------------------------------------------------------------------- diff --git a/tests/vhdl/test_multpipe.vhdl b/tests/vhdl/test_multpipe.vhdl new file mode 100644 index 0000000..6a7692e --- /dev/null +++ b/tests/vhdl/test_multpipe.vhdl @@ -0,0 +1,91 @@ +--* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +--* * * * * * * * * * * * * * * * VHDL Source Code * * * * * * * * * * * * * * +--* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +--* Title : Test_MultPipe +--* Filename & Ext : test_multpipe.vhdl +--* Author : David Bishop X-XXXXX +--* Created : 1999/03/12 +--* Last modified : $Date: 1999-03-12 16:41:40-05 $ +--* WORK Library : testchip_lib +--* Description : A pipline multiplier with lots of redundant logic +--* Known Bugs : +--* : +--* RCS Summary : $Id: test_multpipe.vhdl,v 1.1 1999-03-12 16:41:40-05 bishop Exp bishop $ +--* : +--* Mod History : $Log: test_multpipe.vhdl,v $ +--* Mod History : Revision 1.1 1999-03-12 16:41:40-05 bishop +--* Mod History : Initial revision +--* Mod History : +--* : +--* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + +library ieee; +use ieee.std_logic_1164.all; +use ieee.numeric_std.all; + +entity test_multpipe is + generic ( width : integer := 7 ); + port ( clk : in std_ulogic; + reset : in std_ulogic; + enable : in std_ulogic; + inp1, inp2 : in std_logic_vector ( width downto 0); + sum : out std_logic_vector ( (width * 2 ) downto 0) ); +end test_multpipe; + +architecture rtl of test_multpipe is + subtype vectors is std_logic_vector (( width * 2 ) downto 0 ); + subtype unsigneds is unsigned (( width * 2 ) downto 0 ); + type stage_array is array ( width downto 0 ) of vectors; + type adder_array is array ( width downto 0 ) of unsigneds; + signal stage1, stage2 : stage_array; + signal adder_stage : adder_array; + signal reg_stage : adder_array; +begin -- rtl + + -- Fill the two arrays + build_stage1 : process ( inp1 ) + begin + stage_loop1 : for i in 0 to width loop + stage1 ( i ) <= ( others => '0' ); + stage1 ( i ) ( width + i downto i ) <= inp1; + end loop stage_loop1; + end process build_stage1; + + build_stage2 : process ( inp2 ) + begin + stage_loop2 : for i in 0 to width loop + stage2 ( i ) <= ( others => inp2 ( i ) ); + end loop stage_loop2; + end process build_stage2; + +-- and the two arrays together, now you have a matrix which +-- you can add up. + and_stages : process ( stage1, stage2 ) + begin + and_loop : for i in 0 to width loop + adder_stage ( i ) <= unsigned ( stage1 ( i ) and stage2 ( i ) ); + end loop and_loop; + end process and_stages; + + register_stages : process ( reset, clk ) + variable local_sum : unsigned ( sum'high downto 0 ); + begin + if reset = '0' then + reset_loop : for i in 0 to width loop + reg_stage ( i ) <= ( others => '0' ); + end loop reset_loop; + elsif rising_edge ( clk ) then + if enable = '1' then + adder_loop : for i in 0 to ( width + 1 ) / 2 loop + reg_stage ( i ) <= adder_stage ( i ) + adder_stage ( width - i ); + end loop adder_loop; + reg_stage ( 4 ) <= reg_stage ( 0 ) + reg_stage ( 3 ); + reg_stage ( 5 ) <= reg_stage ( 1 ) + reg_stage ( 2 ); + reg_stage ( 6 ) <= reg_stage ( 4 ) + reg_stage ( 5 ); + end if; + end if; + end process register_stages; + + sum <= std_logic_vector (reg_stage ( 6 )); + +end rtl; diff --git a/tests/vhdl/test_parity.vhdl b/tests/vhdl/test_parity.vhdl new file mode 100644 index 0000000..dceafe1 --- /dev/null +++ b/tests/vhdl/test_parity.vhdl @@ -0,0 +1,67 @@ +--* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +--* * * * * * * * * * * * * * * * VHDL Source Code * * * * * * * * * * * * * * +--* * * Copyright (C) 1997 - Eastman Kodak Company - All Rights Reserved * * * +--* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +--* Title : TEST_PARITY +--* Filename & Ext : test_parity.vhdl +--* Author : David Bishop X-66788 +--* Created : 3/18/97 +--* Version : 1.2 +--* Revision Date : 97/04/15 +--* SCCSid : 1.2 04/15/97 test_parity.vhdl +--* WORK Library : testchip +--* Mod History : +--* Description : This is a parity generator which is written recursively +--* : It is designed to test the ability of Simulation and +--* : Synthesis tools to check this capability. +--* Known Bugs : +--* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + +library IEEE; +use IEEE.STD_LOGIC_1164.all; + +-- Parity is done in a recursively called function. +-- Package definition +package Parity_Pack is + function Recursive_Parity ( BUSX : std_logic_vector ) + return std_ulogic; +end Parity_Pack; + +-- Package body. +package body Parity_Pack is + function Recursive_Parity ( BUSX : std_logic_vector ) + return std_ulogic is + variable Upper, Lower : std_ulogic; + variable Half : integer; + variable BUS_int : std_logic_vector ( BUSX'length - 1 downto 0 ); + variable Result : std_logic; + begin + BUS_int := BUSX; + if ( BUS_int'length = 1 ) then + Result := BUS_int ( BUS_int'left ); + elsif ( BUS_int'length = 2 ) then + Result := BUS_int ( BUS_int'right ) xor BUS_int ( BUS_int'left ); + else + Half := ( BUS_int'length + 1 ) / 2 + BUS_int'right; + Upper := Recursive_Parity ( BUS_int ( BUS_int'left downto Half )); + Lower := Recursive_Parity ( BUS_int ( Half - 1 downto BUS_int'right )); + Result := Upper xor Lower; + end if; + return Result; + end; +end Parity_Pack; + +library IEEE; +use IEEE.STD_LOGIC_1164.all; +use work.Parity_Pack.all; + +entity Test_Parity is + generic ( WIDTH : integer := 16); + port ( BUSX : in std_logic_vector ( WIDTH downto 0 ); + Parity : out std_ulogic ); +end Test_Parity; + +architecture RTL of Test_Parity is +begin + Parity <= Recursive_Parity ( BUSX ); +end RTL; diff --git a/tests/vhdl/testchip_core.vhdl b/tests/vhdl/testchip_core.vhdl new file mode 100644 index 0000000..a2fcb0a --- /dev/null +++ b/tests/vhdl/testchip_core.vhdl @@ -0,0 +1,219 @@ +--* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +--* * * * * * * * * * * * * * * * VHDL Source Code * * * * * * * * * * * * * * +--* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +--* Title : TESTCHIP_CORE +--* Filename & Ext : testchip_core.vhdl +--* Author : David W. Bishop +--* Created : 6/6/96 +--* Version : 1.1 +--* Revision Date : 97/12/03 +--* SCCSid : 1.1 12/03/97 testchip_core.vhdl +--* WORK Library : chiptest +--* Mod History : +--* Description : This is a test chip core, designed to test several +--* : functions in Synthesis and simulation +--* Known Bugs : +--* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + +library ieee; +use ieee.std_logic_1164.all; + +entity testchip_core is + port ( clk : in std_ulogic; + reset : in std_ulogic; + dclk : in std_ulogic; + dclk_i : out std_ulogic; + slow_count : out std_logic_vector ( 4 downto 0 ); + enable : in std_ulogic; + load : in std_ulogic; + input_data : in std_logic_vector ( 7 downto 0 ); + shout : out std_ulogic; + control : out std_logic_vector ( 1 downto 0 ); + count : out std_logic_vector ( 7 downto 0 ); + sum : out std_logic_vector ( 8 downto 0 ); + mulout : out std_logic_vector ( 14 downto 0 ); + parity : out std_ulogic ); +end testchip_core; + +architecture rtl of testchip_core is + +--------------------------------------------------------------------------------- Start with the declarations for the sub-blocks +------------------------------------------------------------------------------- + component Test_ClkGen + port ( clk : in std_ulogic; + reset : in std_ulogic; + dclk : out std_ulogic ); + end component; + + component test_counter + generic ( width : integer := 17 ); + port ( clk : in std_ulogic; + reset : in std_ulogic; + enable : in std_ulogic; + count : out std_logic_vector ( width - 1 downto 0) ); + end component; + + component test_add + generic ( width : integer := 17 ); + port ( clk : in std_ulogic; + reset : in std_ulogic; + enable : in std_ulogic; + inp1 : in std_logic_vector ( width downto 0); + inp2 : in std_logic_vector ( width downto 0); + sum : out std_logic_vector ( (width + 1) downto 0) ); + end component; + + component test_reg + generic ( width : integer := 17 ); + port ( clk : in std_ulogic; + reset : in std_ulogic; + enable : in std_ulogic; + sel : in std_ulogic; + inp1 : in std_logic_vector ( width downto 0); + inp2 : in std_logic_vector ( width downto 0); + outpt : out std_logic_vector ( width downto 0) ); + end component; + + component test_shift + generic ( width : integer := 17 ); + port ( clk : in std_ulogic; + reset : in std_ulogic; + load : in std_ulogic; + en : in std_ulogic; + inp : in std_logic_vector ( width downto 0 ); + outp : out std_ulogic ); + end component; + + component test_state + port ( clk : in std_ulogic; + reset : in std_ulogic; + con1, con2, con3 : in std_ulogic; + out1, out2 : out std_ulogic ); + end component; + + component test_multpipe + generic ( width : integer := 7 ); + port ( clk : in std_ulogic; + reset : in std_ulogic; + enable : in std_ulogic; + inp1, inp2 : in std_logic_vector ( width downto 0); + sum : out std_logic_vector ( (width * 2 ) downto 0) ); + end component; + + component test_parity + generic ( WIDTH : integer := 16); + port ( BUSX : in std_logic_vector ( WIDTH downto 0 ); + Parity : out std_ulogic ); + end component; + +-- Signal declarations + signal internal_data : std_logic_vector ( input_data'high downto 0 ); + signal local_count : std_logic_vector ( input_data'high downto 0 ); + signal local_mulout : std_logic_vector ( mulout'high downto 0 ); + signal VCC, GND : std_ulogic; + +begin -- rtl + + VCC <= '1'; + GND <= '0'; + mulout <= local_mulout; + + U1 : test_clkgen + port map ( + clk => clk, + reset => reset, + dclk => dclk_i ); + + U2 : test_counter + generic map ( width => 5 ) + port map ( + clk => dclk, + reset => reset, + enable => VCC, + count => slow_count + ); + + U3 : test_reg + generic map ( width => internal_data'high ) + port map ( + clk => clk, + reset => reset, + enable => VCC, + sel => VCC, + inp1 => input_data, + inp2 => local_count, + outpt => internal_data + ); + + U4 : test_counter + generic map ( width => internal_data'high + 1 ) + port map ( + clk => clk, + reset => reset, + enable => enable, + count => local_count + ); + + U7 : test_add + generic map ( width => internal_data'high ) + port map ( + clk => clk, + reset => reset, + enable => enable, + inp1 => local_count, + inp2 => internal_data, + sum => sum + ); + + U5 : test_shift + generic map ( width => internal_data'high ) + port map ( + clk => clk, + reset => reset, + en => enable, + load => load, + inp => internal_data, + outp => shout + ); + + U9 : test_state + port map ( + clk => clk, + reset => reset, + con1 => local_count ( 0 ), + con2 => local_count ( 1 ), + con3 => local_count ( 2 ), + out1 => control ( 0 ), + out2 => control ( 1 ) + ); + + U8 : test_multpipe + generic map ( width => internal_data'high ) + port map ( + clk => clk, + reset => reset, + enable => enable, + inp1 => local_count, + inp2 => internal_data, + sum => local_mulout ); + + U6 : Test_Parity + generic map ( Width => internal_data'high ) + port map ( + BUSX => internal_data, + Parity => parity ); + + U10 : test_reg + generic map ( width => count'high ) + port map ( + clk => clk, + reset => reset, + enable => VCC, + sel => GND, + inp1 => input_data, + inp2 => local_count, + outpt => count + ); + + +end rtl;